Repository navigation
fix(ios): hold a canceled request's locks until its runner command ends - #3385
Conversation
A request canceled through its signal released the session and device locks as soon as the runner fetch aborted, while the XCTest runner kept executing the command. A next request whose device work bypasses the runner (simctl launch, privacy, screenshots) could overtake it. The exchange now drains a canceled command it already sent: it polls the runner journal with `status`/`statusCommandId` until the entry is terminal, bounded by the command's own remaining deadline rather than the aborted request signal. Terminal evidence settles the abandoned charge through the shared journal settlement; a drain that runs out of deadline leaves it abandoned. A request canceled before the send releases at once without charging the runner. A caller's own deadline abort is unchanged. Closes #3383 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
A cancellation that landed during runner route resolution reached the POST as an aborted fetch, so the exchange charged and drained a command the runner never received. The transport now discloses `dispatched: no` for a cancellation seen before the POST is issued, and the exchange withdraws the charge of any provably unwritten send instead of marking it abandoned or reading the journal for it. Also qualifies the client docs on when a canceled iOS request's hold ends, and bounds the test helper that waits for the fake runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ypes The journal states and terminal-evidence settlement moved to a new module that the Apple facade entries now evaluated eagerly through runner-lifecycle -> runner-command-recovery, growing their closures. They live beside RunnerCommandAccounting in runner-session-types.ts, which those entries already evaluate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The PR is ready at c8c44a0. The change holds a canceled request's locks until its runner command ends, and I found no problem that should stop the merge. All 21 checks pass at this commit, and I know of no conflicts. Nothing blocks the merge. Not blocking, and you can take or leave these: (1) the read-only route in waitForRunner throws |
…en (#3409) * fix(ios): disclose whether a canceled read-only runner send was written Follow-up to #3385. The read-only route (waitForRunner) threw a bare canceled error, so a cancellation that landed before any connect attempt was charged and drained with one status poll instead of withdrawn. It now discloses `dispatched: no` when no attempt could have written the command, and `unknown` once an attempt was interrupted mid-flight or an earlier attempt may have written it. Pins two exchange behaviors with tests: a refused connection withdraws its charge whether or not the request was canceled (the runner never received the command), and a drain whose status read fails stops at once with the charge left abandoned. Refs #3383 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ios): keep dispatched no when a canceled runner route proves nothing was written Each route of the read-only connect loop replaced its failure with a bare canceled error, so a cancellation whose own failure proved nothing was written (a usbmux connect still in progress, a refused fetch, a simulator curl that could not connect) reported `dispatched: unknown`. Routes now keep `no` when that failure is the proof, and the final simulator post combines the loop's write evidence with its own failure the same way. An abort that interrupts a POST, or the request's shared abort reason, still reads as possibly written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
On iOS, a request canceled with its
signalnow keeps the session and device locks until the runner reports the command finished. Before,open --relaunchor a screenshot could run while XCTest was still running the canceled command. The client still rejects at once.runner-exchange.ts: after a typedrequest_canceledabort of a sent command, the exchange pollsstatus/statusCommandIduntil the journal shows the command as terminal, then settles the charge. The polls are bounded by the command's remaining deadline, not the aborted signal. A deadline,notAccepted, or a status failure ends the drain and leaves the charge abandoned. A cancellation the transport proves unsent (dispatched: no) withdraws its charge and releases at once.runner-session-types.tsbesideRunnerCommandAccounting;client-api.mddocuments the hold.Closes #3383
Validation
Commit
c8c44a01f:pnpm check:affected --runpassed (3220 tests, incl. eager-closure budgets; live run below was onaa533890b). Coverage, Swift builds, and replay lanes are CI-owned; no Swift changed.The new
runner-exchange.test.tscases cover the terminal drain, a second task waiting on the same keyed lock, abort before send,notAccepted, deadline expiry, and a caller-deadline negative case. With the fix removed, 5 of them fail.Live on the iPhone 18 Pro simulator: a 5 s
longPressaborted after 300 ms, thenopen --relaunch.simctl launchfinished 16:08:24.3Z, while the runnerlongPresscompleted at 16:08:29.4Z (overlap).longPresscompleted at 16:09:17.48Z, the drain reportedterminalat 17.50Z, andsimctl launchran 17.70–17.97Z.Risk: a command the runner received but has not yet journaled reads as
notAcceptedand releases early. That window is milliseconds.🤖 Generated with Claude Code