Fix native rebuild runtime verification deadlock
This commit is contained in:
@@ -35,15 +35,21 @@ export function runningVersions(): PlutoniumVersion[] {
|
||||
: revision < target ? 'outdated' : revision > target ? 'ahead' : 'current' };
|
||||
});
|
||||
}
|
||||
export async function probeVersion(server: string, options: { maintenance?: boolean } = {}): Promise<number | null> {
|
||||
export async function probeVersion(server: string, options: { maintenance?: boolean; operationId?: string } = {}): Promise<number | null> {
|
||||
// Startup verification calls this directly; only maintenance observers yield
|
||||
// ownership, without replacing provisioning's version evidence with an error.
|
||||
const suppressed = () => options.maintenance && hasActiveProvisioning(server);
|
||||
const suppressed = () => (options.maintenance && hasActiveProvisioning(server)) || (options.operationId !== undefined
|
||||
&& !db().prepare(`SELECT 1 FROM server_deployments WHERE id=? AND server=? AND server_deleted_at IS NULL
|
||||
AND action IN ('create','rebuild') AND state='awaiting-health' AND submitted_at IS NOT NULL`).get(options.operationId, server));
|
||||
// The verifying operation keeps its recovery slot until every readiness
|
||||
// check passes. Waiting on its own slot would deadlock native rebuilds.
|
||||
const recoveryBlocked = () => restartRecoveryPending(server) && !(options.operationId
|
||||
&& db().prepare('SELECT 1 FROM restart_rollout WHERE server=? AND operation_id=? AND healthy_at IS NULL').get(server, options.operationId));
|
||||
if (suppressed()) return null;
|
||||
const original = getServer(server);
|
||||
if (!original) return null;
|
||||
const epoch = latestGameSubmission(server);
|
||||
const recovering = restartRecoveryPending(server);
|
||||
const recovering = recoveryBlocked();
|
||||
const health = !recovering && original.desired_state !== 'stopped'
|
||||
? await komodoInspectHealthy(server).catch(() => ({ ok: false })) : { ok: false };
|
||||
if (suppressed()) return null;
|
||||
@@ -55,7 +61,7 @@ export async function probeVersion(server: string, options: { maintenance?: bool
|
||||
// or replacement registration, including replacements with no submission yet.
|
||||
if (!current || current.created_at !== original.created_at || epoch !== latestGameSubmission(server)) return null;
|
||||
const checkedAt = nowIso();
|
||||
if (restartRecoveryPending(server)) {
|
||||
if (recoveryBlocked()) {
|
||||
result = { revision: null, error: 'Waiting for restart recovery.' };
|
||||
} else if (current.desired_state === 'stopped') {
|
||||
result = { revision: null, error: 'Server is intentionally stopped.' };
|
||||
|
||||
@@ -319,7 +319,7 @@ async function processJob(job: Job, eligibility: (job: Job) => string | null) {
|
||||
const recipe = recipeForDeployment(job.id)!.recipe;
|
||||
if (status.map !== recipe.expectedMap) { hold(job, `Waiting for selected map ${recipe.expectedMap}; observed ${status.map ?? 'unknown'}.`); return; }
|
||||
await checkRelease();
|
||||
const revision = await probeVersion(job.server); const target = freshRelease();
|
||||
const revision = await probeVersion(job.server, { operationId: job.id }); const target = freshRelease();
|
||||
if (revision === null || target === null) { hold(job, 'Waiting to verify the Plutonium runtime revision.'); return; }
|
||||
if (revision < target) { finishDeployment(job.id, { ok: false, message: `Running Plutonium r${revision} is older than production r${target}.` }); return; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user