From 860997634a5c6b1078a507c8998c238e6bf21794 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Fri, 19 Jun 2026 16:57:47 +0000 Subject: [PATCH] fix(xce-test-e2e-primitives): jobsub clean-ack requires echoed JobId == submitted id (bug-786, surfaced by live soak 7.2) --- src/controller/testsuite/test.c | 44 ++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/controller/testsuite/test.c b/src/controller/testsuite/test.c index 6b50cd1..a54ecf4 100644 --- a/src/controller/testsuite/test.c +++ b/src/controller/testsuite/test.c @@ -1521,13 +1521,25 @@ int cmd_jobsub(int argc, char* argv[]) FREEUP(submit.start_info->exdata); FREEUP(submit.start_info); - /* Report success only on a clean ack with a well-formed JobId (bug-786, - * Req 7.1/7.2/7.3). xce_jobsubmit() writes *jobid only when the reply - * status is >= 0; XCECLI_FAILOVER_CALL surfaces a non-clean outcome as a - * negative rc (e.g. -E_XCE_NOT_READY on no-primary budget exhaustion, - * -E_XCE_REDIRECT / -E_XCE_NOMASTER on a standby, -ETIMEDOUT / -ENOTCONN on - * transport loss). A clean ack is therefore rc >= 0 AND a non-zero jobid; - * anything else is a typed failure and must NOT print "ok" or set $jobid. */ + /* Report success only on a clean ack that echoes back the EXACT JobId we + * submitted (bug-786, Req 7.1/7.2/7.3). xce_jobsubmit() writes *jobid only + * when the reply status is >= 0; XCECLI_FAILOVER_CALL surfaces a non-clean + * outcome as a negative rc (e.g. -E_XCE_NOT_READY on no-primary budget + * exhaustion, -E_XCE_REDIRECT / -E_XCE_NOMASTER on a standby, -ETIMEDOUT / + * -ENOTCONN on transport loss). + * + * The `jobid != 0` gate alone is NOT sufficient: on a failover, libxce's + * handle_sub_reply() returns -1 on a non-jobid reply but LEAVES the stale + * previous value in ctx->reply.u.jobid, and xceapi.c copies it out whenever + * status >= 0 — so a rejected submit can surface as rc >= 0 with a NON-ZERO + * stale-garbage jobid (the live soak captured `63-15--1-1015808[32751]` + * while the controller logged "handle_jobsubmit fail to lock job" and never + * committed it). We therefore require the returned jobid to EQUAL the JobId + * we generated locally and submitted: on a genuine clean ack the controller + * commits and echoes back exactly submit->jobid (handle_jobsubmit replies + * reply.data.jobid = job_submit() = base_id = submit->jobid; verified + * round-trip). `jobid != jobsubmit.jobid` covers BOTH the never-written + * (zero) case AND the stale non-zero garbage case in one uint64 compare. */ if (rc < 0) { char ebuf[128]; @@ -1556,11 +1568,21 @@ int cmd_jobsub(int argc, char* argv[]) return rc; } - if (jobid == 0) { - /* Non-negative rc but no JobId was written: not a clean ack. Never - * print "ok" with an uninitialized/garbage id (Req 7.3). */ + if (jobid != jobsubmit.jobid) { + /* Non-negative rc but the returned JobId does not match the one we + * submitted: not a clean ack. This rejects BOTH the never-written + * (jobid == 0) case AND the stale-garbage non-zero case left in the + * libxce reply union on a failover-rejected submit. Never print "ok" + * with an uninitialized/garbage id (Req 7.3). */ + char wantStr[128]; + char gotStr[128]; + + fmt_jobid(wantStr, sizeof(wantStr) - 1, jobsubmit.jobid); + fmt_jobid(gotStr, sizeof(gotStr) - 1, jobid); fprintf(stderr, - "jobsubmit failed: no clean ack (no JobId returned)\n"); + "jobsubmit failed: no clean ack (controller did not confirm " + "the submitted JobId: wanted %s, got %s)\n", + wantStr, gotStr); return -E_XCE_INTERNAL; }