diff --git a/src/controller/testsuite/test_dispatch_commit.c b/src/controller/testsuite/test_dispatch_commit.c index 7726a640..c60dc691 100644 --- a/src/controller/testsuite/test_dispatch_commit.c +++ b/src/controller/testsuite/test_dispatch_commit.c @@ -274,6 +274,71 @@ static void test_dc_success_commits_alloc_state_and_resets_counter(void) printf("test_dc_success_commits_alloc_state_and_resets_counter: PASS\n"); } +/* bug-1679: spy hook counting HOOK_AFTER_EVENT/HOOK_DETAIL_JOB_UPDATE fires + * specifically -- the exact hook apiserver_job_update_fun (apiserver.c) + * subscribes to in production to push a job's current state to every + * connected apiserver. Mirrors test_apply_committed_entry.c's tace_spy_hook + * pattern, filtered to this one detail rather than every AFTER_EVENT. */ +static int g_dc_update_hook_fires; + +static int dc_job_update_spy_hook(int type, int situation, int detail, + void* data, va_list ap) +{ + (void)type; (void)data; (void)ap; + if (situation == HOOK_AFTER_EVENT && detail == HOOK_DETAIL_JOB_UPDATE) + g_dc_update_hook_fires++; + return 0; +} + +#define DC_JOBID_NOTIFY 91004ULL + +/* THE core proof for bug-1679 (Req: a live successful dispatch must notify + * the apiserver, restart or not): dispatch_a_job_()'s success path fires + * HOOK_DETAIL_JOB_UPDATE's AFTER_EVENT exactly once, synchronously, in the + * SAME operation that commits state+allocs -- unlike before this fix, where + * dispatch_a_job_() fired no hooks at all and relied entirely on + * apply_committed_entry()'s later (here, always-suppressed-by-duplicate- + * state) notify. RED before the fix: g_dc_update_hook_fires stays 0. */ +static void test_dc_success_notifies_job_update_hook_exactly_once(void) +{ + job_t* j; + xce_job_alloc_t* alloc; + int rc; + + dc_setup(); + g_dc_update_hook_fires = 0; + TEST_ASSERT_EQUAL_INT(0, + xce_add_hook(&g_dc.hooksys, XCE_CONTROLLER_HOOK_JOB, + dc_job_update_spy_hook, NULL)); + + j = dc_seed_ready(DC_JOBID_NOTIFY, 0, "dq", "wkr-dc-notify"); + + alloc = make_job_alloc(DC_JOBID_NOTIFY, 1, + make_host_allocs(1, + make_host_alloc((char*)"wkr-dc-notify", 1, + make_allocs(1, + make_alloc((char*)"slots", NULL, 0, 1.0f, 0))))); + TEST_ASSERT_NOT_NULL(alloc); + + rc = dispatch_a_job_test(&g_dc, NULL, alloc); + TEST_ASSERT_TRUE(rc >= 0); + TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, j->job.state); + + TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_dc_update_hook_fires, + "a live successful dispatch must fire HOOK_DETAIL_JOB_UPDATE's " + "AFTER_EVENT exactly once (bug-1679) -- apiserver_job_update_fun's " + "subscription is the ONLY mechanism that pushes the job's new " + "RUN state and allocation to the apiserver; without this fire the " + "job is stuck at PEND from the apiserver's perspective forever"); + + free_JobAlloc_ptr(&alloc); + dc_drain_ep(&g_dc_worker_ep); + xce_disable_hook(&g_dc.hooksys, XCE_CONTROLLER_HOOK_JOB, + dc_job_update_spy_hook); + dc_teardown(); + printf("test_dc_success_notifies_job_update_hook_exactly_once: PASS\n"); +} + #define DC_JOBID_NOTFOUND 91002ULL /* Regression safety net (untouched failure branch): a dispatch whose @@ -666,6 +731,7 @@ void test_dispatch_commit(void) "5.1)\n\n"); RUN_TEST(test_dc_success_commits_alloc_state_and_resets_counter); + RUN_TEST(test_dc_success_notifies_job_update_hook_exactly_once); RUN_TEST(test_dc_worker_not_found_leaves_job_pend_and_alloc_uncommitted); RUN_TEST(test_dc_success_appends_wal_job_dispatch_durably); }