fix: startup scheduler reconciliation replaces blind active_jobs DEL (#455, v3) - #492
Merged
Merged
Conversation
…lind DEL (#455) v3 port of the v2 lib/reconcile.js design that shipped on release/2.6.x in PR #462 (merge a937ec3), adapted to v3's architecture: redis@5 promise API via the shared lib/redis-client.js helpers (#454), async/await, and the v3 boot sequence in server.js. server.js previously ran client.del("active_jobs") at boot, orphaning every job still live on the scheduler and leaving their TTL-less hashes behind forever (issue #455 gap 2). Now boot takes a single scheduler snapshot (squeue/qstat) and reconciles: - entries whose scheduler job is live are KEPT exactly once (deduped, atomic multi rebuild), with a defensive PERSIST against the #453 resurrect-same-id TTL trap; - already-terminal entries are dropped with retention TTLs (completed 24h, other terminals 1h); - dead-scheduler-id zombies are marked aborted (orphaned-at-restart error) with the terminal TTL; - a SCAN sweep (TYPE-guarded against WRONGTYPE) applies the same treatment to in-flight-claiming hashes outside active_jobs (issue #455 gap 1); - FAIL OPEN on any scheduler error — active_jobs is left untouched, and a 15s exec timeout keeps a hung lister from wedging boot (v3-only hardening; submit_type local resolves an empty snapshot without exec). Both spawn surfaces — socket handler registration AND the MCP server — are gated on reconciliation completing, so no new submission can race the snapshot or the sweep. reconcileActiveJobs never rejects; the boot gate depends on that contract. Tests: test/reconcile.js (real sbatch survivor), test/reconcile-edges.js (fail-open, timeout, dedup, qsub branch, local branch), test/ reconcile-sweep.js (SCAN sweep + TYPE guard), test/regression/ boot-reconcile-gate.js (static tripwire: no blind DEL, gate ordering). Closes #455
12 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #455.
v3-only port to
masterof the v2lib/reconcile.jsdesign that shipped onrelease/2.6.xin PR #462 (merge a937ec3), adapted to v3's architecture: redis@5 promise API via the sharedlib/redis-client.js(TTL helpers from #454), async/await, and the v3 boot sequence inserver.js.What changes
server.jsused to run a blind boot-timeclient.del("active_jobs"), orphaning every job still live on the scheduler and leaving their TTL-less hashes behind forever. Boot now takes a single scheduler snapshot (squeue/qstat) and reconciles:MULTI DEL+RPUSHrebuild, purging the historical duplicate-push backlog, with a defensivePERSISTagainst the Ensure proper cleanup of transient data in Redis (bound memory growth) #453 resurrect-same-id TTL trap;aborted(orphaned at server restart) with the terminal TTL;active_jobs(issue gap 1 — the pre-fix restarts deleted the list but not the hashes);active_jobsis left untouched, never mass-aborted. v3-only hardening on top of the v2 module: a 15s exec timeout so a hung lister cannot wedge the boot gate, andsubmit_type: localresolves an empty snapshot without exec'ing a lister.Both spawn surfaces — socket handler registration and the MCP server — are gated on reconciliation completing, so no new submission can race the snapshot or the sweep.
reconcileActiveJobsnever rejects; the gate depends on that contract (asserted by tests).Verification (silverback, isolated Redis :6390 — never prod)
Unit tier — 34 new tests, all green; wired into
test:ci(shimmed suites + static tripwire) andtest:slurm-unit(realsbatchsuite):test/reconcile.js— realsbatch --wrap 'sleep 180'survivor: seeded[live, zombie, zombie, terminal]→entries=4 unique=3 kept=1 reaped=2, live kept exactly once with no TTL, zombie aborted + 1h TTL, terminal reaped + 24h TTL.test/reconcile-edges.js— fail-open (nonzero exit), 15s hung-lister timeout, resolve-on-every-path contract, atomic dedup rebuild, no-hash/unparseable-torque_id edges, qsubqstatparsing branch,localbranch.test/reconcile-sweep.js— SCAN sweep semantics incl. TYPE guard (string/list keys untouched, noHGETALLever issued on them), scheduler-side hashes, batch iteration (redis@5scanIteratoryields arrays).test/regression/boot-reconcile-gate.js— source tripwire: no blinddel("active_jobs"), reconcile precedes both spawn surfaces.npm run test:cigreen (315 + 151 + 7passing) with the CI fixture config;npm run test:slurm-unitgreen (22 passing);npm run lint0 errors; typecheck error count identical to master (no new errors); the 4 new test files are prettier-clean.End-to-end restart survival (dev checkout,
submit_type: slurm, real GARD on CD2.nex via the WebSocket path):active_jobs=[D],TTL(D)=-1→kill -9the server mid-run → restart → boot logreconcile : active_jobs entries=4 unique=3 kept=1 reaped=2 zombie hashes swept=1; D kept exactly once, hash untouched, TTL still -1, SLURM job kept RUNNING (+10s/+30s checks) and completed normally (97s wall) with a validGARD.json. Re-engaging the same id post-restart finalized the lifecycle:status=completed, results in Redis, TTL 86396s; delivered via thejob:statussocket route and MCP HTTPget_job_results(full OAuth dance) — the exact lifecycle the blind DEL destroyed.aborted, errororphaned at server restart, TTL 3600.zombie hashes swept=1, orphan aborted + terminal TTL, string/list untouched, no WRONGTYPE anywhere in the logs.squeueexiting 2 →could not snapshot scheduler queue, leaving active_jobs untouched, list byte-identical (duplicate intact, nothing zombified), WS + MCP surfaces serving; boot withsqueuehung 60s → boot completed after ~17s with the same fail-open warn.entries=3 unique=3 kept=0 reaped=3, eachaborted+ TTL.spawn_analysis(FEL) immediately post-boot spawned cleanly through the gated surface; the job COMPLETED on SLURM with a validFEL.json.v2/
release/2.6.xalready shipped this in #462 and is untouched here.