fix(controller): mark RUN jobs unknown on worker loss and restart (bug-1509 P2)
Phase 2 of the job-finish robustness design (Part B, B1+B3). B1 (worker liveness): a worker that stops reporting (heartbeat timeout, already detected by check_worker_timeout_) or disconnects now has its RUN jobs marked JOB_STAT_UNKWN|JOB_STAT_TRANSIENT (transient_since set, durable WAL_JOB_UPDATE) instead of lingering as RUN forever. Marking happens AFTER the worker lock is released (B5: avoids worker->jobmgr inversion with the scheduler's jobmgr->worker order). B3 (restart): persist_recover marks all recovered RUN jobs UNKWN+TRANSIENT in-memory after replay — a controller restart leaves worker connectivity unknown. In-memory only (no WAL append) because persist_recover may run on a future standby that must never append. Unit tests (red->green): worker-loss marks its RUN jobs unknown, other-host jobs untouched, restart marks all RUN jobs. test_replay_apply_seam updated: replay still drives facts to RUN, then B3 adds UNKWN+TRANSIENT.
This commit is contained in:
parent
a793015f74
commit
fc091348e8
7 changed files with 224 additions and 4 deletions
|
|
@ -1736,11 +1736,110 @@ static void test_unknown_finish_drain_noop_without_registration(void)
|
|||
printf("test_unknown_finish_drain_noop_without_registration: PASS\n");
|
||||
}
|
||||
|
||||
/* ── 6. bug-1509 Phase 2: B1 (worker loss marks its RUN jobs UNKWN+TRANSIENT)
|
||||
* ── and B3 (restart marks ALL RUN jobs UNKWN+TRANSIENT). */
|
||||
|
||||
static void tap_set_job_allocs(job_t* j, const char* host)
|
||||
{
|
||||
xce_host_alloc_t* ha = calloc(1, sizeof(*ha));
|
||||
TEST_ASSERT_NOT_NULL(ha);
|
||||
ha->host = strdup(host);
|
||||
|
||||
xce_job_alloc_t* ja = calloc(1, sizeof(*ja));
|
||||
TEST_ASSERT_NOT_NULL(ja);
|
||||
ja->jobid = j->job.jobid;
|
||||
ja->nhosts = 1;
|
||||
ja->allocs = calloc(1, sizeof(xce_host_alloc_t*));
|
||||
TEST_ASSERT_NOT_NULL(ja->allocs);
|
||||
ja->allocs[0] = ha;
|
||||
j->job.allocs = ja;
|
||||
}
|
||||
|
||||
static void test_worker_loss_marks_its_run_jobs_unknown(void)
|
||||
{
|
||||
tap_setup();
|
||||
|
||||
job_t* j = tap_seed_job(1001, JOB_STAT_RUN);
|
||||
TEST_ASSERT_NOT_NULL(j);
|
||||
lock(j, RTTI_LOCK_WRITE);
|
||||
tap_set_job_allocs(j, "t1");
|
||||
unlock(j);
|
||||
|
||||
mark_worker_jobs_unknown(&g_tap_c, "t1");
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(j->job.state & JOB_STAT_UNKWN,
|
||||
"job must be UNKWN after worker loss");
|
||||
TEST_ASSERT_TRUE_MESSAGE(j->job.state & JOB_STAT_TRANSIENT,
|
||||
"job must be TRANSIENT after worker loss");
|
||||
TEST_ASSERT_TRUE_MESSAGE(j->job.state & JOB_STAT_RUN,
|
||||
"RUN bit must be preserved");
|
||||
TEST_ASSERT_TRUE_MESSAGE(j->job.transient_since > 0,
|
||||
"transient_since must be set");
|
||||
|
||||
tap_teardown();
|
||||
printf("test_worker_loss_marks_its_run_jobs_unknown: PASS\n");
|
||||
}
|
||||
|
||||
static void test_worker_loss_does_not_touch_other_host_jobs(void)
|
||||
{
|
||||
tap_setup();
|
||||
|
||||
job_t* j_t1 = tap_seed_job(1002, JOB_STAT_RUN);
|
||||
job_t* j_t2 = tap_seed_job(1003, JOB_STAT_RUN);
|
||||
TEST_ASSERT_NOT_NULL(j_t1);
|
||||
TEST_ASSERT_NOT_NULL(j_t2);
|
||||
lock(j_t1, RTTI_LOCK_WRITE);
|
||||
tap_set_job_allocs(j_t1, "t1");
|
||||
unlock(j_t1);
|
||||
lock(j_t2, RTTI_LOCK_WRITE);
|
||||
tap_set_job_allocs(j_t2, "t2");
|
||||
unlock(j_t2);
|
||||
|
||||
mark_worker_jobs_unknown(&g_tap_c, "t1");
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(j_t1->job.state & JOB_STAT_UNKWN,
|
||||
"t1 job must be marked");
|
||||
TEST_ASSERT_FALSE_MESSAGE(j_t2->job.state & JOB_STAT_UNKWN,
|
||||
"t2 job must be untouched");
|
||||
|
||||
tap_teardown();
|
||||
printf("test_worker_loss_does_not_touch_other_host_jobs: PASS\n");
|
||||
}
|
||||
|
||||
static void test_restart_marks_all_run_jobs_unknown(void)
|
||||
{
|
||||
tap_setup();
|
||||
|
||||
job_t* j_run1 = tap_seed_job(1004, JOB_STAT_RUN);
|
||||
job_t* j_run2 = tap_seed_job(1005, JOB_STAT_RUN);
|
||||
job_t* j_done = tap_seed_job(1006, JOB_STAT_DONE);
|
||||
TEST_ASSERT_NOT_NULL(j_run1);
|
||||
TEST_ASSERT_NOT_NULL(j_run2);
|
||||
TEST_ASSERT_NOT_NULL(j_done);
|
||||
|
||||
mark_all_run_jobs_unknown_in_memory(&g_tap_c);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(j_run1->job.state & JOB_STAT_UNKWN,
|
||||
"run job 1 must be marked");
|
||||
TEST_ASSERT_TRUE_MESSAGE(j_run2->job.state & JOB_STAT_UNKWN,
|
||||
"run job 2 must be marked");
|
||||
TEST_ASSERT_FALSE_MESSAGE(j_done->job.state & JOB_STAT_UNKWN,
|
||||
"done job must be untouched");
|
||||
TEST_ASSERT_TRUE_MESSAGE(j_run1->job.transient_since > 0,
|
||||
"transient_since must be set on run job");
|
||||
|
||||
tap_teardown();
|
||||
printf("test_restart_marks_all_run_jobs_unknown: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Test group entry point ───────────────────────────────────────────── */
|
||||
|
||||
void test_apply_pump(void)
|
||||
{
|
||||
UnitySetTestFile(__FILE__);
|
||||
RUN_TEST(test_worker_loss_marks_its_run_jobs_unknown);
|
||||
RUN_TEST(test_worker_loss_does_not_touch_other_host_jobs);
|
||||
RUN_TEST(test_restart_marks_all_run_jobs_unknown);
|
||||
RUN_TEST(test_unknown_finish_registry_register_and_take);
|
||||
RUN_TEST(test_unknown_finish_applied_once_job_materializes);
|
||||
RUN_TEST(test_unknown_finish_exit_code_maps_to_exit);
|
||||
|
|
|
|||
|
|
@ -361,9 +361,15 @@ static void test_replay_applies_every_classified_type_via_apply_seam(void)
|
|||
TEST_ASSERT_NOT_NULL_MESSAGE(j,
|
||||
"replay must construct the job via the apply seam's "
|
||||
"WAL_JOB_SUBMIT route");
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_RUN, j->job.state,
|
||||
/* bug-1509 B3: after recovery, RUN jobs' state is uncertain (worker
|
||||
* connectivity unknown), so persist_recover marks them UNKWN+TRANSIENT
|
||||
* in-memory. The replay itself must still drive the fact to RUN; the
|
||||
* UNKWN/TRANSIENT bits come from B3's post-replay marking. */
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_RUN | JOB_STAT_UNKWN
|
||||
| JOB_STAT_TRANSIENT,
|
||||
j->job.state,
|
||||
"replay must apply the job-state fact via the apply seam's "
|
||||
"guarded transition core, reaching the same end state as before");
|
||||
"guarded transition core, then B3 marks the RUN job UNKWN");
|
||||
|
||||
queue_t* q = trr_find_queue("rq1");
|
||||
TEST_ASSERT_NOT_NULL(q);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue