Skip to content

Fix multi-QOS per-user/account GRES usage recharged to wrong QOS on reload#207

Open
Yash0270 wants to merge 7 commits into
SchedMD:slurm-26.05from
Yash0270:fix/multiqos-gres-usage-leak
Open

Fix multi-QOS per-user/account GRES usage recharged to wrong QOS on reload#207
Yash0270 wants to merge 7 commits into
SchedMD:slurm-26.05from
Yash0270:fix/multiqos-gres-usage-leak

Conversation

@Yash0270

@Yash0270 Yash0270 commented Jun 28, 2026

Copy link
Copy Markdown

Problem

A job can be submitted with more than one QOS (--qos=a,b). It runs under whichever member the scheduler picked, and its GRES/TRES usage is accounted to that member (saved in job_ptr->qos_id).

When the controller recovers job state from StateSaveLocation (a slurmctld restart or a backup takeover) it re-reads each running job and resets its QOS to the head of the QOS list instead of the member it actually started under, then recharges its per-user/per-account usage there. The list head is the first --qos member after sorting by priority; when the listed QOS share the same priority, the head is simply the first one listed.

So every running multi-QOS job a user has is re-attributed to that single head QOS, regardless of which member it really ran under. The head QOS's usage counter then grows past what is physically running, and new jobs are blocked with QOSMaxGRESPerUser. In production the head QOS's per-user counter showed ~976 GPUs in use while the user was actually running only ~300 (the two QOS had equal priority, so the head was just the first one in the job's --qos list).

Where it goes wrong: job_mgr_load_job_state() resolves the QOS via _get_qos_info(), which returns list_peek() (the list head) and overwrites job_ptr->qos_id with it; _restore_job_accounting() then recharges usage against that member.

The in-place scontrol reconfigure path is affected in Slurm 25.05 version but it got fixed in the future version, 25.11.

Fix

At state recovery, keep a running/suspended multi-QOS job on the member it was dispatched under (job_ptr->qos_id); only pending jobs fall back to the list head. This mirrors the pending-vs-running handling already in _foreach_cache_update_job(), and makes accounting reflect the QOS the job is physically running under.

That is the whole fix: one change in job_mgr_load_job_state(). The qos_ptr restore already present in _adjust_limit_usage() keeps acct_policy_add_job_submit() from leaving the job on the head member before the subsequent JOB_BEGIN, so nothing else is needed.

(Note for backports: on 25.05 that restore in _adjust_limit_usage() runs only for ACCT_POLICY_REM_SUBMIT, so the ADD_SUBMIT highest-QOS bump is left in place and this job_mgr.c change alone is not sufficient there; a 25.05 backport also needs _adjust_limit_usage() to skip _set_highest_prio_qos_ptr() for running jobs.)

Target Release

Targets 26.05.2 Slurm release. This is a minor patch to an existing bug.

Issue

Reported in SchedMD support ticket: https://support.schedmd.com/show_bug.cgi?id=23033

Testing

Regression testing: added an ATF test (testsuite/python/tests/test_124_2.py) with two cases:

  • test_multiqos_gres_usage_survives_reconfigure guards the in-place cache-rebuild path.
  • test_multiqos_gres_usage_survives_restart exercises the state-recovery path this fix targets; it fails on unpatched 26.05 and passes with the fix.

Functional testing: verified end-to-end on a from-source 26.05 cluster, comparing a plain-26.05 build against the patched build and triggering a slurmctld restart:

sbatch --qos=high     --gres=gpu:2 --wrap "sleep 900"   # fills high's per-user cap (MaxTRESPerUser=gres/gpu=2)
sbatch --qos=low,high --gres=gpu:2 --wrap "sleep 900"   # forced onto non-head 'low'
scontrol -o show assoc_mgr flags=qos     # per-user gres/gpu used
# restart slurmctld (recovers job state from StateSaveLocation)
scontrol -o show assoc_mgr flags=qos     # re-check
per-user gres/gpu before fix after fix
pre-restart low=2 high=2 low=2 high=2
post-restart low=0 high=4 (job re-attributed to the head) low=2 high=2 (stays on low)

(For reference, scontrol reconfigure was also tested and is unaffected on 26.05, before and after the fix.)

@Yash0270
Yash0270 force-pushed the fix/multiqos-gres-usage-leak branch 6 times, most recently from 4c0361b to abf754b Compare June 28, 2026 19:34
@Yash0270
Yash0270 marked this pull request as ready for review June 29, 2026 16:18
@Yash0270
Yash0270 marked this pull request as draft June 30, 2026 17:23
@Yash0270
Yash0270 marked this pull request as ready for review June 30, 2026 17:44
@Yash0270
Yash0270 marked this pull request as draft June 30, 2026 17:51
@Yash0270
Yash0270 marked this pull request as ready for review July 1, 2026 05:03
Yash0270 added 7 commits July 1, 2026 23:51
A job submitted with multiple QOS (--qos=a,b) is dispatched and charged
under one qos_list member, recorded in job_ptr->qos_id. On state reload
(scontrol reconfigure, slurmctld restart, backup controller takeover)
job_mgr_load_job_state() resolves qos_ptr via _get_qos_info(), which
returns the highest priority qos_list member (list_peek()) and then
overwrites job_ptr->qos_id with it. For a running/suspended job this
re-attributes its per-user and per-account QOS TRES usage to the head
member when _restore_job_accounting() recharges it, inflating that
member's counters and spuriously blocking jobs with QOSMaxGRESPerUser.

Keep a running/suspended multi-QOS job on the member it was dispatched
under (job_ptr->qos_id), mirroring the pending-vs-running handling in
_foreach_cache_update_job(). Pending jobs still resolve to the highest
priority member. The qos_ptr restore already present in
_adjust_limit_usage() keeps acct_policy_add_job_submit() from leaving the
job on the head member before the subsequent JOB_BEGIN.

Changelog: slurmctld - Fix multi-qos per-user and per-account QOS TRES usage being recharged against the wrong QOS member on state reload.
Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
… test

Verify a running --qos=low,high job forced onto the non-highest-priority
member stays attributed to it (QOS label and per-user gres/gpu usage)
across scontrol reconfigure.

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
The existing test only triggers "scontrol reconfigure", which on current
26.05 already preserves the running job's dispatched-QOS attribution, so it
passes with or without the fix and does not actually exercise it. The path the
fix targets is job state reload via job_mgr_load_job_state(), hit on a slurmctld
restart / backup takeover.

Add test_multiqos_gres_usage_survives_restart, which restarts slurmctld and
asserts the running --qos=low,high job stays charged to its dispatched member
(QOS_LOW) instead of being recharged to the highest member. This case fails on
unpatched 26.05 and passes with the fix. Add an autouse fixture to cancel jobs
between the two tests so the per-user QOS cap is free for each.

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
ATF's function-scoped autouse fixture (conftest.py function_setup) already
cancels each test's jobs on teardown, so the extra cancel_jobs_after_test
fixture added alongside the restart test is unnecessary; the two tests are
isolated by the framework. Removing it (and its misleading comment).

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
used_gpu_for_qos() matched per-user gres/gpu usage with a greedy
"[^}]*gres/gpu=..." pattern. A user's assoc_mgr block holds two gres/gpu
entries: the MaxTRESPU used value and the MaxTRESRunMinsPU "unlimited"
sentinel (2*UINT32_MAX). The greedy match ran to the last one, so the
helper returned 8589934590 instead of the used value (2), and the
baseline "assert used_gpu_for_qos(QOS_LOW) == JOB_GPUS" failed before the
reload path could be exercised.

Anchor the match on the MaxTRESPU field and capture the used value from
its parentheses, robust to the limit being N or a number. Verified on
26.05 and master: both tests fail on unpatched builds for the intended
reason and pass with the fix.

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
Non-functional: black (pre-commit) wraps two >88-char lines added earlier
in this series. Kept separate from the functional change per CONTRIBUTING.

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
used_gpu_for_qos only checked the per-user (MaxTRESPU) block, but the fix
also covers per-account attribution. Add used_gpu_for_acct_under_qos()
parsing the MaxTRESPA (Account Limits) block and assert account usage
stays on the dispatched QOS across reconfigure and restart. Drop the now-
redundant helper docstrings to match the surrounding test style.

Signed-off-by: Yash Gupta <guptayash0270@gmail.com>
@Yash0270
Yash0270 force-pushed the fix/multiqos-gres-usage-leak branch from 3d40a39 to 44f4329 Compare July 2, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant