feat(scheduler-dispatch-commit): handle_jobdispatch no longer duplicates dispatch-outcome reporting (task 4.1)
dispatch_a_job_() (tasks 3.1-3.3) now owns notification entirely for every outcome -- notify_dispatch() on a retryable failure, mark_job_fail()'s own hook chain on a terminal failure, nothing on success. handle_jobdispatch()'s loop still unconditionally called notify_dispatch() itself for every job regardless, producing two conflicting outcome reports for any failed dispatch. Removed that caller-side call; the loop is now a thin dispatch-and-free loop with no notification logic of its own. New test_jobdispatch_no_double_notify.c drives a 3-job batch (success, retryable, terminal) through handle_jobdispatch() and confirms exactly one notification fires (from the retryable job only), not four. Independently reviewed and approved. Confirmed handle_jobdispatch()'s sole caller never depended on its notification behavior (always returns 0 unconditionally). Flagged that test_commit_barrier_reply.c's test_jobdispatch_receipt_ack_no_wal_append is now stale for a second, independent reason (notify count, not just WAL count) -- both left for task 5.4 per Requirement 5.3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cxa1PmqAB5PyXfJaNvj8TN
This commit is contained in:
parent
65390fb111
commit
7b3dfed56e
5 changed files with 374 additions and 10 deletions
|
|
@ -64,7 +64,7 @@
|
|||
- _Depends: 1.1, 1.3, 3.2_
|
||||
|
||||
- [ ] 4. Integration: caller no longer duplicates dispatch-outcome reporting
|
||||
- [ ] 4.1 Remove the caller's own dispatch-outcome reporting now that resolution owns it
|
||||
- [x] 4.1 Remove the caller's own dispatch-outcome reporting now that resolution owns it
|
||||
- The function that invokes a dispatch attempt for each job in a batch no longer separately reports the outcome itself — reporting is now entirely owned by the dispatch resolution added in Task 3, preventing a failed dispatch from ever producing two different, conflicting outcome reports for the same job
|
||||
- Observable completion: a dispatch failure that resolves as permanent produces exactly one outcome report, not two conflicting ones
|
||||
- _Requirements: 3.1, 4.1, 4.2_
|
||||
|
|
@ -112,3 +112,5 @@
|
|||
- Task 3.3: capturing a site's natural error code (`rc`/`errno`) into a stable local must happen BEFORE any intervening call once more logic is inserted before the eventual `return` — `errno` is not guaranteed stable across `lock()`/`ERROR()`/`free_launchinfo()` etc. This bit the worker-null and worker-not-ready sites specifically (both originally read `errno` directly at the `return` statement with nothing in between; task 3.3 had to capture it immediately after `SET_ERROR()`/at branch entry, before inserting the new lock/classify/resolve sequence).
|
||||
- Task 3.3: `test_commit_barrier_reply.c` (a file otherwise owned by the `ha-commit-barrier-rollout` spec) is this binary's sole host of the `notify()` stub — any future task needing to observe `notify_dispatch()`'s calls in a controller unit test must add accessors there too (purely additive, read-only `_ext`-suffixed pass-throughs to its existing static capture globals — do not modify the stub body itself or its existing pinned tests). Confirmed as legitimate shared test infrastructure, not a boundary violation, by task 3.3's independent review.
|
||||
- Task 3.3: the bound constant is `XCE_DISPATCH_FAIL_RETRY_BOUND = 3` (design.md's stated default), defined once near job.c's top and used at all four retryable sites — no magic numbers. 3 consecutive failures still retry; the 4th resolves terminal.
|
||||
- Task 4.1: **`test_commit_barrier_reply.c`'s `test_jobdispatch_receipt_ack_no_wal_append` is now stale for a SECOND, independent reason**, on top of task 3.1's already-documented WAL-append-count staleness: its 2-job batch (one real-worker success, one `nhosts==0` terminal failure — no retryable job) now produces ZERO `notify_dispatch()` calls total post-task-4.1 (the success path never called it even before task 4.1, per task 3.1's design; the terminal path resolves via `mark_job_fail()`'s `schedFailJob` hook chain, not `notify_dispatch()`/`jobDispatchFail`, per task 3.2's design) — breaking its pinned `g_notify_count == 2` / both-`jobDispatchFail`-event assertions (lines ~1991-2003). **Task 5.4 must update BOTH the WAL-count assertion (task 3.1's finding) and the notify-count/event assertions (this finding) in the same test** before claiming full-suite regression-clean; the test's own scenario (1 success + 1 terminal, zero retryable jobs) may need to change too since it can no longer meaningfully exercise `notify_dispatch()` at all with today's cast of outcomes — consider adding a retryable-classified job to the batch if the intent is still to prove `notify_dispatch()`'s wiring through `handle_jobdispatch()`.
|
||||
- Task 4.1: confirmed `handle_jobdispatch()`'s only caller (`cluster.c:471`, inside `myreq_handler`) uses its return value only as the generic RPC-handler `rc`, and `handle_jobdispatch()` itself always returns 0 unconditionally regardless of any job's outcome — nothing depends on the removed per-job notification behavior at the caller level.
|
||||
|
|
|
|||
|
|
@ -1626,19 +1626,21 @@ int handle_jobdispatch(cluster_t* c, endpoint_t* ep, xce_request_t* request)
|
|||
xce_reply(ep, c->myhost, &reply);
|
||||
|
||||
for (i = 0; i < dispatchmsg->njobs; i++) {
|
||||
int rc1;
|
||||
xce_job_alloc_t* alloc = NULL;
|
||||
|
||||
MOVE_PTR(alloc, dispatchmsg->allocs[i]);
|
||||
|
||||
rc1 = dispatch_a_job_(c, ep, alloc);
|
||||
if (rc1 < 0) {
|
||||
/* notify dispatch fail */
|
||||
notify_dispatch(c, ep, alloc->jobid, rc1);
|
||||
} else {
|
||||
/* notify dispatch ok */
|
||||
notify_dispatch(c, ep, alloc->jobid, 0);
|
||||
}
|
||||
/* scheduler-dispatch-commit task 4.1 (Req 3.1, 4.1, 4.2): reporting
|
||||
* the dispatch outcome is now entirely owned by dispatch_a_job_()
|
||||
* itself (tasks 3.1-3.3) -- it already calls notify_dispatch() on
|
||||
* the retryable-failure path and relies on mark_job_fail()'s own
|
||||
* hook chain for the terminal-failure path, never on success. This
|
||||
* caller used to unconditionally call notify_dispatch() again for
|
||||
* every job regardless of that outcome, producing a conflicting
|
||||
* double report (e.g. both jobDispatchFail and schedFailJob for the
|
||||
* same terminal failure). The return value has nothing left for
|
||||
* this caller to act on. */
|
||||
(void)dispatch_a_job_(c, ep, alloc);
|
||||
|
||||
free_JobAlloc_ptr(&(alloc));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ controller_unit_test_src = [
|
|||
'testsuite/test_dispatch_commit.c',
|
||||
'testsuite/test_dispatch_fail_terminal.c',
|
||||
'testsuite/test_dispatch_fail_retry.c',
|
||||
'testsuite/test_jobdispatch_no_double_notify.c',
|
||||
'testsuite/test_upgrade_snapshot_restore.c',
|
||||
'testsuite/test_upgrade_snapshot_apply.c',
|
||||
'testsuite/test_bug857_regression.c',
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ extern void test_job_dispatch(void);
|
|||
extern void test_dispatch_commit(void);
|
||||
extern void test_dispatch_fail_terminal(void);
|
||||
extern void test_dispatch_fail_retry(void);
|
||||
extern void test_jobdispatch_no_double_notify(void);
|
||||
|
||||
void setUp(void) { return; }
|
||||
|
||||
|
|
@ -269,6 +270,7 @@ int main(int argc, char* argv[])
|
|||
TEST(test_dispatch_commit);
|
||||
TEST(test_dispatch_fail_terminal);
|
||||
TEST(test_dispatch_fail_retry);
|
||||
TEST(test_jobdispatch_no_double_notify);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
357
src/controller/testsuite/test_jobdispatch_no_double_notify.c
Normal file
357
src/controller/testsuite/test_jobdispatch_no_double_notify.c
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/* test_jobdispatch_no_double_notify.c -- scheduler-dispatch-commit task 4.1
|
||||
*
|
||||
* Proves handle_jobdispatch() (job.c) no longer duplicates the dispatch-
|
||||
* outcome reporting that dispatch_a_job_() (tasks 3.1-3.3) now owns
|
||||
* entirely. Before this task, handle_jobdispatch()'s loop called
|
||||
* notify_dispatch() itself for EVERY job in the batch regardless of
|
||||
* dispatch_a_job_()'s own outcome/classification -- on top of whatever
|
||||
* dispatch_a_job_() had already decided to do (nothing on success or on a
|
||||
* terminal failure, exactly one notify_dispatch() call on a retryable
|
||||
* failure). That produced a conflicting double report for a terminal
|
||||
* failure specifically (a caller-added jobDispatchFail alongside
|
||||
* mark_job_fail()'s own schedFailJob -- design.md's round-2 finding) and a
|
||||
* redundant extra notify_dispatch() call for a retryable failure.
|
||||
*
|
||||
* This test drives handle_jobdispatch() directly (not dispatch_a_job_test())
|
||||
* with a 3-job batch covering all three of dispatch_a_job_()'s own
|
||||
* notify_dispatch() behaviors:
|
||||
* - success (a live, ready worker) -> dispatch_a_job_() itself
|
||||
* calls notify_dispatch() 0 times
|
||||
* - retryable failure (unregistered worker) -> dispatch_a_job_() itself
|
||||
* calls notify_dispatch() exactly 1 time (below the retry bound)
|
||||
* - terminal failure (nhosts == 0) -> dispatch_a_job_() itself
|
||||
* calls notify_dispatch() 0 times (mark_job_fail()'s hook chain is the
|
||||
* sole notification for this case)
|
||||
*
|
||||
* dispatch_a_job_() alone (already proven correct by tasks 3.1-3.3's own
|
||||
* test_dispatch_commit.c / test_dispatch_fail_terminal.c /
|
||||
* test_dispatch_fail_retry.c) therefore produces exactly ONE
|
||||
* notify_dispatch() call for this batch. The fix under test is that
|
||||
* handle_jobdispatch() must not add anything on top of that -- the total
|
||||
* observed via notify() (test_commit_barrier_reply.c's capture stub, the
|
||||
* sole notify() definition linked into this binary) must be exactly 1, not
|
||||
* 4 (1 extra per job x 3, on top of the 1 dispatch_a_job_() already
|
||||
* produces) as it was before this task.
|
||||
*
|
||||
* Fixture mirrors test_dispatch_commit.c's dc_setup/dc_teardown (cluster_t
|
||||
* with a real disk-backed persist_t, cluster == NULL routing so
|
||||
* persist_submit_job_fact() takes the direct persist_wal_async() path, no
|
||||
* HA/commit-engine machinery needed) plus test_commit_barrier_reply.c's
|
||||
* g_req_ep / c->myhost requester-endpoint pattern (zeroed, no iomgr --
|
||||
* xce_reply()'s enqueue always succeeds against it, proven by that file's
|
||||
* own header) so handle_jobdispatch()'s receipt-ACK xce_reply() call has
|
||||
* somewhere to enqueue to.
|
||||
*
|
||||
* notify_dispatch() observability: see test_dispatch_fail_retry.c's header
|
||||
* for the full rationale -- this binary's sole notify() definition lives in
|
||||
* test_commit_barrier_reply.c, exposed read-only via its
|
||||
* cbrp_notify_capture_*_ext() accessors (added by task 3.3).
|
||||
*/
|
||||
|
||||
#include <rtti.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "cluster.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "dataq.h"
|
||||
#include "hook.h"
|
||||
#include "jobj.h"
|
||||
#include "kvmgr.h"
|
||||
#include "netio.h"
|
||||
#include "persist.h"
|
||||
#include "qobj.h"
|
||||
#include "typemap.h"
|
||||
#include "wobj.h"
|
||||
#include "xce/cluster_def.h"
|
||||
#include "xce/types.h"
|
||||
#include "xcealloc.h"
|
||||
#include "xstring.h"
|
||||
|
||||
/* test_commit_barrier_reply.c's minimal notify()-capture accessors (task
|
||||
* 3.3) -- see this file's header. This binary's sole notify() definition
|
||||
* lives there (dataex.c's real one is excluded from the unit-test link). */
|
||||
extern int cbrp_notify_capture_count_ext(void);
|
||||
extern int cbrp_notify_capture_event_ext(int idx);
|
||||
extern uint64_t cbrp_notify_capture_jobid_ext(int idx);
|
||||
extern int cbrp_notify_capture_code_ext(int idx);
|
||||
extern void cbrp_notify_capture_reset_ext(void);
|
||||
|
||||
/* jobj.c's job type table and the process-global xstring table are shared
|
||||
* across every test file in the controller_unit_test binary -- guard
|
||||
* idempotently, mirroring every sibling fixture in this suite. */
|
||||
static int g_jnd_jobobj_ready;
|
||||
|
||||
static cluster_t g_jnd;
|
||||
static char g_jnd_tmpdir[256];
|
||||
static endpoint_t g_jnd_req_ep; /* requester endpoint (receipt ACK) */
|
||||
static endpoint_t g_jnd_worker_ep; /* the one live worker's endpoint */
|
||||
static xce_peerinfo_t g_jnd_req_from;
|
||||
|
||||
static void jnd_setup(void)
|
||||
{
|
||||
kvmgr_params_t int_params = { .key_type = KVMGR_KEY_INT,
|
||||
.nslots = 32, .delta = 16 };
|
||||
kvmgr_params_t str_params = { .key_type = KVMGR_KEY_STRING,
|
||||
.nslots = 32, .delta = 16 };
|
||||
persist_config_t cfg;
|
||||
|
||||
if (makeString((char*)"jnd-probe") == NULL)
|
||||
xstring_table_init(64);
|
||||
|
||||
if (!g_jnd_jobobj_ready) {
|
||||
TEST_ASSERT_EQUAL_INT(0, job_obj_init(32, 32));
|
||||
g_jnd_jobobj_ready = 1;
|
||||
}
|
||||
|
||||
memset(&g_jnd, 0, sizeof(g_jnd));
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
xce_hook_init(&g_jnd.hooksys, XCE_CONTROLLER_HOOK_MAX));
|
||||
|
||||
g_jnd.jobmgr = makeInstance(&xkvmgr_type, &int_params);
|
||||
g_jnd.workermgr = makeInstance(&xkvmgr_type, &str_params);
|
||||
g_jnd.qmgr = makeInstance(&xkvmgr_type, &str_params);
|
||||
TEST_ASSERT_NOT_NULL(g_jnd.jobmgr);
|
||||
TEST_ASSERT_NOT_NULL(g_jnd.workermgr);
|
||||
TEST_ASSERT_NOT_NULL(g_jnd.qmgr);
|
||||
|
||||
/* Real disk-backed persist, cluster == NULL: wal_hook_append_
|
||||
* classified() takes the direct persist_wal_async() path -- no raft/
|
||||
* commit-engine machinery needed for the successful job's plain
|
||||
* WAL_JOB_DISPATCH fact append (mirrors test_dispatch_commit.c). */
|
||||
cfg = persist_config_defaults();
|
||||
cfg.wal_sync_ms = 5000;
|
||||
strcpy(g_jnd_tmpdir, "/tmp/xce_jnd41_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_jnd_tmpdir));
|
||||
persist_set_workdir(&g_jnd.persist, g_jnd_tmpdir, "ctrl-test");
|
||||
TEST_ASSERT_EQUAL_INT(0, persist_init(&g_jnd.persist, &cfg, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, persist_start(&g_jnd.persist));
|
||||
|
||||
g_jnd.myhost = &g_jnd.myhost_info;
|
||||
g_jnd.myhost_info.name = (char*)"ctrl-test";
|
||||
g_jnd.myhost_info.key = (char*)"";
|
||||
|
||||
/* Zeroed, no iomgr -- xce_reply()'s/xce_startjob_req()'s enqueue always
|
||||
* succeeds against an endpoint like this (test_dispatch_commit.c's and
|
||||
* test_commit_barrier_reply.c's own established, proven pattern). */
|
||||
memset(&g_jnd_req_ep, 0, sizeof(g_jnd_req_ep));
|
||||
g_jnd_req_ep.fd = -1;
|
||||
|
||||
memset(&g_jnd_worker_ep, 0, sizeof(g_jnd_worker_ep));
|
||||
g_jnd_worker_ep.fd = -1;
|
||||
|
||||
g_jnd_req_from.name = (char*)"requester";
|
||||
g_jnd_req_from.key = (char*)"";
|
||||
}
|
||||
|
||||
/* Frees every packet left queued on an endpoint's outq -- mirrors
|
||||
* test_dispatch_commit.c's dc_drain_ep. */
|
||||
static void jnd_drain_ep(endpoint_t* ep)
|
||||
{
|
||||
data_item_t* it;
|
||||
|
||||
while ((it = endpoint_deq(ep)) != NULL) {
|
||||
io_packet_t* pkt = ITPTR_TO_PACKET(it);
|
||||
free_packet(pkt);
|
||||
free(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void jnd_teardown(void)
|
||||
{
|
||||
char cmd[512];
|
||||
|
||||
jnd_drain_ep(&g_jnd_req_ep);
|
||||
jnd_drain_ep(&g_jnd_worker_ep);
|
||||
|
||||
persist_stop(&g_jnd.persist);
|
||||
persist_fini(&g_jnd.persist);
|
||||
|
||||
if (g_jnd.workermgr) dereference(g_jnd.workermgr);
|
||||
if (g_jnd.qmgr) dereference(g_jnd.qmgr);
|
||||
if (g_jnd.jobmgr) dereference(g_jnd.jobmgr);
|
||||
xce_hook_fini(&g_jnd.hooksys);
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", g_jnd_tmpdir);
|
||||
if (system(cmd) != 0)
|
||||
fprintf(stderr, "cleanup warning: rm -rf %s failed\n", g_jnd_tmpdir);
|
||||
}
|
||||
|
||||
/* Seeds a queue and a PEND job with a real submit/start_info against
|
||||
* g_jnd.jobmgr/qmgr (dispatch_a_job_() reads submit->queue and
|
||||
* submit->start_info). Returns the seeded job_t* (borrowed -- g_jnd.jobmgr
|
||||
* owns the creation reference). Mirrors test_dispatch_fail_retry.c's
|
||||
* dfr_seed_job. */
|
||||
static job_t* jnd_seed_job(uint64_t jobid, const char* qname)
|
||||
{
|
||||
xce_queue_t qdef;
|
||||
job_startinfo_t startinfo;
|
||||
job_submit_data_t submit;
|
||||
xce_job_t jdef;
|
||||
job_t* j;
|
||||
|
||||
memset(&qdef, 0, sizeof(qdef));
|
||||
qdef.queue = (char*)qname;
|
||||
TEST_ASSERT_NOT_NULL(makeQueue(g_jnd.qmgr, &qdef));
|
||||
|
||||
memset(&startinfo, 0, sizeof(startinfo));
|
||||
startinfo.cmdline = (char*)"/bin/true";
|
||||
memset(&submit, 0, sizeof(submit));
|
||||
submit.queue = (char*)qname;
|
||||
submit.user = (char*)"tester";
|
||||
submit.start_info = &startinfo;
|
||||
|
||||
memset(&jdef, 0, sizeof(jdef));
|
||||
jdef.jobid = jobid;
|
||||
jdef.state = JOB_STAT_PEND;
|
||||
jdef.submit = &submit;
|
||||
j = makeJob(g_jnd.jobmgr, &jdef);
|
||||
TEST_ASSERT_NOT_NULL(j);
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Registers a ready worker (host.status defaults to 0 via calloc) in
|
||||
* g_jnd.workermgr with ep pointed at the given endpoint. Mirrors
|
||||
* test_dispatch_commit.c's dc_seed_ready's worker half. */
|
||||
static void jnd_seed_ready_worker(const char* wname, endpoint_t* ep)
|
||||
{
|
||||
xce_worker_t wdef;
|
||||
hostattrs_t* attrs;
|
||||
worker_t* w = NULL;
|
||||
|
||||
attrs = calloc(1, sizeof(*attrs));
|
||||
TEST_ASSERT_NOT_NULL(attrs);
|
||||
attrs->hosttype = makeString("LINUX");
|
||||
attrs->hostmodel = makeString("x86");
|
||||
attrs->cpufactor = 1.0f;
|
||||
attrs->maxcpus = 4;
|
||||
memset(&wdef, 0, sizeof(wdef));
|
||||
wdef.name = (char*)wname;
|
||||
wdef.attrs = attrs;
|
||||
TEST_ASSERT_NOT_NULL(makeWorker(g_jnd.workermgr, &wdef));
|
||||
|
||||
lock(g_jnd.workermgr, RTTI_LOCK_READ);
|
||||
key_find(g_jnd.workermgr, (void*)wname, (void**)&w);
|
||||
unlock(g_jnd.workermgr);
|
||||
TEST_ASSERT_NOT_NULL(w);
|
||||
w->ep = ep;
|
||||
}
|
||||
|
||||
#define JND_JOBID_SUCCESS 92101ULL
|
||||
#define JND_JOBID_RETRYABLE 92102ULL
|
||||
#define JND_JOBID_TERMINAL 92103ULL
|
||||
|
||||
/* THE core proof (Req 3.1, 4.1, 4.2): a batch mixing all three of
|
||||
* dispatch_a_job_()'s own notify_dispatch() behaviors must produce EXACTLY
|
||||
* what dispatch_a_job_() itself produces (1 -- the retryable job's own
|
||||
* call), not 1 extra per job from handle_jobdispatch()'s own loop. */
|
||||
static void test_handle_jobdispatch_matches_dispatch_a_job_notify_count(void)
|
||||
{
|
||||
xce_job_alloc_t* allocs[3];
|
||||
xce_job_dispatch_t dispatchmsg;
|
||||
xce_request_t req;
|
||||
job_t* jsucc;
|
||||
job_t* jretry;
|
||||
job_t* jterm;
|
||||
int rc;
|
||||
|
||||
jnd_setup();
|
||||
|
||||
jsucc = jnd_seed_job(JND_JOBID_SUCCESS, "jnd-q1");
|
||||
jretry = jnd_seed_job(JND_JOBID_RETRYABLE, "jnd-q2");
|
||||
jterm = jnd_seed_job(JND_JOBID_TERMINAL, "jnd-q3");
|
||||
|
||||
jnd_seed_ready_worker("jnd-wkr-ok", &g_jnd_worker_ep);
|
||||
/* jretry deliberately targets an unregistered worker name -- no
|
||||
* corresponding makeWorker() call, matching test_dispatch_fail_retry.c's
|
||||
* own established WORKER_NULL/WORKER_LOOKUP fixture. */
|
||||
|
||||
allocs[0] = make_job_alloc(JND_JOBID_SUCCESS, 1,
|
||||
make_host_allocs(1,
|
||||
make_host_alloc((char*)"jnd-wkr-ok", 1,
|
||||
make_allocs(1,
|
||||
make_alloc((char*)"slots", NULL, 0, 1.0f, 0)))));
|
||||
TEST_ASSERT_NOT_NULL(allocs[0]);
|
||||
|
||||
allocs[1] = make_job_alloc(JND_JOBID_RETRYABLE, 1,
|
||||
make_host_allocs(1,
|
||||
make_host_alloc((char*)"jnd-no-such-worker", 1,
|
||||
make_allocs(1,
|
||||
make_alloc((char*)"slots", NULL, 0, 1.0f, 0)))));
|
||||
TEST_ASSERT_NOT_NULL(allocs[1]);
|
||||
|
||||
/* nhosts == 0: XCE_DISPATCH_FAIL_SITE_INVALID_ALLOC, terminal --
|
||||
* mirrors test_commit_barrier_reply.c's cbrp_arrange_jobdispatch()
|
||||
* second allocation exactly. */
|
||||
allocs[2] = make_job_alloc(JND_JOBID_TERMINAL, 0, NULL);
|
||||
TEST_ASSERT_NOT_NULL(allocs[2]);
|
||||
|
||||
memset(&dispatchmsg, 0, sizeof(dispatchmsg));
|
||||
dispatchmsg.njobs = 3;
|
||||
dispatchmsg.allocs = allocs;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.opcode = OPCode_jobdispatch;
|
||||
req.seq = 42;
|
||||
req.from = &g_jnd_req_from;
|
||||
req.body_which = Request_body_dispatchmsg;
|
||||
req.body.dispatchmsg = &dispatchmsg;
|
||||
|
||||
cbrp_notify_capture_reset_ext();
|
||||
|
||||
/* Act: handle_jobdispatch() consumes/frees every alloc itself via
|
||||
* MOVE_PTR + free_JobAlloc_ptr() in its own loop -- nothing left for
|
||||
* this test to free afterward. */
|
||||
rc = handle_jobdispatch(&g_jnd, &g_jnd_req_ep, &req);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "handle_jobdispatch's own return "
|
||||
"is unconditionally 0, unaffected by any per-job outcome");
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_RUN, jsucc->job.state,
|
||||
"the successful dispatch must still commit RUN (unaffected by this "
|
||||
"task -- only the caller's own notification logic changes)");
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_PEND, jretry->job.state,
|
||||
"a below-bound retryable failure must still leave the job PEND");
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(1, jretry->dispatch_fail_count,
|
||||
"the retryable failure's own counter increment is unaffected by "
|
||||
"this task");
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_EXIT, jterm->job.state,
|
||||
"the terminal (nhosts == 0) failure must still resolve to EXIT via "
|
||||
"mark_job_fail(), unaffected by this task");
|
||||
|
||||
/* THE assertion this task exists for: dispatch_a_job_() alone (tasks
|
||||
* 3.1-3.3, already proven correct) produces exactly ONE
|
||||
* notify_dispatch() call for this exact batch -- the retryable job's
|
||||
* own call. Before this task, handle_jobdispatch()'s own unconditional
|
||||
* per-job notify_dispatch() calls would add one more per job (3), for a
|
||||
* total of 4 -- a conflicting double (and, for the terminal job,
|
||||
* mismatched) report. */
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, cbrp_notify_capture_count_ext(),
|
||||
"handle_jobdispatch() must not add any notify_dispatch() call on "
|
||||
"top of what dispatch_a_job_() itself already produced for this "
|
||||
"batch (Req 3.1, 4.1, 4.2) -- exactly the retryable job's own "
|
||||
"call, none for the success or terminal jobs");
|
||||
TEST_ASSERT_EQUAL_INT(NotifyData_data_jobDispatchFail,
|
||||
cbrp_notify_capture_event_ext(0));
|
||||
TEST_ASSERT_EQUAL_UINT64(JND_JOBID_RETRYABLE,
|
||||
cbrp_notify_capture_jobid_ext(0));
|
||||
|
||||
jnd_teardown();
|
||||
printf("test_handle_jobdispatch_matches_dispatch_a_job_notify_count: "
|
||||
"PASS\n");
|
||||
}
|
||||
|
||||
void test_jobdispatch_no_double_notify(void)
|
||||
{
|
||||
UnitySetTestFile(__FILE__);
|
||||
printf("\n>> handle_jobdispatch() no-double-notify tests (Req 3.1, 4.1, "
|
||||
"4.2)\n\n");
|
||||
|
||||
RUN_TEST(test_handle_jobdispatch_matches_dispatch_a_job_notify_count);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue