fix: bound retained iOS runner lifetime with an idle stop#1025
Conversation
A runner retained after session close (#1021) holds the device's runner lease indefinitely, which blocks every other daemon on the machine from using the device - observed in the wild when a leftover verification daemon squatted a simulator through its retained runner. Session close now schedules an idle stop when it retains the runner: if nothing touches the runner within 5 minutes (any ensureRunnerSession call cancels the timer), it is stopped and the lease released. AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MS overrides the window; 0 disables idle stops entirely (the previous retain-until-daemon-exit behavior). The timer is unref'd and cleared on explicit stop and shutdown detach. Runners warmed by explicit prepare ios-runner are not timed - prepare expresses intent to keep a warm runner, and its ensureRunnerSession call cancels any pending stop.
37a975e to
9fd1396
Compare
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
| const mockPrewarmAppleRunnerCache = vi.mocked(prewarmAppleRunnerCache); | ||
| const mockPrepareIosRunner = vi.mocked(prepareIosRunner); | ||
| const mockStopIosRunner = vi.mocked(stopIosRunnerSession); | ||
| const mockScheduleIosRunnerIdleStop = vi.mocked(scheduleIosRunnerIdleStop); |
There was a problem hiding this comment.
🟡 New test mock is never reset between tests, allowing call history to leak across test cases
The newly added mock for the idle-stop scheduler is never cleared between tests (mockScheduleIosRunnerIdleStop.mockReset() missing from beforeEach at src/daemon/handlers/__tests__/session.test.ts:162), so call counts from earlier close-on-iOS-simulator tests leak into later ones.
Impact: A test asserting that the scheduler was called could pass even if the code under test never invoked it, masking a real regression.
Every other mock in the file follows the reset pattern
All 25+ other mocks declared at src/daemon/handlers/__tests__/session.test.ts:134-160 have a corresponding mockXxx.mockReset() call inside beforeEach at lines 162-219. The new mockScheduleIosRunnerIdleStop (line 143) is the only one without a reset. The assertion at line 3530 (expect(mockScheduleIosRunnerIdleStop).toHaveBeenCalledWith('sim-1')) could see stale calls from a prior test that also closes an iOS simulator session with runner retention.
Prompt for agents
In src/daemon/handlers/__tests__/session.test.ts, the beforeEach block (starting around line 162) resets every mock declared in the file except the newly added mockScheduleIosRunnerIdleStop. Add mockScheduleIosRunnerIdleStop.mockReset() inside beforeEach, following the same pattern as the other mocks (e.g., after mockStopIosRunner.mockReset() around line 181). This prevents call-count leakage between tests.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in ac96532 — added mockScheduleIosRunnerIdleStop.mockReset() to beforeEach, matching the file's pattern. Good catch.
Review finding on #1025: every other mock in the file resets in beforeEach; without it the retention assertion could pass on call history leaked from an earlier close test.
|
Summary
Follow-up to #1021 and the #1013 thread. A runner retained after session close holds the device's runner lease indefinitely, which blocks every other daemon on the machine from using the device — observed in the wild during benchmarking, when a leftover verification daemon squatted the simulator through its retained runner and every command from other daemons failed with "already owned by another agent-device daemon".
Session close now schedules an idle stop when it retains the runner: if nothing touches the runner within 5 minutes, it is stopped and the lease released. Any
ensureRunnerSessioncall (a new session opening on the device, a command, an explicitprepare) cancels the pending stop — so the fast close→open reuse from #1021 is unaffected in the window that matters.AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MSoverrides the window;0disables idle stops entirely (the previous retain-until-daemon-exit behavior).prepare ios-runnerare not timed — prepare expresses intent to keep a warm runner.Validation
0disables (947 tests green across apple core + daemon handler suites).clean:daemonand detach paths unaffected.