--- id: "bug-1766" status: "fixed" status_updated: "2026-09-25" status_note: "601b245e" component: "scheduler" severity: "crash" timestamp: "2026-09-04T08:03:28Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: [] tags: ["scheduler", "compound-resreq", "rollback", "partial-allocation", "open-gap", "scheduler-allocation-spec", "requirements-review"] related: [] fix_commits: ["601b245e"] legacy: true --- # bug-1766: c source: sched_make_compound_allocation() does not roll back already-succeeded primaries when a… ## Description OPEN, NOT A CRASH -- discovered while reviewing the scheduler-allocation spec requirements against live scheduler.c source: sched_make_compound_allocation() does not roll back already-succeeded primaries when a LATER primary in the same COMPOUND resreq fails allocation, and its own post-loop code can misattribute a partial allocation to a failed compound. ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause sched_make_compound_allocation() (src/scheduler/src/scheduler.c:206-238) loops over a COMPOUND resreq's primaries, calling sched_make_primary_alloc_(sched, &ctx) for each with ONE shared ctx across the whole loop. On a mid-loop failure (rc <= 0) it just `break`s -- it never undoes the primaries that already succeeded (no zeroing of ctx.alloc, no reversal of any resource state those primaries touched). Worse: seqjob/parjob's job_alloc_fn sets ctx->alloc directly on success (see seqjob_allocator.c my_alloc()), so if primary 1 of a 2-primary compound succeeds (leaving ctx.alloc non-NULL) and primary 2 then fails, the function's unconditional post-loop `if (ctx.alloc != NULL) { MOVE_PTR(obj->alloc, ctx.alloc); }` still fires regardless of the overall `rc <= 0` -- so a COMPOUND job that is reported as allocation-FAILED can still end up with obj->alloc set to primary 1's partial, orphaned allocation. requirements.md for scheduler-allocation (Req 9 AC2) had assumed, based on the sibling scheduler-framework brief's own text, that this path already 'tracks allocations in ctx and cleans up on failure' -- that claim does not hold against the actual code; requirements.md was corrected during this review. Not yet confirmed whether the orphaned ctx.alloc is ever read for a job later found to be PEND/failed (i.e. whether this is dormant/leak-only or produces an observably wrong result) -- not investigated further since it is out of the scheduler-allocation spec's own boundary (Req 9 AC2 explicitly freezes COMPOUND/ALTER dispatch as unchanged). ## Fix NOT FIXED -- out of scope for the scheduler-allocation spec (which explicitly leaves COMPOUND/ALTER dispatch unchanged, Req 9 AC2) and no other spec currently owns sched_make_compound_allocation(). Discovered only because scheduler-allocation's own NEW per-plan rollback (Req 4's rollback_plan_allocation(), for the parjob multi-host-bucket case) needed a reusable precedent to model itself on; this path was found not to provide one. Needs its own follow-up: likely either (a) track which primaries in the compound loop actually succeeded and explicitly free/clear their contribution to ctx.alloc before returning on a later failure, or (b) defer the MOVE_PTR(obj->alloc, ctx.alloc) until the loop confirms overall success, freeing ctx.alloc on the failure path instead. Whether this ever produces an observably wrong result (vs. a dormant orphaned pointer) is unconfirmed and should be checked first. ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._