From 8d6f610ef981080dae3346de804385a53b342e1d Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Tue, 25 Aug 2026 03:44:24 +0000 Subject: [PATCH] test(controller): cover live dispatch's apiserver notification (bug-1679) New test_dc_success_notifies_job_update_hook_exactly_once (test_dispatch_commit.c): a spy hook counting HOOK_AFTER_EVENT/ HOOK_DETAIL_JOB_UPDATE fires specifically -- the exact hook apiserver_job_update_fun subscribes to in production. Asserts exactly one fire per successful live dispatch. Independently reproduced RED->GREEN: disabled the new notify call with the test kept, rebuilt, confirmed FAIL (Expected 1 Was 0); restored, rebuilt, confirmed PASS. Also independently confirmed no double-WAL-append regression via two existing tests whose fixtures genuinely wire persist.cluster with persist_job_hook_fun registered: test_jobdispatch_receipt_ack_no_wal_append (asserts exactly one WAL entry for a successful dispatch) and test_handle_jobdispatch_matches_dispatch_a_job_notify_count -- both pass unchanged. Full controller_unit_test: 1137/1137 (2 pre-existing test_config_repl.c failures, confirmed identical on stock code via git-stash -- the known overlayfs fdatasync errno=5 flake, bug-1561). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg --- .../testsuite/test_dispatch_commit.c | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) 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); }