5.4 KiB
| id | status | status_updated | status_note | component | severity | timestamp | last_seen | occurrences | files | tags | related | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bug-1923 | open | 2026-10-08 | newly logged | scheduler | wrong-result | 2026-10-08T07:17:34Z | 2026-10-08T07:17:34Z | 1 |
|
|
bug-1923: xce_scheduler --sim drops shared resource values from the snapshot (license shows value=(none))
Description
Observed: a shared (cluster-wide, GLOBAL|SHARED|CONSUMABLE) resource value that is present in a recording's SnapshotEntry is not loaded by xce_scheduler --sim. info resource license, after step or straight after load, shows:
[sim] resource license: desc= valtype=numeric ordtype=incr flags=GLOBAL,SHARED,CONSUMABLE interval=0
[sim] value=(none)
[sim] DISPATCH jobid=0-0-0-101
[sim] PEND jobid=0-0-0-102
[sim] PEND jobid=0-0-0-103
[sim] PEND jobid=0-0-0-104
When the same value arrives as a RESVALS_FULL replay event, it loads (value=2.00 used=2.00), and jobs 102 and 103 dispatch.
Expected: every shared resource value in the snapshot is loaded into core->sharedInst, so the simulator starts with the same shared pool the recorder had.
Impact: wrong result in the simulator. A replay whose shared resource values were set before recording started, and never re-sent as a RESVALS_FULL event during the recording, simulates with no license pool. Jobs needing the resource pend instead of dispatching, so a sim replay diverges from what live did. Nothing is logged when the value is dropped.
Found by: spec task scheduler-sim-decision-trace 1.1, while building the trace-off REPL baseline fixture (src/scheduler/testsuite/test_repl_trace_baseline.c). That fixture now sends the license value as a RESVALS_FULL event to work around this bug.
Two separate defects were found (see Reproduction, variants A and C):
- Read side, confirmed with gdb and a decoder:
snapshot_load.c:129skips the entry whenrv->host != NULL. After the capnp round trip, a host that was written as NULL decodes as"", so every shared resval in a snapshot is skipped and line 140'szarray_putis never reached. TheRESVALS_FULLpath (core_events.c:1124-1147) has no such host check. - Write side, Suspected (from code reading, matches the decoder output):
snapshot.c:431loopsfor (i = 0; i < core->sharedInst.count; i++), butzarray_put_ex(src/lib/zarray.c:291-330) counts filled slots, not the highest index plus one. A sparsesharedInsttherefore drops values at higher indices: with the license at idx 2 andcount == 1, the snapshot carriesresvals count=0. The same pattern may affect per-worker resvals atsnapshot_wire.c:242(w->resvals.count); not checked.
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 @ ed201686-dirty
- Deployment: none (offline
xce_scheduler --sim <replay> --interactive; the replay is synthetic, written by a test generator with 2 virtual workers) - Note:
-dirty= only the uncommitted task 1.1 test files and unrelated .wolf/ bookkeeping; no production scheduler source was changed
Reproduction
Durable copies are in /home/claude/tmp/bug-1923/: fixA/contention.replay has the value in the snapshot (license idx 2); fixC/contention.replay is the same but the license is idx 0; fixB/contention.replay uses the RESVALS_FULL workaround.
- Build the scheduler:
bash src/tools/xce-build.sh scheduler(ASAN,src/xce.build). cd "$(mktemp -d)" && cp /home/claude/tmp/bug-1923/fixA/contention.replay .printf 'info resource license\nstep\ninfo resource license\nquit\n' | ASAN_OPTIONS=detect_leaks=0 TERM=dumb /home/rshen/projects/xce/scheduler-framework/src/xce.build/scheduler/xce_scheduler --sim contention.replay --interactive --logfile sched.log- Both
info resource licensecalls printvalue=(none), and jobs 102 and 103 PEND. Repeat withfixC(same result: the read-side skip alone is enough) andfixB(the first call printsvalue=(none)because the event is applied in round 1; afterstepit printsvalue=2.00 used=2.00and jobs 102 and 103 dispatch).
- How the replays are generated:
/home/claude/tmp/bug-1923/gen.cis a copy of the test'swrite_contention_replay(), with-DSNAPSHOT_LICENSE(A) or-DLICENSE_FIRST(C). The value is put in withzarray_put(&sched.core.sharedInst, lic->idx, val)beforesched_record_start().build_and_run.sh [N]rebuilds and reruns all three; it needs the scratch object copies, which were not kept.decode.cdumps a replay'sSnapshotEntryresvals: A showscount=0, C showsresval name=license host="" = 2.000000. - Frequency: deterministic (A 20/20 and C 20/20 show
value=(none); B 0/20; 2026-10-08) - Evidence:
/home/claude/tmp/bug-1923/(replays, generator, decoder, probe script)
Root cause
TODO: required for fixed/partial
Fix
TODO: required for fixed/partial
Verification
- Test: TODO: name + file; RED before the fix, GREEN after
- Suites: TODO: suite results and build profile
- Live: TODO: soak/deploy run, or n/a and why
References
- Related: none