5.3 KiB
| id | status | status_updated | status_note | component | severity | timestamp | last_seen | occurrences | files | tags | related | fix_commits | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bug-1892 | fixed | 2026-10-01 | scheduler | wrong-result | 2026-09-30T03:58:52Z | 2026-09-30T03:58:52Z | 1 |
|
|
|
|
bug-1892: xce_scheduler admission check reads per-resource uncommitted amounts by list position, not index
Description
Observed: found by code reading, so there is no failing run yet. unsatisfy_() (src/scheduler/src/resource.c:130-169) compares
if (total[i] < inuse[i] + uncomitted[i] + wanted[i])
where total, inuse and wanted are positional arrays (entry i is the
i-th resource of the request, gathered by get_resvals(..., wanted->idxs, ...)),
but uncomitted is the context's dense, resource-index-keyed vector
(ctx->uncomitted.shared / ctx->uncomitted.hosts[w], 1024 floats,
schedobj.c:514/595), filled by xvec_idval(uncomit, &req, XVEC_ADD_FLOAT),
which writes d[idx] += val (lib/xvec.c, DEFINE_XVEC_IDVAL_OP). Both
sched_check_shared_satisfy_() (resource.c:309) and
sched_check_host_satisfy_() (resource.c:376) pass uncomitted->data
straight through, so entry i reads the uncommitted amount of resource index
i, not of resource wanted->idxs[i].
Expected: entry i uses uncomitted[wanted->idxs[i]] (gather it
positionally like total/inuse, e.g. with the existing get_resvals()).
Impact: wrong admission result. Uncommitted amounts are nonzero only
within one allocation context that already committed something, i.e. from the
second primary of a COMPOUND (or ALTER) job on, since those primaries share one
ctx (scheduler.c:348-354); the first primary and ordinary PRIMARY jobs
always see all-zero uncommitted vectors, and parjob rolls back failed plans.
For a later primary, the check subtracts an unrelated resource's uncommitted
amount (or 0) instead of the earlier primaries' real claim on the same
resource, so it can over-admit (earlier primary's claim ignored: the job is
dispatched beyond host/shared capacity) or wrongly pend (another resource's
amount charged). Pend reasons printed for the same check carry the wrong
shortfall.
Found by: review of the scheduler-allocator-runner requirements
(2026-09-29), while checking the shared-resource precheck the runner inherits.
Environment
- OS: no /etc/os-release; Docker container; NixOS kernel; kernel Linux 6.18.33 x86_64
- libc: glibc 2.42-47 (interpreter /nix/store/wb6rhpznjfczwlwx23zmdrrw74bayxw4-glibc-2.42-47/lib/ld-linux-x86-64.so.2)
- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1
- Build: asan (xce.build)
- Code: scheduler-framework @ a370d724-dirty
- Deployment: n/a: found by code reading; affects any deployment scheduling COMPOUND/ALTER jobs
Reproduction
git checkout a370d724; readsrc/scheduler/src/resource.c:130-169(unsatisfy_) and its two callers at:309and:376, andsrc/scheduler/src/schedobj.c:509-514,586-600(dense 1024-entry uncommitted vectors) andsrc/lib/xvec.cDEFINE_XVEC_IDVAL_OP(d[idx] += val).- Unit-test sketch (white-box
#include "resource.c", liketest_resource_accounting.c): one host withmemtotal 10 at resource idx 5 (idx 0 =slots, plenty); set the host's uncommitted vector[5] = 8(an earlier primary's claim); callsched_check_host_satisfy()withwanted = {idx 5: 4}. Expected 0 (8 + 4 > 10); today it returns 1, because it readsuncomitted[0](= 0).
- Frequency: deterministic (by code reading; no test exercises it yet)
- Evidence: not kept (code reading only)
Root cause
unsatisfy_() (src/scheduler/src/resource.c) indexed the uncommitted vector positionally (uncomitted[i]) like total/inuse/wanted, but both callers, sched_check_shared_satisfy_() and sched_check_host_satisfy_(), pass the allocation context's dense vector keyed by resource index (ctx->uncomitted.shared / .hosts[w], 1024 floats, filled with xvec_idval()). Entry i therefore read the uncommitted amount of resource index i instead of wanted->idxs[i]. It matters from the second primary of a COMPOUND/ALTER job on, the only place a context holds nonzero uncommitted amounts.
Fix
unsatisfy_() takes the uncommitted xvec_t* and reads it through a new uncomit_at_(uncomitted, wanted->idxs[i]), which returns 0 for a NULL vector, a negative index, or an index past the vector's end. The last case is needed because sim_loop.c's reconcile checks pass a short all-zero vector sized to the request; reading it by resource index would otherwise run past its end.
Verification
- Test:
test_host_satisfy_counts_uncommitted_of_the_wanted_resourceRED ("Expected 0 Was 1": over-admit),test_host_satisfy_ignores_uncommitted_of_another_resourceRED ("Expected 1 Was 0": wrong pend),test_shared_satisfy_counts_uncommitted_of_the_wanted_resourceRED ("Expected 0 Was 1"), all GREEN after;test_host_satisfy_short_uncommitted_vector_counts_as_zero(guards the bounds check, passes before and after), all in testsuite/test_resource_accounting.c. - Suites: scheduler ASAN suite 97/97 (build.alloc), 2026-10-01.
- Live: n/a for HA (no HA path touched). Sanity: the bug-1897 replay (
--sim) still completes, exit 0, no ASan report.
References
- Related: none