slurm: fall back to sacct when scontrol cannot answer - #10
Open
dadamsncsa wants to merge 1 commit into
Open
dadamsncsa wants to merge 1 commit into
dadamsncsa wants to merge 1 commit into
Conversation
The scontrol lookup at the top of wait_final_status is not guarded, and run_command raises RuntimeError on any non-zero exit. scontrol only knows jobs the controller still holds, so once a finished job is older than the site's MinJobAge the poll raises instead of reading the answer from the sacct call sitting directly beneath it. That sacct loop was the whole of wait_final_status before fcf92e0 put scontrol in front of it; it is now unreachable in exactly the case it exists for. On NCSA DeltaAI (Slurm 25.11.1, MinJobAge = 300 s) a job that has aged out of the controller gives: $ scontrol show job -o 3159304 slurm_load_jobs error: Invalid job id specified (exit 1) $ sacct -j 3159304 --parsable2 --noheader \ --format=JobID,State,ExitCode,Elapsed,AllocCPUS,NodeList 3159304|COMPLETED|0:0|00:03:40|64|gh007 Any poll interval or stall longer than MinJobAge therefore turns a COMPLETED job into a failed flow. By inference from the code rather than from a run here, the same happens on the very first poll wherever scontrol is restricted or not installed, since create_subprocess_exec then raises FileNotFoundError. Asking the controller first is right: it answers while the job is live and does not depend on accounting being configured. So this keeps scontrol and makes it an optimisation that degrades. A failed scontrol is logged at DEBUG and leaves stdout empty, and the loop reads sacct as before. A failed sacct still raises, because with neither source there is nothing to report. What that costs is written into the docstring rather than fixed here. If scontrol fails and sacct exits 0 with no row for the job -- accounting down, or the record purged -- the poll has no answer from either source and the wait then ends only at timeout_seconds, which run_slurm_job leaves at None. Today that combination ends the wait at once, by failing it. Bounding the wait properly is a separate change; this one states the behaviour in the docstring and pins it with a test. Two things in the same few lines go with it. The raw scontrol record was logged at INFO behind a "DEBUG:" prefix -- da81614, merged as qiskit-community#36, had just promoted it there from a print() -- which at the default ten-second interval writes several hundred copies of the full job record into the run log of a job that waits half an hour. It is now a real DEBUG record, formatted lazily so the string is not built when DEBUG is off. The neighbouring INFO line announcing the final state still uses an f-string; leaving it alone keeps the diff to one idea. And the terminal-state list was written out twice, once as _is_terminal_state() and once inline in the loop. The two copies agree today, so dropping the inline one changes no outcome on the scontrol path; it removes a second list that has to be maintained in step with the first. The helper is the copy worth keeping because the inline one compared for equality, and sacct decorates a state: over jobs run here since 2026-09-14, sacct --parsable2 reports "CANCELLED by 33444" on 15 rows, and its default fixed-width output truncates the same value to "CANCELLED+". Neither spelling can reach this code today -- the loop passes --parsable2, so nothing is truncated, and scontrol -o does not decorate at all (in one snapshot of this controller all 2,169 JobState= values were bare). So that tolerance is insurance for the sacct branch rather than a captured case. The helper also gained a guard for an empty state string rather than raising IndexError on one. The tests drive run_command through a fake that dispatches on the command name and records the full argv: a raising scontrol still returns the terminal status from sacct, a missing scontrol does too, a scontrol answer short-circuits without calling sacct, each of the nine terminal states ends the wait while PENDING, RUNNING, COMPLETING and SUSPENDED fall through to sacct, a still-running job is polled twice, a job neither source knows raises WaitTimeout, a failing sacct still raises, and the scontrol record appears at DEBUG and nowhere above it. Nine of the thirty new cases fail against this file's previous contents; the rest pin paths that already work and that this change must not drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Upstream PR qiskit-community#36 ("Replaced the logic to use
scontrolinstead of usingsacct") put ascontrol show job -o <id>call at the top of thewait_final_statuspoll, ahead of theexisting
sacctparse. Preferring the controller is the right instinct — it answers whilethe job is live and does not depend on accounting being configured. But the call is not
guarded, and
run_commandraisesRuntimeErroron any non-zero exit, so thesacctfallback directly beneath it is now unreachable in exactly the case it exists for.
scontrolonly knows jobs the controller still holds. Measured on NCSA DeltaAI, whereMinJobAge = 300:The controller has forgotten the job, the accounting database still has it, and the wait
raises instead of returning COMPLETED. Any poll gap longer than the site's
MinJobAge— along
watch_poll_interval, a stalled event loop, a driver that reconnects — turns asuccessful job into a failed flow. On a site where
scontrol show jobis restricted orabsent, every poll raises on the first attempt.
What this changes. The
scontrolprobe becomes an optimisation that degrades: if itcannot answer, the poll falls through to
sacctas it did before qiskit-community#36. Two things in the samefew lines come with it:
scontrolrecord was logged at INFO on every poll (prefixedDEBUG:). A job thatwaits 36 minutes at the default 10-second interval writes that record over two hundred
times into the run log. It moves to DEBUG.
_is_terminal_state()at module level, whichtolerates Slurm's trailing
+(as inCANCELLED+), and an inlinefinal_stateslist inthe loop, which does not. They can drift, and already differ. Both paths now use the helper.
Review focus. That a genuinely broken
sacctstill raises — the fallback must not swalloweverything — and whether the
scontrolfailure deserves a one-time warning rather thansilence.
Interacts with the abandoned-wait PR: after both, a failing
scontrolno longer cancelsthe job, which is the intended behaviour. They touch adjacent lines; expect a small conflict.
Limits a reviewer should know:
scontrolfails ANDsacctexits 0 with no row — accounting down, or the record purged— the poll has no answer from either source and the wait ends only at
timeout_seconds, whichrun_slurm_jobleaves atNone. This restores the pre-Added components for RPI demo qiskit-community/qcsc-prefect#36sacct-only behaviour rather thanintroducing a new failure; today's loud immediate failure is an accident of the unguarded call.
Bounding that wait properly (a consecutive-empty-poll budget, or defaulting
timeout_secondsfrom the job's
TimeLimit) is deliberately a separate change.watching a long wait sees nothing distinguishing "still queued" from "
scontrolis broken". OneWARNING on the first poll where neither source answered would be a small follow-up.
except (RuntimeError, OSError)is wider than the two failures argued for, becauserun_commandraises a bareRuntimeError. Making the catch exact means givingrun_commanditsown exception type, which touches the fugaku/miyabi/local runtimes too.
CANCELLED+tolerance on thescontrolpath is drift insurance, not a captured case:scontrol show job -oemits bare states here. The decoration is real on thesacctside.git merge-treeconfirms real conflicts in bothruntime.pyandtest_slurm_runtime.py. Whoever resolves it decides whether a guardedscontrolpoll should still leave the job alone — these two branches say yes, together.Part of the NCSA DeltaAI port series (index: #1). Base is
main, which mirrorsqiskit-community/qcsc-prefect@93a7940. Nothing here has been offered upstream;this fork is a review surface.