feat(xce-test-cli-ux): wrap every blocking RPC with the interrupt adapter
Routes all 26 blocking xcecli_* call sites across ~24 commands through xcetest_run_interruptible(), wires xcetest_set_active_connection() at execute_line()'s dispatch point (active_ctx->ctx, read fresh per command), and fixes main()'s REPL loop to distinguish a real EOF from an EINTR-interrupted readline() so Ctrl-C at an empty prompt discards the line instead of exiting the session. Live-discovered and fixed bug-1794: fill_job_alloc() never initializes xce_job_alloc_t's nres_shared/allocs_shared fields -- harmless as stack garbage before this task, genuine heap garbage once moved into a heap-allocated ctx block (a necessary consequence of the adapter's heap-only precondition). Fixed by switching all ~26 call-context allocations from malloc to calloc uniformly. Also fixed an argv-aliasing hazard: cmd_hello/cmd_jobckpt's -m values aliased optarg, which execute_line() frees immediately after return including on the abandoned path -- now strdup'd into the heap block. Also closed a safety-critical gap this task's own design introduced (bug-1795): before this task the REPL was fully synchronous, so leave_ctx() (called by up/quit) could never run while an RPC was outstanding. An abandoned worker can now persist past command completion, so leave_ctx() tearing down the connection could use-after-free it. Fixed via xcetest_wait_connection_idle(), which blocks on the same per-connection semaphore the adapter already tracks, then releases it, proving no worker is active before teardown. Proven with a deterministic white-box RED->GREEN test, since the race itself is too timing-dependent for a black-box test to reliably force. 1361/1361 across the full controller suite (+2), 0 regressions. Task 3.2 of .kiro/specs/xce-test-cli-ux/tasks.md. Requirements: 1.1, 1.2, 1.3, 1.4, 1.5 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
parent
f152c59e75
commit
45120af2cc
7 changed files with 1887 additions and 70 deletions
|
|
@ -314,3 +314,41 @@ int xcetest_run_interruptible(xcetest_blocking_fn fn, void* ctx)
|
|||
xcetest_sleep_poll_interval();
|
||||
}
|
||||
}
|
||||
|
||||
/* See interruptcall.h for the full contract. Reuses xcetest_conn_lock_for()
|
||||
* -- the SAME lazy-lookup/create used by xcetest_run_interruptible() above
|
||||
* -- rather than a parallel registry, so this genuinely observes the same
|
||||
* per-connection semaphore any in-flight or abandoned worker on `conn_key`
|
||||
* is holding. */
|
||||
void xcetest_wait_connection_idle(void* conn_key)
|
||||
{
|
||||
sem_t* lock;
|
||||
int rc;
|
||||
|
||||
lock = xcetest_conn_lock_for(conn_key);
|
||||
if (!lock) {
|
||||
/* Allocation failure in the lazy-create path: this key has never
|
||||
* been registered, so by definition no worker could be holding a
|
||||
* lock for it via this registry. Nothing to wait for. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Plain BLOCKING wait, deliberately not the interruptible
|
||||
* sem_trywait()-poll loop used above -- see interruptcall.h's
|
||||
* "CONTRACT" section on this function. A signal delivered while
|
||||
* blocked here can legitimately interrupt sem_wait() with EINTR even
|
||||
* though nothing else ever posts spuriously; retry rather than treat
|
||||
* that as "acquired" or bail out early, since either would defeat
|
||||
* this function's entire purpose (proving the wait genuinely
|
||||
* completed before the caller proceeds to tear the connection down). */
|
||||
do {
|
||||
rc = sem_wait(lock);
|
||||
} while ((rc != 0) && (errno == EINTR));
|
||||
assert(rc == 0); /* NDEBUG caveat: see xcetest_worker_main()'s comment. */
|
||||
|
||||
/* Immediately release: the semantic is "prove idle right now," not
|
||||
* "hold the connection locked." */
|
||||
rc = sem_post(lock);
|
||||
(void)rc;
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue