Skip to content

fix(ios-runner): bound recorder frame capture through runMainThreadWork #2801

Description

@thymikee

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 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions