Repository navigation
fix(ios-runner): snapshot capture plan reads and writes main-owned target state off the main thread #2781
Description
Purpose
The runner builds snapshots off the main thread, on the private serial commandExecutionQueue (RunnerTests.swift:52). The capture plan also reads and writes runner state that main-thread lifecycle code owns. These are plain vars with no lock. The target builds in Swift 5 mode without strict concurrency, so the compiler does not report the race.
Line references are at 01328de411. Paths are under apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/.
Off-main writes:
RunnerTests+Snapshot.swift:504-507(snapshotAccessibilityUnavailable): setsrunnerAccessibilityHealth = .unavailableand callsinvalidateCachedTargetdirectly. fix(ios): keep the runner alive through hostile snapshots #2621 addedinvalidateCachedTargetAfterSnapshotFailure(RunnerTests+CommandExecution.swift:1356-1381) so this work would run on main. It checkshasAbandonedMainThreadWork()first. This call site was not switched to it.RunnerTests+SnapshotCapturePlan.swift:638: writesrunnerAccessibilityHealth.RunnerTests+SnapshotCapturePlan.swift:146-147: consumessnapshotXCTestPenaltyWarmupExemptionPending.
Off-main reads of main-owned target identity:
RunnerTests+SnapshotCapturePlan.swift:284RunnerTests+AXSnapshotFallback.swift:123-124,180-181,270(currentBundleId,currentAppProcessIdentifier,currentApp)
Main-thread writers of the same state: RunnerTests+Lifecycle.swift:170-214 and RunnerTests+CommandExecution.swift:1131/1174/1183.
The race is most likely in the fail-closed case, where abandoned main-thread work is still running.
Required behavior
- Snapshot preparation, which runs on main, reads the target identity once and captures it as
letvalues: bundle id, pid, and the app reference. The off-main plan gets those values as parameters and does not read thevars. - Every write to
runnerAccessibilityHealthand every target invalidation from the off-main plan goes through the existing main hop, using the same abandoned-work guard asinvalidateCachedTargetAfterSnapshotFailure. Do not add a second lock around these fields. - The warm-up exemption flag is consumed on main during preparation, or it is moved under the lock that already protects the penalty and depth fields. Pick one and document the owner in the type.
Completion conditions
grepfinds no read or write ofcurrentBundleId,currentApp,currentAppProcessIdentifier,runnerAccessibilityHealthorsnapshotXCTestPenaltyWarmupExemptionPendingin code that runs oncommandExecutionQueue, other than through the hop. List the audited call sites in the PR.- A unit test drives
snapshotAccessibilityUnavailableand asserts that invalidation is deferred through the hop. Model it on the existing fix(ios): keep the runner alive through hostile snapshots #2621 test. - Run
pnpm check:xctest-selection, build the runner, and run the runner unit tests on the macOS host lane (seedocs/agents/testing.mdfor signing and permissions). Add or change tests under the existing#if AGENT_DEVICE_RUNNER_UNIT_TESTSguard. Do not keep a second list of test names. - The iOS simulator smoke lane is green.
Non-goals
Turning on -strict-concurrency=complete for the whole target. That is a separate, larger task, so file a follow-up with the warning count. No behavior change to the capture plan tiers or budgets.
Activity
fix(ios): take snapshot target identity on main and hop plan state wr…
fix(ios): take snapshot target identity on main and hop plan state wr…
test(ios): pin the query-sweep tier timeout and the tap routing ident…
fix(ios): take snapshot target identity on main and hop plan state wr…
test(ios): pin the query-sweep tier timeout and the tap routing ident…
fix(ios-runner): keep snapshot-plan target state on main and bound th…