mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
bbd53d6c79
* fix(ios): budget cold toolchain probes for the first-exec signature stall xcodebuild/xcrun toolchain probes in cache-identity.ts and runner-cache-metadata.ts were budgeted for a warm toolchain (10s/5s), below the ~18-19s syspolicyd signature-verification stall on the first exec after a fresh macOS host boots. Share one 30s floor constant between both call sites and retry once after a timeout while the deadline allows, since the second exec is instant. Closes #2422 * fix(apple): duplicate the cold-toolchain-probe budget instead of a shared module toolchain-probe-budget.ts sat outside every platform-apple facade's eager closure, but runner-cache-metadata.ts (imported from it) sits inside all seven -- so the new import added one module to each, tripping the eager-closure-budgets no-growth gate (#2422). Delete the shared module. cache-identity.ts keeps the canonical constant inline (it was already outside the gated closures); runner-cache-metadata.ts declares its own copy, guarded by a new unit test that asserts the two stay equal. * fix(apple): bound the cold toolchain probes by the owning request budget Three synchronous probes could each retry once at 30 s, so a wedged toolchain host blocked a request for ~180 s with no deadline and no cancellation check. The runner cache decision now takes the owning request's budget (remaining ms + abort signal) and builds one clock per fingerprint read: every attempt runs at min(per-call ceiling, remaining), the retry is skipped once the budget is spent, an exhausted budget fails the decision without starting another probe, and an aborted signal surfaces the cancellation instead of retrying. `ensureXctestrunArtifact` passes the build budget and signal, session reuse passes the startup budget and the request signal, and lease adoption passes the startup budget; a caller with neither is still capped at 45 s total, so the worst case falls from ~180 s to 45 s. Error codes, texts, and the probe hint are unchanged. Both retry classifiers now read the exec layer's structured timeout detail instead of matching "timed out after Nms" in the message. The predicate is exported once from host-kit's command surface and reaches `runner-cache-metadata.ts` through the Apple runner host port, so the file's eager closure is unchanged. Tests use a fake clock that only advances when a probe actually blocks for the timeout it was given, so the exhausted-budget and cancellation cases have to spend the budget to pass; both consumers also pin that an error saying "timed out after 10ms" without the structured detail is not retried. Refs #2422 * fix(apple): spend one deadline across the toolchain probes and the step they precede The Apple runner cache decision runs up to three blocking toolchain probes before the step that needs the decision. Those probes were handed the phase's timeout and the step was then handed the same number again, so a cold-start probe stall added its 30 to 45 seconds on top of the phase budget instead of coming out of it. RunnerCacheProbeBudget now carries the phase's deadline rather than a timeout number, and each caller creates exactly one: - ensureXctestrunArtifact: the probes and xcodebuild read the same clock, and a phase with nothing left fails before the spawn. - ensureRunnerSession: the reuse probe spends from the startup clock, and the new session gets what it left. - tryAdoptRunnerSessionFromLease: the fingerprint probe spends from the caller's clock, and the adopted session gets the remainder. COLD_TOOLCHAIN_PROBE_TIMEOUT_MS now has one owner, core/config.ts. Snapshot source imports it; runner-cache-metadata reads it through the Apple runner host port, because core/config.ts is missing from one of the seven eager closures that evaluate that file and a direct import would grow it. createSnapshotSourceDeadline takes an injectable clock so a test can prove that a probe which blocked for its whole timeout leaves the retry only the remainder. * fix(apple): keep cancellation typed after a probe timeout and simplify the probe budget * fix(apple): check cancellation before the warm toolchain fingerprint cache * refactor(apple): guard each toolchain probe attempt in one place Fold the toolchain probe's three duplicated cancellation/budget guard sites (runToolchainProbe's pre-check, runToolchainProbeCommand's retry pre-check, and execToolchainProbeCommand's timeout computation) into one: attemptToolchainProbe checks cancellation and the remaining budget before every exec, first attempt and retry alike. The outer runToolchainProbe now rethrows cancellation and a spent budget instead of swallowing them into a probe failure, and only genuine probe errors become one. * fix(apple): keep cancellation typed when the final toolchain probe fails The guard fold left one gap: a request that aborts while the last probe is in flight and then fails with a non-timeout error has no next attempt whose guard could see the abort, so the catch classified it as an unreadable toolchain. The catch checks the signal again before classifying, as it did before the fold. * refactor(apple): own the toolchain probe budget in platform-apple and trim narration COLD_TOOLCHAIN_PROBE_TIMEOUT_MS moves from @agent-device/host-kit/command to runner/apple-runner-platform.ts, beside the SDK names the probes are run against. Both Apple toolchain probers import it directly, so the runner host port no longer carries a coldToolchainProbeTimeoutMs() accessor for a plain number. isCommandTimeoutError stays in host-kit, where the exec layer stamps the detail it reads. The comments that narrated control flow the code already shows are gone; the cold-start stall rationale (on the constant), the spawnSync cancellation limitation (on the probe clock) and one line per phase-deadline creation site remain.