fix(scheduler): sim mode picker never learns about the initial snapshot's own jobs/queues (bug-1567)
xce_scheduler -i -s <replay>: WARN "failed to resolve job picker plugin (rc=-2)" on load, and every job already present in the replay file's INITIAL snapshot stayed PEND forever -- never even reached the picker, let alone got dispatched. Root cause: sched_sim_driver_start() (sim.c) loads the initial snapshot via xce_sched_core_load_snapshot() BEFORE any picker plugin is registered (sim.h's own documented "Known scope boundary" -- only the picker's NAME is threaded through from the ReplayHeader). sched_resolve_job_picker() therefore always returns -ENOENT there, and xce_sched_core_load_snapshot()'s own picker-rebuild walk (on_queue_update/on_job_push per queue/job) is silently skipped. The real builtin_fcfs picker only gets registered moments later in sched_sim_driver_run() -- but nothing then replayed that rebuild walk, so any job/queue from the INITIAL snapshot (as opposed to one arriving later via a ReplayEvent, which correctly reaches the picker through xce_core_push_events()'s own picker!=NULL check) was permanently invisible to next_job(). Fixed by a new file, sim_picker.c: sim_bootstrap_picker() (replacing sim_loop.c's sim_register_default_picker(), moved out because sim_loop.c was already at its own documented 800-line "zero line-budget headroom" cap) registers the picker AND then replays the missed rebuild walk against it. Wired into scheduler_srcs, sched_sim_isolation_lib, and all 9 white-box test TUs that #include sim_loop.c directly. New regression test (test_sim_event_loop.c): a job baked into a fixture's INITIAL snapshot (not a ReplayEvent) now gets a live PEND decision. RED confirmed by temporarily no-op'ing the rebuild walk (test failed as expected), GREEN with the real fix. Full scheduler suite green (41/41, excluding the pre-existing unrelated bug-1560). Note: sim mode still never DISPATCHes (only PENDs) -- that is a separate, intentionally out-of-scope gap (real resource-matching allocation belongs to the not-yet-designed scheduler-allocation spec), not addressed here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0f1df4998e
commit
0c8e417bda
14 changed files with 395 additions and 93 deletions
|
|
@ -82,6 +82,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
|
|
@ -107,6 +108,7 @@
|
|||
#include "../src/repl_params.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() now calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
#include "../src/sim_record.c"
|
||||
#include "../src/sim_verify.c"
|
||||
#include "../src/snapshot.c"
|
||||
|
|
@ -551,6 +553,179 @@ test_run_processes_multiple_sessions_in_order_and_no_longer_returns_enosys(void)
|
|||
"returns_enosys: PASS\n");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test 3 (bug-1567 regression): a job/queue already present in the */
|
||||
/* INITIAL snapshot -- as opposed to arriving later via a ReplayEvent -- */
|
||||
/* must still reach the real picker and get a live decision. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* Same fixture technique as write_header_and_snapshot() above, but (a)
|
||||
* records schedPolicy="builtin_fcfs" (test_sim_verify.c's own
|
||||
* build_fixture_scheduler() precedent -- this file's own copy deliberately
|
||||
* uses a non-matching stub name instead, so it cannot be reused here
|
||||
* unmodified) so sim_bootstrap_picker() (sim_picker.c) registers the REAL
|
||||
* builtin_fcfs plugin, not a no-op stub, and (b) adds one real queue and
|
||||
* one real PEND job directly to core->queues/core->jobs (core_build_queue()/
|
||||
* core_build_job() + sched_add_queue()/sched_add_job(), core_events.c,
|
||||
* already white-box #included above) BEFORE calling sched_record_start() --
|
||||
* so xce_sched_core_save_snapshot() (snapshot.c) captures both into the
|
||||
* INITIAL snapshot itself, not as a later ReplayEvent. */
|
||||
static FILE* write_header_and_snapshot_with_pending_job(char* out_path,
|
||||
size_t out_path_len,
|
||||
const char* suffix,
|
||||
const char* queue_name,
|
||||
uint64_t jobid)
|
||||
{
|
||||
char record_dir[PATH_MAX];
|
||||
xce_scheduler_t sched;
|
||||
xce_queue_t wire_queue;
|
||||
xce_sched_queue_t* q;
|
||||
job_submit_data_t submit;
|
||||
xce_job_t wire_job;
|
||||
xce_sched_job_t* job;
|
||||
int rc;
|
||||
|
||||
make_tmp_dir(record_dir, sizeof(record_dir), suffix);
|
||||
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
mylock_init(&(sched.record_lock));
|
||||
rc = xce_sched_core_init(&(sched.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
sched.core.job_pick_plugin = (char*)"builtin_fcfs";
|
||||
sched.record_dir = record_dir;
|
||||
|
||||
memset(&wire_queue, 0, sizeof(wire_queue));
|
||||
wire_queue.queue = (char*)queue_name;
|
||||
wire_queue.status = QUEUE_STAT_OPEN;
|
||||
wire_queue.priority = 1;
|
||||
q = core_build_queue(&wire_queue);
|
||||
TEST_ASSERT_NOT_NULL(q);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, sched_add_queue(&(sched.core), q));
|
||||
|
||||
memset(&submit, 0, sizeof(submit));
|
||||
submit.queue = (char*)queue_name;
|
||||
submit.user = (char*)"alice";
|
||||
submit.mintasks = 1;
|
||||
submit.maxtasks = 1;
|
||||
|
||||
memset(&wire_job, 0, sizeof(wire_job));
|
||||
wire_job.jobid = jobid;
|
||||
wire_job.state = JOB_STAT_PEND;
|
||||
wire_job.submit = &submit;
|
||||
job = core_build_job(&(sched.core), &wire_job);
|
||||
TEST_ASSERT_NOT_NULL(job);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, sched_add_job(&(sched.core), job));
|
||||
|
||||
rc = sched_record_start(&sched, "fixture.replay");
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_TRUE(strlen(sched.record_file_path) < out_path_len);
|
||||
strcpy(out_path, sched.record_file_path);
|
||||
|
||||
return sched.record_file;
|
||||
}
|
||||
|
||||
/* Same dup()/dup2() STDOUT_FILENO capture technique test_sim_verify.c's own
|
||||
* run_sim_and_capture_stdout() establishes (this file's white-box TU does
|
||||
* not #include that file, so a plain copy is needed here rather than a
|
||||
* cross-file static call). */
|
||||
static char* run_sim_and_capture_stdout(const char* path, int* out_rc)
|
||||
{
|
||||
sched_sim_args_t args;
|
||||
xce_sched_sim_driver_t sim;
|
||||
char tmp[] = "/tmp/xce_simloop_run_XXXXXX";
|
||||
int fd = mkstemp(tmp);
|
||||
int saved_stdout;
|
||||
off_t len;
|
||||
char* buf;
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp for stdout capture must "
|
||||
"succeed");
|
||||
|
||||
fflush(stdout);
|
||||
saved_stdout = dup(STDOUT_FILENO);
|
||||
TEST_ASSERT_TRUE_MESSAGE(saved_stdout >= 0, "dup(STDOUT_FILENO) must "
|
||||
"succeed");
|
||||
TEST_ASSERT_TRUE_MESSAGE(dup2(fd, STDOUT_FILENO) >= 0,
|
||||
"dup2 redirect of STDOUT_FILENO must succeed");
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.replay_path = path;
|
||||
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
|
||||
args.sim_speed = 0; /* frozen -- deterministic, no wall-clock wait */
|
||||
|
||||
*out_rc = sched_sim_driver_run(&sim, &args);
|
||||
|
||||
fflush(stdout);
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
|
||||
len = lseek(fd, 0, SEEK_CUR);
|
||||
TEST_ASSERT_TRUE(len >= 0);
|
||||
|
||||
buf = (char*)calloc(1, (size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
TEST_ASSERT_TRUE(lseek(fd, 0, SEEK_SET) == 0);
|
||||
if (len > 0) {
|
||||
ssize_t nread = read(fd, buf, (size_t)len);
|
||||
TEST_ASSERT_TRUE(nread == len);
|
||||
}
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* bug-1567: before this fix, sched_sim_driver_start() (sim.c) loaded the
|
||||
* initial snapshot while no picker plugin was registered yet (sim.h's own
|
||||
* "Known scope boundary"), so xce_sched_core_load_snapshot()'s
|
||||
* picker-rebuild walk (on_queue_update/on_job_push) was skipped for the
|
||||
* job/queue this fixture bakes into the INITIAL snapshot -- and nothing
|
||||
* ever replayed that walk once sim_bootstrap_picker() registered the real
|
||||
* picker moments later. The job was therefore invisible to next_job()
|
||||
* forever: no PEND, no DISPATCH, nothing -- exactly the user report this
|
||||
* bug tracks ("no job will be scheduled and all jobs in pend state").
|
||||
* After the fix, sim_bootstrap_picker() (sim_picker.c) replays that walk
|
||||
* itself, so this job -- despite never appearing in a single ReplayEvent
|
||||
* -- still reaches the picker and gets a live PEND decision (this driver
|
||||
* never DISPATCHes at all, a separate, intentionally out-of-scope gap --
|
||||
* see sim_picker_thread_fn()'s own doc comment, sim_loop.c).
|
||||
*/
|
||||
static void
|
||||
test_job_present_only_in_initial_snapshot_still_reaches_the_picker(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
FILE* f;
|
||||
int rc;
|
||||
char* captured;
|
||||
|
||||
f = write_header_and_snapshot_with_pending_job(path, sizeof(path),
|
||||
"_initsnap", "normal",
|
||||
4242ULL);
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
|
||||
/* One session boundary is enough to trigger one scheduling round --
|
||||
* the job under test arrives via the INITIAL SNAPSHOT, not via any
|
||||
* ReplayEvent appended here (deliberately: zero jobs/queues events in
|
||||
* this stream is the whole point of this regression). */
|
||||
append_replay_session(f, 1000ULL);
|
||||
append_end_of_stream_marker(f);
|
||||
fclose(f);
|
||||
|
||||
captured = run_sim_and_capture_stdout(path, &rc);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "PEND jobid=4242"),
|
||||
"a job present only in the replay file's INITIAL snapshot must "
|
||||
"still reach the real picker and receive a live PEND decision "
|
||||
"(bug-1567)");
|
||||
|
||||
free(captured);
|
||||
printf("test_job_present_only_in_initial_snapshot_still_reaches_the_"
|
||||
"picker: PASS\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
|
@ -559,6 +734,8 @@ int main(void)
|
|||
test_process_replay_event_advances_virtual_clock_to_exact_event_ts_in_file_order);
|
||||
RUN_TEST(
|
||||
test_run_processes_multiple_sessions_in_order_and_no_longer_returns_enosys);
|
||||
RUN_TEST(
|
||||
test_job_present_only_in_initial_snapshot_still_reaches_the_picker);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue