Repository navigation
fix(ios-runner): bound recorder frame capture through runMainThreadWork #2801
Copy link
Copy link
Closed
Labels
Description
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 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 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.- The recorder's timer (
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
- When recording captures a frame from off the main thread, on both iOS and macOS, it goes through
runMainThreadWorkwith 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 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.
Reactions are currently unavailable
Activity
[-]decide: recorder and capture use unbounded main.sync outside busy accounting[/-][+]fix(ios-runner): bound recorder frame capture through runMainThreadWork[/+]fix(ios-runner): bound recorder frame capture through runMainThreadWork
docs(recording): note that the Apple runner drops frames it cannot ca…
fix(ios-runner): bound recorder frame capture through runMainThreadWork
docs(recording): note that the Apple runner drops frames it cannot ca…
fix(ios-runner): bound recorder frame capture through runMainThreadWork
docs(recording): note that the Apple runner drops frames it cannot ca…
fix(ios-runner): return work that finished at the timeout boundary an…