fix(scheduler): sim resource over-commit, byte-unit display, sync job-state-at-capture

- bug-1799/1801: fix resInUse over-commit in sim replay -- sched_account_jobs's
  per-round re-derive pass now skips RECONCILING/UNAVAIL workers the same way
  core_reset_workers already does (bug-1751), closing the gap that let a
  worker's used slots exceed its own capacity across a resync replay
- bug-1804: revise scheduler-sim-eager-reconciliation spec (user-directed) so
  a captured job's job->state is set to RUN synchronously at dispatch capture,
  mirroring sched_commit_decision's normal-mode semantics (bug-1624), instead
  of deferring until its separately-recorded JOB_STATE_CHANGE event replays
- bug-1800: render bytes-valued CONSUMABLE resources (mem/swp/tmp) with the
  same human-scaled unit formatting already used for maxmem/maxswap/maxtmp
- bug-1802/1803: add then revert a break-on-ANY-event breakpoint kind after
  live feedback that it over-fires inside busy sessions; replace it with a
  prescan-based "next non-dummy session" hint in info summary instead

Full scheduler suite: 77/77 tests pass (asan profile).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-12 14:06:41 +00:00
parent ffca959ed9
commit 25fb5c115a
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
22 changed files with 1827 additions and 303 deletions

View file

@ -1714,6 +1714,404 @@ test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource(
"bytes_resource: PASS\n");
}
/*
* User-reported (2026-09-11, live replay session): a CONSUMABLE byte-valued
* per-host resource (mem/tmp/swp) rendered its value=/used= pair as a raw
* `%.2f` byte count (e.g. "used=87960930222080.00"), unlike every other
* bytes-valued field in this same listing (maxmem/maxswap/maxtmp, and mem/
* tmp/swp themselves when NOT flagged consumable, via the Load Metrics
* path) which already render human-scaled. repl_info_worker_one()'s own
* pre-fix header comment explicitly scoped this out ("no bytes-valued
* CONSUMABLE resource is exercised anywhere in this codebase's tests
* today") -- this test (and its "raw"/swp/info-resource siblings below)
* closes that gap. A non-bytes-valued consumable (slots/licenses) must keep
* its existing pinned "%.2f"/"%.2f" shape unchanged -- see
* test_info_worker_reports_consumable_value_and_used (bug-1727,
* test_repl_breakpoints_inspection.c), left untouched by this fix.
*/
static void
test_info_worker_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource(
void)
{
xce_sched_sim_driver_t sim;
xce_core_event_t events[1];
xce_resource_t wire_resource;
xce_resource_t* r;
xce_worker_t wire_worker;
xce_worker_t* worker_ptrs[1];
xce_val_t val;
xce_name_val_t nv;
xce_name_val_t* nv_ptrs[1];
xce_val_t used_val;
xce_sched_worker_t* sw;
char* captured;
char expected_value[64];
char expected_used[64];
char expected_line[160];
double avail;
double used;
int rc;
avail = 8589934592.0; /* 8 GB */
used = 4294967296.0; /* 4 GB */
memset(&sim, 0, sizeof(sim));
rc = xce_sched_core_init(&(sim.core));
TEST_ASSERT_EQUAL_INT(0, rc);
memset(&wire_resource, 0, sizeof(wire_resource));
wire_resource.name = "mem";
wire_resource.valtype = XCE_VAL_NUMERIC;
wire_resource.flags = XCE_RESF_CONSUMABLE;
r = makeResource(sim.core.resmgr, &wire_resource);
TEST_ASSERT_NOT_NULL(r);
memset(&val, 0, sizeof(val));
val.which = Value_u_fvalue;
val.u.fvalue = avail;
memset(&nv, 0, sizeof(nv));
nv.name = "mem";
nv.val = &val;
nv_ptrs[0] = &nv;
memset(&wire_worker, 0, sizeof(wire_worker));
wire_worker.name = "hostA";
wire_worker.status = 0;
wire_worker.nresvals_to_sched = 1;
wire_worker.resvals_to_sched = nv_ptrs;
worker_ptrs[0] = &wire_worker;
memset(events, 0, sizeof(events));
events[0].type = XCE_CORE_EVENT_WORKERS_PUSH;
events[0].u.workers_push.nworkers = 1;
events[0].u.workers_push.workers = worker_ptrs;
rc = xce_core_push_events(&(sim.core), events, 1);
TEST_ASSERT_EQUAL_INT(0, rc);
sw = sched_get_worker(&(sim.core), "hostA");
TEST_ASSERT_NOT_NULL(sw);
memset(&used_val, 0, sizeof(used_val));
used_val.which = Value_u_fvalue;
used_val.u.fvalue = used;
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
sim.repl_state = repl_state_ensure(&sim);
TEST_ASSERT_NOT_NULL(sim.repl_state);
sim.repl_state->units_mode = XCE_REPL_UNITS_BOTH;
captured = capture_dispatch(&sim, "info worker hostA");
repl_format_bytes_human(avail, expected_value, sizeof(expected_value));
repl_format_bytes_human(used, expected_used, sizeof(expected_used));
snprintf(expected_line, sizeof(expected_line), "value=%f (%s) used=%f (%s)",
avail, expected_value, used, expected_used);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, expected_line),
"a CONSUMABLE byte-valued resource (mem/tmp/swp) must render both "
"value= and used= human-scaled under units_mode=both, the same "
"as every other bytes-valued field in this listing");
free(captured);
printf(
"test_info_worker_units_mode_both_renders_raw_and_human_for_"
"consumable_bytes_resource: PASS\n");
}
/*
* AC 6.2 'raw' counterpart for a CONSUMABLE byte-valued resource: under
* units_mode=raw, value=/used= must show ONLY the bare wire numbers, no
* "GB"/human suffix -- mirrors
* test_info_worker_units_mode_raw_renders_bare_wire_value_without_mutating_stored_value's
* contract, applied to the consumable value=/used= pair instead of a
* single value= line.
*/
static void
test_info_worker_units_mode_raw_renders_bare_values_for_consumable_bytes_resource(
void)
{
xce_sched_sim_driver_t sim;
xce_core_event_t events[1];
xce_resource_t wire_resource;
xce_resource_t* r;
xce_worker_t wire_worker;
xce_worker_t* worker_ptrs[1];
xce_val_t val;
xce_name_val_t nv;
xce_name_val_t* nv_ptrs[1];
xce_val_t used_val;
xce_sched_worker_t* sw;
char* captured;
char expected_line[160];
double avail;
double used;
int rc;
avail = 8589934592.0;
used = 4294967296.0;
memset(&sim, 0, sizeof(sim));
rc = xce_sched_core_init(&(sim.core));
TEST_ASSERT_EQUAL_INT(0, rc);
memset(&wire_resource, 0, sizeof(wire_resource));
wire_resource.name = "mem";
wire_resource.valtype = XCE_VAL_NUMERIC;
wire_resource.flags = XCE_RESF_CONSUMABLE;
r = makeResource(sim.core.resmgr, &wire_resource);
TEST_ASSERT_NOT_NULL(r);
memset(&val, 0, sizeof(val));
val.which = Value_u_fvalue;
val.u.fvalue = avail;
memset(&nv, 0, sizeof(nv));
nv.name = "mem";
nv.val = &val;
nv_ptrs[0] = &nv;
memset(&wire_worker, 0, sizeof(wire_worker));
wire_worker.name = "hostA";
wire_worker.status = 0;
wire_worker.nresvals_to_sched = 1;
wire_worker.resvals_to_sched = nv_ptrs;
worker_ptrs[0] = &wire_worker;
memset(events, 0, sizeof(events));
events[0].type = XCE_CORE_EVENT_WORKERS_PUSH;
events[0].u.workers_push.nworkers = 1;
events[0].u.workers_push.workers = worker_ptrs;
rc = xce_core_push_events(&(sim.core), events, 1);
TEST_ASSERT_EQUAL_INT(0, rc);
sw = sched_get_worker(&(sim.core), "hostA");
TEST_ASSERT_NOT_NULL(sw);
memset(&used_val, 0, sizeof(used_val));
used_val.which = Value_u_fvalue;
used_val.u.fvalue = used;
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
sim.repl_state = repl_state_ensure(&sim);
TEST_ASSERT_NOT_NULL(sim.repl_state);
sim.repl_state->units_mode = XCE_REPL_UNITS_RAW;
captured = capture_dispatch(&sim, "info worker hostA");
snprintf(expected_line, sizeof(expected_line), "value=%f used=%f", avail,
used);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, expected_line),
"AC 6.2 'raw': a CONSUMABLE byte-valued resource must render bare "
"wire values for both value= and used= under units_mode=raw");
TEST_ASSERT_NULL_MESSAGE(
strstr(captured, "GB"),
"AC 6.2 'raw': must show no human-readable unit suffix");
free(captured);
printf(
"test_info_worker_units_mode_raw_renders_bare_values_for_"
"consumable_bytes_resource: PASS\n");
}
/*
* AC 6.1's swp-is-megabytes special case, applied to the CONSUMABLE
* value=/used= pair: both fields must be converted MB->bytes before
* human-scaling, mirroring
* test_info_worker_units_mode_converts_swp_megabytes_before_human_scaling's
* contract for the Load Metrics (non-consumable) path.
*/
static void
test_info_worker_units_mode_converts_swp_megabytes_for_consumable_resource(
void)
{
xce_sched_sim_driver_t sim;
xce_core_event_t events[1];
xce_resource_t wire_resource;
xce_resource_t* r;
xce_worker_t wire_worker;
xce_worker_t* worker_ptrs[1];
xce_val_t val;
xce_name_val_t nv;
xce_name_val_t* nv_ptrs[1];
xce_val_t used_val;
xce_sched_worker_t* sw;
char* captured;
char expected_value_human[64];
char expected_used_human[64];
double avail_mb;
double used_mb;
int rc;
avail_mb = 16384.0; /* 16384 MB -> 16 GB */
used_mb = 4096.0; /* 4096 MB -> 4 GB */
memset(&sim, 0, sizeof(sim));
rc = xce_sched_core_init(&(sim.core));
TEST_ASSERT_EQUAL_INT(0, rc);
memset(&wire_resource, 0, sizeof(wire_resource));
wire_resource.name = "swp";
wire_resource.valtype = XCE_VAL_NUMERIC;
wire_resource.flags = XCE_RESF_CONSUMABLE;
r = makeResource(sim.core.resmgr, &wire_resource);
TEST_ASSERT_NOT_NULL(r);
memset(&val, 0, sizeof(val));
val.which = Value_u_fvalue;
val.u.fvalue = avail_mb;
memset(&nv, 0, sizeof(nv));
nv.name = "swp";
nv.val = &val;
nv_ptrs[0] = &nv;
memset(&wire_worker, 0, sizeof(wire_worker));
wire_worker.name = "hostA";
wire_worker.status = 0;
wire_worker.nresvals_to_sched = 1;
wire_worker.resvals_to_sched = nv_ptrs;
worker_ptrs[0] = &wire_worker;
memset(events, 0, sizeof(events));
events[0].type = XCE_CORE_EVENT_WORKERS_PUSH;
events[0].u.workers_push.nworkers = 1;
events[0].u.workers_push.workers = worker_ptrs;
rc = xce_core_push_events(&(sim.core), events, 1);
TEST_ASSERT_EQUAL_INT(0, rc);
sw = sched_get_worker(&(sim.core), "hostA");
TEST_ASSERT_NOT_NULL(sw);
memset(&used_val, 0, sizeof(used_val));
used_val.which = Value_u_fvalue;
used_val.u.fvalue = used_mb;
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
sim.repl_state = repl_state_ensure(&sim);
TEST_ASSERT_NOT_NULL(sim.repl_state);
sim.repl_state->units_mode = XCE_REPL_UNITS_HUMAN;
captured = capture_dispatch(&sim, "info worker hostA");
repl_format_bytes_human(avail_mb * 1024.0 * 1024.0, expected_value_human,
sizeof(expected_value_human));
repl_format_bytes_human(used_mb * 1024.0 * 1024.0, expected_used_human,
sizeof(expected_used_human));
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, expected_value_human),
"swp's value= must be converted MB->bytes before human-scaling, "
"same as the Load Metrics path");
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, expected_used_human),
"swp's used= must be converted MB->bytes before human-scaling too");
free(captured);
printf(
"test_info_worker_units_mode_converts_swp_megabytes_for_"
"consumable_resource: PASS\n");
}
/*
* info resource's counterpart: a GLOBAL/SHARED CONSUMABLE byte-valued
* resource must render its value=/used= pair human-scaled too, mirroring
* test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource
* (the non-consumable case) and this file's own info-worker tests above.
*/
static void
test_info_resource_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource(
void)
{
xce_sched_sim_driver_t sim;
xce_core_event_t events[1];
xce_resource_t wire_mem;
xce_resource_t* r;
xce_val_t val_mem;
xce_resval_t rv_mem;
xce_resval_t* rv_ptrs[1];
xce_val_t* stored;
xce_val_t used_val;
char* captured;
char expected_value[64];
char expected_used[64];
char expected_line[160];
double avail;
double used;
int rc;
avail = 8589934592.0; /* 8 GB */
used = 2147483648.0; /* 2 GB */
memset(&sim, 0, sizeof(sim));
rc = xce_sched_core_init(&(sim.core));
TEST_ASSERT_EQUAL_INT(0, rc);
memset(&wire_mem, 0, sizeof(wire_mem));
wire_mem.name = "mem";
wire_mem.valtype = XCE_VAL_NUMERIC;
wire_mem.flags = XCE_RESF_SHARED | XCE_RESF_CONSUMABLE;
r = makeResource(sim.core.resmgr, &wire_mem);
TEST_ASSERT_NOT_NULL(r);
memset(&val_mem, 0, sizeof(val_mem));
val_mem.which = Value_u_fvalue;
val_mem.u.fvalue = avail;
memset(&rv_mem, 0, sizeof(rv_mem));
rv_mem.name = "mem";
rv_mem.val = &val_mem;
rv_ptrs[0] = &rv_mem;
memset(events, 0, sizeof(events));
events[0].type = XCE_CORE_EVENT_RESVALS_FULL;
events[0].u.resvals_full.n = 1;
events[0].u.resvals_full.resvals = rv_ptrs;
rc = xce_core_push_events(&(sim.core), events, 1);
TEST_ASSERT_EQUAL_INT(0, rc);
memset(&used_val, 0, sizeof(used_val));
used_val.which = Value_u_fvalue;
used_val.u.fvalue = used;
zarray_put(&(sim.core.sharedInUse), r->idx, dup_val(&used_val));
sim.repl_state = repl_state_ensure(&sim);
TEST_ASSERT_NOT_NULL(sim.repl_state);
sim.repl_state->units_mode = XCE_REPL_UNITS_BOTH;
captured = capture_dispatch(&sim, "info resource mem");
repl_format_bytes_human(avail, expected_value, sizeof(expected_value));
repl_format_bytes_human(used, expected_used, sizeof(expected_used));
snprintf(expected_line, sizeof(expected_line), "value=%f (%s) used=%f (%s)",
avail, expected_value, used, expected_used);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, expected_line),
"a GLOBAL/SHARED CONSUMABLE byte-valued resource must render both "
"value= and used= human-scaled under units_mode=both");
stored = (xce_val_t*)zarray_get(&(sim.core.sharedInst), r->idx);
TEST_ASSERT_NOT_NULL(stored);
TEST_ASSERT_EQUAL_INT(Value_u_fvalue, stored->which);
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
(float)avail, (float)stored->u.fvalue,
"AC 6.4: displaying a resource under any units_mode must never "
"mutate the underlying stored/wire value");
free(captured);
printf(
"test_info_resource_units_mode_both_renders_raw_and_human_for_"
"consumable_bytes_resource: PASS\n");
}
int main(void)
{
UNITY_BEGIN();
@ -1724,6 +2122,9 @@ int main(void)
RUN_TEST(test_info_worker_units_mode_human_renders_scaled_value_without_mutating_stored_value);
RUN_TEST(test_info_worker_units_mode_both_renders_raw_and_human_without_mutating_stored_value);
RUN_TEST(test_info_worker_units_mode_converts_swp_megabytes_before_human_scaling);
RUN_TEST(test_info_worker_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource);
RUN_TEST(test_info_worker_units_mode_raw_renders_bare_values_for_consumable_bytes_resource);
RUN_TEST(test_info_worker_units_mode_converts_swp_megabytes_for_consumable_resource);
RUN_TEST(test_info_worker_assigned_job_listing_compresses_contiguous_array_indices);
RUN_TEST(test_info_worker_assigned_job_listing_appends_slot_count_when_uniform);
RUN_TEST(test_info_worker_assigned_job_listing_excludes_terminal_job_despite_stale_allocs);
@ -1731,6 +2132,7 @@ int main(void)
RUN_TEST(test_info_resource_groups_resources_into_labeled_table_sections_in_order);
RUN_TEST(test_info_resource_load_metric_whole_number_omits_trailing_zeros);
RUN_TEST(test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource);
RUN_TEST(test_info_resource_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource);
return UNITY_END();
}