perf(ios): derive runner demand for Simulator opens and stop observation from awaiting the runner - #2329
perf(ios): derive runner demand for Simulator opens and stop observation from awaiting the runner#2329thymikee wants to merge 14 commits into
Conversation
Size Report
Startup median (7 runs, lower is better):
|
|
Not ready at bc68c7e.
Please clarify the runner-retention claim: demand none skips a new prewarm but does not release an existing speculative runner. Either prove the promised release through the existing lifecycle owner, or narrow the claim and explicitly settle that acceptance requirement. |
Thermo-nuclear code quality reviewThis slice should not land in its current shape. The demand pipeline is the right idea, but the model still treats optimizations as requirements, misreads default Do not approve. Behavior on the happy observation batch is better; the implementation preserves incidental complexity and gets the policy model wrong in ways that will keep growing special cases. 1. Demand treats preferred operations as “must start a runner”
That fights ADR 0019: preferred is a measured fast path, not a requirement. The tests pin the wrong model ( That also makes the 80-row host table earn its keep: it classifies what a required operation actually executes, instead of laundering optimizations into prewarm. 2.
|
…ion from awaiting the runner Local Simulator opens now decide how much the XCTest runner is needed from the runtime operations declared by the steps still ahead in the same batch: an observation-only plan starts no runner, an unknown plan keeps the speculative prewarm without ever awaiting it, and a plan with an interaction prepares readiness for that step. open --relaunch no longer waits for runner readiness on a Simulator and resets the runner target only when a session is already alive. The Apple find ports report not-proven instead of starting a runner on a Simulator without a live session, so wait and read-only find observe through the canonical AX-bridge tree. Physical devices keep their lifecycle unchanged. The plan travels through the server-private internal request channel, never the wire; the Apple owner maps declared operations to a runner demand through a record complete over the runtime operation union. Refs #2198
…runtime fixtures The Apple and Android runtime fixtures carried identical audio-probe doubles; host-kit now owns the one copy and both fixtures import it. Also folds the two Apple native-find ports onto one admission helper and lifts the Simulator runner prewarm policy out of the open sequence, keeping both under the complexity gate.
The find ports and the relaunch target reset asked the local session registry whether a runner was alive, which misreads scripted and request-scoped runner providers as absent. Liveness is now a provider question: the local provider consults its session registry, a provider without startup cost counts as live, and an awaited prewarm proves liveness without asking.
…targets a bounded bridge grace A snapshot, diff, or find step now selects the runtime uses its structured input reaches, the way its handler does, so a plain snapshot no longer counts the custom-actions alternative and an observation-only batch resolves runner demand none. The descriptor declares the selector next to its alternatives; the daemon plan derivation honors it and keeps the union for every other command. Without the runner wait, the first snapshot after an open reached the AX bridge while the app was still becoming the primary foreground owner or registering its accessibility server, and the typed fallback then started the runner the plan had just avoided. A target younger than ten seconds is re-read for a bounded grace measured from the first such failure: five seconds for a missing AX server, one second for an ownership miss so a launch-time system dialog still reaches the fallback quickly. Established targets get no grace.
… answered A session record exists while xcodebuild is still connecting, so an alive child pid is not a runner that can answer. Treating it as live sent the relaunch target reset into a starting runner, queued behind its connection retries, and the failed reset invalidated the very session the prewarm was building. Liveness now also requires the session's readiness flag, which the first successful runner response sets.
… runner-session tests within the size ratchet A Simulator open schedules a best-effort runner prewarm that outlives its request. The replay test-runner suite opened a Simulator with the real Apple tools, so the prewarm's deferred import resolved after the file finished and spawned into whichever file the worker ran next, where the hermetic signal guard failed an unrelated test.
…steps the way their handlers do Runner demand now counts a command's required operations only: a preferred or conditional operation is a measured fast path the command succeeds without, so get, wait, and read-only find stay observation-only. The step selectors for snapshot, diff, and find live next to the registry and read the daemon step exactly as the handlers do: the daemon flag for custom actions, and find's positionals through the same parser, where a missing action is a click and an unparseable step keeps every declared alternative. The handler and the selector share one action-to-intent map. The batch runner hands each step its remaining steps in handler shape, and the derived operations reach the platform as a typed list on the lifecycle execution instead of an untyped plan on every open.
…nd make runner liveness explicit The snapshot route no longer infers a launch from process start text and retries inside its own capture. Open owns launch timing instead: a local Simulator open asks the AX bridge whether the launched app is observable, bounded by per-code windows measured from the first typed launch-transition failure and never extended, so an ownership miss seen after an AX-server miss shrinks the deadline to the ownership window and a launch-time system dialog still reaches the typed fallback quickly. Any other device, or a bridge that cannot answer, keeps the fixed settle. The open response reports what it learned. Every runner provider now states whether it can answer without a startup wait; a bare executor answers directly by construction and scripted providers say so. The runner prewarm policy and the observation settle move out of the open sequence into their own module, and the native find admission is named for what it admits.
…budget The enumeration and the no-public-flag rule live on the contract type that owns them; CONTEXT.md keeps the term itself, and two neighbouring entries lose words that carried no meaning.
…operations union The lifecycle execution carries the operations a plan requires, but typing that list with the operations union closed a 36-file type cycle: the operations types depend on the lifecycle types. The vocabulary now lives as a const list below both, proven equal to the union by a type test, so the plan is typed end to end, the Apple host table indexes it without casts, and the daemon narrows descriptor names through a guard instead of a cast.
Every Apple tool port loads the runner operations through the one memoized loader (#2314): a port that opens its own dynamic import can resolve the unmocked module while a test's mock factory is still loading and let a real local runner escape. The liveness port now uses the loader like its siblings; the facade members consumed only through the loader are declared to fallow, and the plan resolver reads one step per helper to stay under the complexity threshold.
The launch observation, the runner-free find admission, and the relaunch policy apply only where the host AX bridge exists: iOS Simulators. A tvOS Simulator keeps its awaited prewarm and asks for no observation, which the tvOS provider scenario now pins.
|
Addressed both reviews at Demand model
Launch timing
Retain/release
Types and seams
CI
Evidence
|
bc68c7e to
d654cc3
Compare
Summary
Slice A of #2198. A local iOS Simulator
opennow derives how much the XCTest runner is needed from the runtime operations declared by the steps still ahead of it in the samebatch, and observation stops waiting for runner readiness.runBatchhands every step its place in the plan through the server-privateinternalrequest channel (never the wire). The daemon flattens the remaining steps' declaredplatformExecutionuses;snapshot,diff, andfindselect their alternative from the step input the way their handlers do (selectUseson the descriptor). The Apple owner maps the operations through a record complete over the runtime-operation union. Observation-only plan →none(no runner started or retained). Unknown plan, including anopenthat ends its batch →possible(today's speculative prewarm, never awaited). Any runner-served operation →required(prewarm now; the first runner-dependent step awaits the same startup under the runner session lock with today's typed errors and deadlines).open --relaunchno longer awaits runner readiness on a Simulator. The runner re-resolves a new app PID at its next command, sotargetResetis sent only to a runner that is already alive and ready, or that an awaited prewarm just proved. Physical devices keep their relaunch lifecycle unchanged.waitand read-onlyfindobserve runner-free. The Apple native find ports reportfound: false("not proven here") instead of starting a runner on a Simulator without a ready session, so the poll consults the canonical AX-bridge tree (ADR 0019 §2). A ready runner keeps answering; provider-backed runners count as live through the runner provider seam.timinggainsrunnerDemandnext to the existing prewarm fields (additive; ADR 0006 untouched). Vocabulary added to CONTEXT.md.Also folds the two Apple native-find ports onto one admission helper, lifts the runner prewarm policy out of the open sequence (complexity gate), and shares one inert audio-probe fixture through
@agent-device/host-kit/audio-probe-fixturesinstead of two identical platform copies.Scope: 41 files, ~1,370 gross lines. Over the 1,000-line budget because the liveness seam, the plan derivation, and the Simulator policy only make sense together with the regressions that fell out of live validation; I can split it into a three-PR stack (liveness seam → plan derivation → Simulator policy) if preferred.
Proxy parity, lease isolation, cancellation, version skew and controlled-RTT measurements remain slice B of #2198.
Validation
Head:
bc68c7eabf; base:cf83afb9c9.none/possible/required; reset only on a live runner, physical-device relaunch unchanged; find ports gated by liveness; provider-scoped liveness; batch step context; descriptor selectors return only declared alternatives; launch grace re-reads young targets, is bounded, and never applies to established targets or non-launch failures.pnpm check:affected --runon the exact head passes every stage up tovitest-related; that stage then trips a pre-existing cross-file leak: the provider-scenario recording flow (ios-record-trace.test.ts) starts a local runner for its clock anchor (its runner call carries no request id, so the request-scoped scripted provider is bypassed by the pinned scope rule), and the runner's stale-xcodebuildpkillcleanup lands in whichever unit file the worker runs next, where the hermetic signal guard fails an unrelated Android open test. The same file set fails the same way on basecf83afb9c9(first run). The stage passes standalone at two workers (573 files, 4,342 tests), and every stage after it was run individually: provider-integration, integration-progress, replay-compat, daemon-wire-compat, affected-selector, gate-manifest (+model), depgraph, tmpdir-leaks (+model), coverage-model, wire-compat-model, production-exports, bundle-owner-files, fixture-cache, and fixture-fallback all pass; the fullcheck:unitrun passes 9,048 of 9,050 (1 skipped) with only that pre-existing leak failing. GitHub stays authoritative for the device lanes.cf83afb9c9built in a sibling worktree, runner killed before each run, two runs each. A =batch [open --relaunch, snapshot -i, wait text]on the dev-client home screen; B = lonebatch [open --relaunch]; C = standalonewait; D = firstclickafter the unawaited relaunch.none, no prewarm)possible, not awaited)none, no prewarm)possible, not awaited)The observation-only batch starts no runner on head (its
waitpolls the bridge tree); on base the same batch awaits a cold runner insideopen. Head's D pays the remaining runner start it deferred (1.7–3.8 s) where base's runner was already up; that is the intended trade. Sequential runs on one host, not a general speed guarantee. The #2189 cold/warm/relaunch corpus re-run and the first-runner-interaction cell are the acceptance evidence still to publish for #2198.