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
|
|
@ -249,6 +249,64 @@ void xcetest_set_active_connection(void* conn_key);
|
|||
*/
|
||||
int xcetest_run_interruptible(xcetest_blocking_fn fn, void* ctx);
|
||||
|
||||
/*
|
||||
* xcetest_wait_connection_idle (xce-test-cli-ux task 3.2 remediation round
|
||||
* 1, bug-1795) -- blocks the calling thread until no worker thread
|
||||
* (running or abandoned-but-still-running) is active on `conn_key`, per
|
||||
* this module's own "at most one worker thread per connection" invariant,
|
||||
* then returns immediately.
|
||||
*
|
||||
* WHY THIS EXISTS: xcetest_run_interruptible()'s per-connection lock
|
||||
* registry above enforces that invariant BETWEEN calls on the same
|
||||
* connection -- a second call on a connection an abandoned worker still
|
||||
* holds waits (interruptibly) for that worker to finish. It does NOT by
|
||||
* itself protect connection TEARDOWN: test.c's leave_ctx() (called by
|
||||
* `up`/`quit`) tears down the connection object out from under an
|
||||
* abandoned-but-still-running worker with no wait/join of its own,
|
||||
* because leave_ctx() never calls xcetest_run_interruptible() -- it is
|
||||
* connection teardown, not a new RPC. xcecli_wakeup() (called by
|
||||
* leave_ctx() just before this function, best-effort) nudges that worker
|
||||
* to unwind faster than its own ~10s deadline, but nothing forced
|
||||
* leave_ctx() to actually wait for it -- a genuine, exploitable
|
||||
* use-after-free window (any abandoned worker still reading/writing the
|
||||
* connection object while leave_ctx()'s xcecli_stop()/xcecli_ctx_fini()
|
||||
* frees it). This function closes that gap by reusing the SAME
|
||||
* per-connection semaphore registry xcetest_run_interruptible() already
|
||||
* builds and maintains, rather than inventing a second mechanism.
|
||||
*
|
||||
* CONTRACT: looks up (or lazily creates, via the exact same internal
|
||||
* lookup xcetest_run_interruptible() itself uses) the sem_t for
|
||||
* `conn_key`, does a PLAIN BLOCKING sem_wait() on it (deliberately NOT
|
||||
* the interruptible sem_trywait()-poll variant used inside
|
||||
* xcetest_run_interruptible() -- this is a narrow, safety-critical wait
|
||||
* where correctness matters more than Ctrl-C responsiveness; it should
|
||||
* resolve quickly in the ordinary case since the caller's own
|
||||
* xcecli_wakeup() call already nudges the worker to finish sooner), then
|
||||
* immediately sem_post()s it back before returning. It does NOT hold the
|
||||
* semaphore locked on return -- the semantic this function provides is
|
||||
* "prove that, at this instant, no worker is active on this connection,"
|
||||
* not "hold this connection locked for some ongoing operation of the
|
||||
* caller's own." A caller that needs the latter must acquire and hold the
|
||||
* lock itself (which only xcetest_run_interruptible() currently does).
|
||||
*
|
||||
* A transient EINTR from sem_wait() itself (a signal delivered while
|
||||
* blocked, distinct from `errno`-based break_out signal state) is
|
||||
* retried internally rather than surfaced -- this function's whole
|
||||
* purpose is to make the wait unconditional, not skippable.
|
||||
*
|
||||
* Safe to call with any `conn_key` previously passed to
|
||||
* xcetest_set_active_connection() (or, for that matter, any key never
|
||||
* seen before -- the lazy-create path simply creates a fresh, immediately
|
||||
* uncontended semaphore for it and returns right away). Must be called
|
||||
* from the same single REPL thread this module's other entry points are
|
||||
* documented to run on (see this header's "CONCURRENCY OF THE REGISTRY
|
||||
* ITSELF" note) -- it is not itself safe to call concurrently from
|
||||
* multiple threads, though the semaphore it waits on is of course safe to
|
||||
* be concurrently sem_post()'d by a worker thread while this function
|
||||
* waits on it.
|
||||
*/
|
||||
void xcetest_wait_connection_idle(void* conn_key);
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/*
|
||||
* Test-only instrumentation, NOT part of the production API and never
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue