From f9869c44a7fe31c690bcd3c2cf3d5dbc42105a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 6 Nov 2026 13:57:33 +0100 Subject: [PATCH] refactor(apple): name the foreground reading type Export SystemSurfaceForegroundReading and use it in the probe signature and both route sites. The native wedge test block now says it pins the stale abandon stamp, and its expectation names the abandonment it waits for. Follow-up to #4267. --- .../UnitTests/RunnerTests+SnapshotBridgeTests.swift | 8 +++++--- packages/platform-apple/src/snapshot-route.ts | 5 +++-- packages/platform-apple/src/system-surface-foreground.ts | 8 +++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/UnitTests/RunnerTests+SnapshotBridgeTests.swift b/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/UnitTests/RunnerTests+SnapshotBridgeTests.swift index c4b99a1e7d..97d8aa9716 100644 --- a/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/UnitTests/RunnerTests+SnapshotBridgeTests.swift +++ b/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/UnitTests/RunnerTests+SnapshotBridgeTests.swift @@ -83,9 +83,11 @@ extension RunnerTests { let admitted = RunnerSnapshotBridgeReadForTest(request, 1000, { ["ok": true] }, 16) XCTAssertEqual(admitted["ok"] as? Bool, true) + // A read admitted after an abandoned one must not inherit the old abandon stamp (10): checked at + // 100, while this read is still inside its budget, a stale stamp would answer bridge-reader-wedged. let started = DispatchSemaphore(value: 0) let secondRelease = DispatchSemaphore(value: 0) - let finished = expectation(description: "second read outlives its budget") + let secondAbandoned = expectation(description: "the second read is abandoned at its budget") DispatchQueue.global().async { let second = RunnerSnapshotBridgeReadForTest(request, 1000, { started.signal() @@ -93,12 +95,12 @@ extension RunnerTests { return ["ok": true] }, 100) XCTAssertEqual(second["error_code"] as? String, "bridge-read-abandoned") - finished.fulfill() + secondAbandoned.fulfill() } started.wait() let secondBusy = RunnerSnapshotBridgeReadForTest(request, 100, { ["ok": true] }, 100) XCTAssertEqual(secondBusy["error_code"] as? String, "bridge-read-in-flight") - wait(for: [finished], timeout: 5) + wait(for: [secondAbandoned], timeout: 5) secondRelease.signal() RunnerSnapshotBridgeDrainForTest() } diff --git a/packages/platform-apple/src/snapshot-route.ts b/packages/platform-apple/src/snapshot-route.ts index 26529ab7d6..c15957224d 100644 --- a/packages/platform-apple/src/snapshot-route.ts +++ b/packages/platform-apple/src/snapshot-route.ts @@ -41,6 +41,7 @@ import { SnapshotSourceError } from './snapshot-source/errors.ts'; import { createSystemSurfaceForegroundProbe, type SystemSurfaceForegroundProbe, + type SystemSurfaceForegroundReading, } from './system-surface-foreground.ts'; import { bridgeFailureDisposition, @@ -208,7 +209,7 @@ function createSnapshotBridgeRouteCore */ const circuitForProbe = ( startingCircuit: typeof currentCircuit, - probeResult: Awaited>, + probeResult: SystemSurfaceForegroundReading, device: DeviceInfo, ): typeof currentCircuit => { const probedSessionId = channel.sessionChanged ? probeResult.sessionId : undefined; @@ -451,7 +452,7 @@ async function readSurfaceForeground( input: CaptureSnapshotInput, signal: AbortSignal, probe: SystemSurfaceForegroundProbe, -): Promise>> { +): Promise { const requestId = input.execution?.requestId; const foreground = await probe(device, { signal, ...(requestId ? { requestId } : {}) }); if (foreground.foreground === 'background') { diff --git a/packages/platform-apple/src/system-surface-foreground.ts b/packages/platform-apple/src/system-surface-foreground.ts index c0d8543b8b..d9bf0d79a5 100644 --- a/packages/platform-apple/src/system-surface-foreground.ts +++ b/packages/platform-apple/src/system-surface-foreground.ts @@ -23,10 +23,16 @@ export type SystemSurfaceForeground = | 'background' | 'unknown'; +/** One foreground read, with the runner session it was read through when that session is known. */ +export type SystemSurfaceForegroundReading = Readonly<{ + foreground: SystemSurfaceForeground; + sessionId?: string; +}>; + export type SystemSurfaceForegroundProbe = ( device: DeviceInfo, input: Readonly<{ signal: AbortSignal; requestId?: string }>, -) => Promise>; +) => Promise; /** * Bounds each read below the read-only resend window, so a runner busy draining abandoned XCTest