Skip to content

feat: keep the runner responsive under heavy capture load - #23

Open
Timo972 wants to merge 14 commits into
masterfrom
timo/dro-2713-wda-runner-reliability-under-capture-load
Open

feat: keep the runner responsive under heavy capture load#23
Timo972 wants to merge 14 commits into
masterfrom
timo/dro-2713-wda-runner-reliability-under-capture-load

Conversation

@Timo972

@Timo972 Timo972 commented Aug 20, 2026

Copy link
Copy Markdown

What

Three independent reliability improvements for the runner when screen capture is active:

  1. Bounded synthesis waits — the wait for a synthesized touch/typing event's acknowledgement now has a deadline derived from the event's own duration plus a margin (default 15 s, tunable via EVENT_SYNTHESIS_TIMEOUT_MARGIN). If the system sheds the event under load, that one request fails with a 5xx instead of blocking the agent forever.
  2. Responsive control endpoints — each HTTP connection now gets its own socket queue, and routes that never touch XCUI state (/status, the sessionless screencapture control endpoints, the unknown-route fallback) are served directly on the connection's queue. Automation routes keep their main-queue semantics and are additionally serialized through a funnel queue so at most one is in flight. A blocked automation command no longer takes down status probes or keyframe requests.
  3. Capture size capPOST /mobilerun/screencapture/start accepts an optional maxPixels argument (aspect-preserving downscale, 0 = uncapped). When omitted, devices with an A12 chip or older default to a 370k-pixel budget that is safe for sustained 60 fps encoding on that hardware class; newer devices are uncapped.

Why

Sustained high-resolution capture on older hardware can make the system drop synthesized HID events. Previously a single lost acknowledgement wedged the agent permanently: the wait had no timeout, every route was funneled through the main queue, and all connections shared one socket queue — so even /status stopped answering and only a relaunch recovered the agent.

Tests

  • New unit coverage: bounded spinner semantics, route-flag propagation, queue-placement/dispatch behavior (including control-route responsiveness while the main queue is blocked, and non-nesting of automation requests), capture clamp math and device-class mapping, config env override.
  • Full UnitTests bundle green locally (iOS sim); iOS and watchOS generic builds green locally; tvOS build and the integration suites run in CI.
  • Docs: the dispatch/queue/timeout model is documented in docs/request-dispatch.md; API docs updated in docs/mobilerun-actions.md and docs/mobilerun-screencapture.md.

🤖 Generated with Claude Code

Timo972 and others added 14 commits August 19, 2026 23:15
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tatus caches

Address two review findings on the HTTP layer split:

- Pre-warm FBSDKVersion() and FBTestmanagerdVersion() on the main thread during
  startServing, before the HTTP server starts accepting connections. Both cache
  their result behind a dispatch_once, and /status is now served off the
  automation queue, so the first /status request could otherwise resolve the
  testmanagerd daemon proxy from a connection queue.
- Unmark onControlQueue from the six session-required screencapture routes.
  Decorating a session-required route reads FBSession's static active-session
  state, which the automation queue writes without synchronization. Only the
  .withoutSession variants (already synchronization-safe) stay on the
  connection queue.
Whole-branch review found two Important cross-task defects in the runner-reliability-under-capture-load work:

- FBSession's _activeSession static was read off-main by every control-route
  response builder (FBResponsePayload) while session create/kill wrote it on
  the main queue, an ARC data race with a use-after-free window. All reads
  and writes now go through @synchronized (FBSession.class) accessors,
  scoped tightly around the bare static access so XCUI/teardown work in
  kill/markSessionActive: never runs under the lock.

- With per-connection queues, a second automation request could dispatch_sync
  onto the main queue while a first automation handler was spinning the run
  loop (FBRunLoopSpinner waiting on synthesis), and the nested run loop drain
  would execute the second handler reentrantly inside the first -- something
  the old shared connection queue serialized away. FBWebServer now funnels
  all non-control-route requests through a dedicated serial
  automation-funnel queue before hopping to main, restoring one-at-a-time
  semantics. Added FBWebServerDispatchTests.
  testAutomationRequestsDoNotNestInsideRunLoopSpin to pin the non-nesting
  behavior via a new /probe/spinning route.

Also folds in two docs corrections: mobilerun-screencapture.md notes that
only sessionless capture-control endpoints stay responsive during a blocked
automation command, and the design spec notes the same for the broadcast
status route plus the new automation funnel queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…et for min-clamped sizes

Move the FBSDKVersion()/FBTestmanagerdVersion() warm-up in FBWebServer startServing to after
keepAlive is set to YES and initialization is complete, so a /wda/shutdown that races the
bounded legacy-daemon handshake (which can spin the main run loop up to 30s) clears keepAlive
via stopServing instead of being negated by code that runs after it.

Fix fb_sizeForWidth:height:pixelBudget: in FBVideoStreamSession so the minimum-size clamp
(MAX(axis, 2)) can no longer push the product back over budget for skinny inputs (e.g.
10000x2 at budget 100 previously clamped to 1412px, 14x over budget). When the clamp pushes
one axis to the 2px floor, the other axis is now shrunk to fit; honoring the budget outranks
aspect ratio for extreme inputs. Budgets 1-3 can never be honored (2x2=4 is the minimum
encodable size) and are now rejected by handleStartScreenCapture:'s maxPixels validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… notes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant