You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bound the recorder's frame captures through the existing main-thread owner (runMainThreadWork), and drop frames on timeout. Screenshot commands already run inside the bounded command_execution hop (apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift:1097), so a capture that runs on main from there adds no unbounded wait. Screenshots are out of scope. This decision was cross-checked with a second model and against the code at 01328de411.
Facts
captureResolvedAppScreen (RunnerAppScreenCapture.swift:70-86) runs the capture directly when already on main, and otherwise calls DispatchQueue.main.sync with no bound. The iOS hop came in fix(ios): capture the display that hosts the app, not the main screen #2741 (6f4a9dc, v0.21.9). The macOS recording capture does the same at RunnerTests+Lifecycle.swift:67.
The recorder's timer (RunnerTests+ScreenRecorder.swift:112-121, 15 fps by default per RunnerTests.swift:48) calls captureFrame() synchronously on the recorder queue, and already skips nil frames. While the main thread is wedged, the first tick blocks the recorder queue and later ticks pile up behind it. Busy and wedged accounting (runMainThreadWork, RunnerTests+MainThreadWork.swift:55-124, fix(ios): keep the runner alive through hostile snapshots #2621) never sees any of it.
Required behavior
When recording captures a frame from off the main thread, on both iOS and macOS, it goes through runMainThreadWork with a per-frame timeout no longer than the frame interval. Keep the existing on-main fast path for screenshot callers.
On timeout, the frame is dropped (nil). A late result from abandoned work is thrown away and never appended.
While abandoned main-thread work is still pending, the recorder skips capture instead of queueing another hop, so each tick adds no more than one pending main-thread item.
stop() during a timed-out capture finishes. It does not append a late frame, and the next recording generation is unaffected.
A missing screenshot frame still fails the screenshot command. A missing recording frame is still optional.
Completion conditions
Unit tests, using the existing MainThreadWork test seams:
A timeout drops the frame and counts it as abandoned, the pending work drains, and recording resumes.
A persistent wedge produces no unbounded queued work.
A stop during the timeout appends no late frame.
A new recording after a stop is clean.
Also:
Run pnpm check:xctest-selection, build the runner, and run the host-lane unit tests (docs/agents/testing.md).
Live check on an iOS simulator through the runner, not simctl capture: a record start/record stop of about 20 s produces a playable export with a sensible frame count, and a screenshot during and after recording still works. Record the commands in the PR.
Decision
Bound the recorder's frame captures through the existing main-thread owner (
runMainThreadWork), and drop frames on timeout. Screenshot commands already run inside the boundedcommand_executionhop (apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift:1097), so a capture that runs on main from there adds no unbounded wait. Screenshots are out of scope. This decision was cross-checked with a second model and against the code at01328de411.Facts
captureResolvedAppScreen(RunnerAppScreenCapture.swift:70-86) runs the capture directly when already on main, and otherwise callsDispatchQueue.main.syncwith no bound. The iOS hop came in fix(ios): capture the display that hosts the app, not the main screen #2741 (6f4a9dc, v0.21.9). The macOS recording capture does the same atRunnerTests+Lifecycle.swift:67.RunnerTests+ScreenRecorder.swift:112-121, 15 fps by default perRunnerTests.swift:48) callscaptureFrame()synchronously on the recorder queue, and already skipsnilframes. While the main thread is wedged, the first tick blocks the recorder queue and later ticks pile up behind it. Busy and wedged accounting (runMainThreadWork,RunnerTests+MainThreadWork.swift:55-124, fix(ios): keep the runner alive through hostile snapshots #2621) never sees any of it.Required behavior
runMainThreadWorkwith a per-frame timeout no longer than the frame interval. Keep the existing on-main fast path for screenshot callers.nil). A late result from abandoned work is thrown away and never appended.stop()during a timed-out capture finishes. It does not append a late frame, and the next recording generation is unaffected.Completion conditions
Unit tests, using the existing
MainThreadWorktest seams:Also:
pnpm check:xctest-selection, build the runner, and run the host-lane unit tests (docs/agents/testing.md).record start/record stopof about 20 s produces a playable export with a sensible frame count, and a screenshot during and after recording still works. Record the commands in the PR.