fix(libxce): bug-1425 -- populate jobsubmit's *jobid on idempotent-replay success

xcecli_jobsubmit() could return rc>=0 (success) while leaving its *jobid
output parameter at 0, reproducible under concurrent client load
immediately after a fresh single-node election (~1-3/9 clients per round
in the original repro).

Root cause: XCECLI_FAILOVER_CALL's idempotent-replay rule
(client_failover.c's xce_failover_classify) normalizes a retried resend
that hits the server's EEXIST/inflight-collision rejection into overall
success -- but that rejection reply carries no payload
(handle_jobsubmit's EEXIST branches reply with RequestReply_data_nodata,
job.c), so xce_jobsubmit() never populates *jobid on the winning
attempt, nor did it on the earlier failing attempt (raw status was
already negative).

Adds jobsubmit_finalize_jobid() (client_internal.h/client.c), called
from xcecli_jobsubmit() right after XCECLI_FAILOVER_CALL returns. Sets
*jobid = submit->jobid (the client-generated id already on the wire --
the controller commits/echoes this exact value on every genuine success
reply, bug-786/job.c:1439) whenever the overall call succeeded,
otherwise leaves it untouched. Placed at the jobsubmit-specific wrapper
layer, not the generic classify/decide layer, which stays fully generic.

3 new socket-free unit tests in test_libxce_retry.c (following this
codebase's direct-state-simulation style for I/O-adjacent logic).
Live-validated: full ASAN rebuild clean, libxce test suite 1/1 OK with
all 3 new tests passing, and a 5-round/45-client concurrent-submit
repro against a live single-node cluster showed 0 "no clean ack"
failures (vs. 1-3/9 per round pre-fix).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-16 10:47:09 +00:00
parent b33f8b68a6
commit 8a6c339a55
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
3 changed files with 95 additions and 2 deletions

View file

@ -323,6 +323,15 @@ int xcecli_jobdispatch(xcecli_ctx_t c, xce_job_dispatch_t* info)
xce_jobdispatch(&(ctx->ctx), info));
}
/* bug-1425: see the doc comment on the declaration in client_internal.h for
* the full mechanism this closes. */
void jobsubmit_finalize_jobid(uint64_t* jobid, uint64_t submitted_jobid,
int overall_rc)
{
if (jobid != NULL && overall_rc >= 0)
*jobid = submitted_jobid;
}
int xcecli_jobsubmit(xcecli_ctx_t c, xce_job_submit_t* submit, uint64_t* jobid)
{
api_ctx_t* ctx = (api_ctx_t*)c;
@ -331,8 +340,10 @@ int xcecli_jobsubmit(xcecli_ctx_t c, xce_job_submit_t* submit, uint64_t* jobid)
return -EINVAL;
}
return XCECLI_FAILOVER_CALL(ctx, /*is_write=*/true,
xce_jobsubmit(&(ctx->ctx), submit, jobid));
int rc = XCECLI_FAILOVER_CALL(ctx, /*is_write=*/true,
xce_jobsubmit(&(ctx->ctx), submit, jobid));
jobsubmit_finalize_jobid(jobid, submit->jobid, rc);
return rc;
}
int xcecli_joblist(xcecli_ctx_t c, xce_job_t*** jobs)