Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ extension RunnerTests {
RunnerSnapshotBridgeDrainForTest()
let admitted = RunnerSnapshotBridgeReadForTest(request, 1000, { ["ok": true] }, 16)
XCTAssertEqual(admitted["ok"] as? Bool, true)

let started = DispatchSemaphore(value: 0)
let secondRelease = DispatchSemaphore(value: 0)
let finished = expectation(description: "second read outlives its budget")
DispatchQueue.global().async {
let second = RunnerSnapshotBridgeReadForTest(request, 1000, {
started.signal()
secondRelease.wait()
return ["ok": true]
}, 100)
XCTAssertEqual(second["error_code"] as? String, "bridge-read-abandoned")
finished.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)
Comment thread
thymikee marked this conversation as resolved.
Resolved
secondRelease.signal()
RunnerSnapshotBridgeDrainForTest()
}

func testReaderDoesNotRefuseTheBoundsOfAFirstRead() throws {
Expand Down
2 changes: 1 addition & 1 deletion packages/daemon/src/sessions/handlers/lease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ async function heartbeatLease(
phase: 'lease_provider_ended_session_teardown_failed',
data: {
leaseId: lease.leaseId,
error: error instanceof Error ? error.message : String(error),
error: errorMessage(error),
},
});
}
Expand Down
18 changes: 12 additions & 6 deletions packages/platform-apple/src/runner-snapshot-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ type RunnerProcess = { pid: number; processStartTime: string };
* foreground process, and `describe` reads it unless the host planned another process, or answers
* the refusal `refuseDescribe` names. A describe given `holdNextDescribe` reads until that promise
* settles, and, like the runner, it keeps reading after the daemon stops waiting: a describe that
* arrives meanwhile is refused as `bridge-read-in-flight`. A describe that names surface hosts is
* refused as `system-surface-host-unverified`, as a sandbox that refuses the process table does.
* arrives meanwhile is refused as `bridge-read-in-flight`. With `refuseSurfaceHostCheck`, a describe that
* names surface hosts is refused as `system-surface-host-unverified`, as a sandbox that refuses the
* process table does.
*/
function bridgeRunner(state: {
process: RunnerProcess;
refuseDescribe?: string;
refuseTarget?: string;
refuseSurfaceHostCheck?: boolean;
holdNextDescribe?: Promise<void>;
}) {
const sent: RunnerCommand[] = [];
Expand Down Expand Up @@ -86,7 +88,7 @@ function bridgeRunner(state: {
) {
return refusal('stale_target', 'target-process-changed');
}
if (request.surfaceHostExecutables)
if (request.surfaceHostExecutables && state.refuseSurfaceHostCheck)
return refusal('unsupported', 'system-surface-host-unverified');
if (state.refuseDescribe) return refusal('application_not_responding', state.refuseDescribe);
if (reading) return refusal('application_not_responding', 'bridge-read-in-flight');
Expand Down Expand Up @@ -142,7 +144,7 @@ function verbs(sent: readonly RunnerCommand[]): string[] {
return sent.map((command) => command.snapshotBridge?.verb ?? command.command);
}

/** The bridge route of a runner whose foreground read proves no system surface covers the app. */
/** The bridge route of a runner with an optional foreground session. */
function bridgeRoute(provider: AppleRunnerProvider, runnerSessionId?: string) {
const bridge = createRunnerSnapshotBridge({ device: IOS_SIMULATOR, provider });
const presented: SnapshotRuntimeAcquiredResult[] = [];
Expand All @@ -165,7 +167,9 @@ function bridgeRoute(provider: AppleRunnerProvider, runnerSessionId?: string) {
...(runnerSessionId ? { sessionChanged: bridge.resetTargets } : {}),
},
async () =>
runnerSessionId ? { foreground: 'background', sessionId: runnerSessionId } : 'background',
runnerSessionId
? { foreground: 'background', sessionId: runnerSessionId }
: { foreground: 'unknown' },
);
const snapshot = (signal = new AbortController().signal) =>
route.capture(
Expand Down Expand Up @@ -427,6 +431,7 @@ test('the runner runtime reads a remote Simulator through the bridge once its fo

test('a runner whose foreground read cannot answer asks the bridge read to check the surface hosts', async () => {
const { provider, sent } = bridgeRunner({
refuseSurfaceHostCheck: true,
process: { pid: 41, processStartTime: '1700000000.000001' },
});
const { snapshot, fallbacks } = runtimeSnapshots(withHostStates(provider, undefined));
Expand Down Expand Up @@ -478,6 +483,7 @@ test('the foreground proof starts the runner when none is live, then reads only
test('a runner whose session id cannot be read answers the foreground proof as unknown', async () => {
registry.session = undefined;
const { provider, sent } = bridgeRunner({
refuseSurfaceHostCheck: true,
process: { pid: 41, processStartTime: '1700000000.000001' },
});
const { snapshot, fallbacks } = runtimeSnapshots(withHostStates(provider, 'notRunning'));
Expand Down Expand Up @@ -650,7 +656,7 @@ test('a wedge before the runner id is known does not disable a newly identified
targetFreshness: 'cached',
sessionChanged: bridge.resetTargets,
},
async () => (sessionId ? { foreground: 'background', sessionId } : 'background'),
async () => (sessionId ? { foreground: 'background', sessionId } : { foreground: 'unknown' }),
);
const snapshot = () =>
route.capture(
Expand Down
11 changes: 6 additions & 5 deletions packages/platform-apple/src/snapshot-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,12 @@ test.for([
async ({ runner, host, acquired, checked, served }) => {
const source = sourceReturning(host === 'present' ? surfaceHostPresent : bridgeAcquisition());
const fallback = vi.fn(async () => runnerResult());
const systemSurfaceForeground = vi.fn(async () =>
runner === 'foreground'
? ({ kind: 'foreground', host: presentSurface.host } as const)
: runner,
);
const systemSurfaceForeground = vi.fn(async () => ({
foreground:
runner === 'foreground'
? ({ kind: 'foreground', host: presentSurface.host } as const)
: runner,
}));
const route = createAppleSnapshotRoute(bridgePresentingHost(), {
source,
resolveTarget: vi.fn(async () => target),
Expand Down
17 changes: 3 additions & 14 deletions packages/platform-apple/src/snapshot-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,7 @@ function createSnapshotBridgeRouteCore<Target extends SnapshotBridgeRouteTarget>
probeResult: Awaited<ReturnType<SystemSurfaceForegroundProbe>>,
device: DeviceInfo,
): typeof currentCircuit => {
const probedSessionId =
channel.sessionChanged && typeof probeResult === 'object' && 'sessionId' in probeResult
? probeResult.sessionId
: undefined;
const probedSessionId = channel.sessionChanged ? probeResult.sessionId : undefined;
if (probedSessionId === undefined) return startingCircuit;
if (currentCircuit !== startingCircuit) {
return probedSessionId === currentCircuit.sessionId ? currentCircuit : startingCircuit;
Expand Down Expand Up @@ -264,10 +261,7 @@ function createSnapshotBridgeRouteCore<Target extends SnapshotBridgeRouteTarget>
const startingCircuit = currentCircuit;
const probeResult = await readSurfaceForeground(device, input, signal, surfaceForeground);
const circuit = circuitForProbe(startingCircuit, probeResult, device);
const foreground =
typeof probeResult === 'object' && 'sessionId' in probeResult
? probeResult.foreground
: probeResult;
const { foreground } = probeResult;
const captureBridgeDisabled = (target: SnapshotBridgeRouteTarget) =>
isDisabledIn(circuit, target);
if (foreground !== 'background' && foreground !== 'unknown') {
Expand Down Expand Up @@ -460,12 +454,7 @@ async function readSurfaceForeground(
): Promise<Awaited<ReturnType<SystemSurfaceForegroundProbe>>> {
const requestId = input.execution?.requestId;
const foreground = await probe(device, { signal, ...(requestId ? { requestId } : {}) });
if (
foreground === 'background' ||
(typeof foreground === 'object' &&
'sessionId' in foreground &&
foreground.foreground === 'background')
) {
if (foreground.foreground === 'background') {
emitDiagnostic({
level: 'debug',
phase: 'ios_snapshot_route_surface_host_background',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ test.for([
] as const)('%s is unknown and sends no command', async ([, liveness]) => {
runner.readRunnerSessionLiveness.mockReturnValue(liveness);

await expect(probe()).resolves.toBe('unknown');
await expect(probe()).resolves.toEqual({ foreground: 'unknown' });
expect(runner.runAppleRunnerCommand).not.toHaveBeenCalled();
});

Expand Down
6 changes: 2 additions & 4 deletions packages/platform-apple/src/system-surface-foreground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ export type SystemSurfaceForeground =
export type SystemSurfaceForegroundProbe = (
device: DeviceInfo,
input: Readonly<{ signal: AbortSignal; requestId?: string }>,
) => Promise<
SystemSurfaceForeground | Readonly<{ foreground: SystemSurfaceForeground; sessionId: string }>
>;
) => Promise<Readonly<{ foreground: SystemSurfaceForeground; sessionId?: string }>>;

/**
* Bounds each read below the read-only resend window, so a runner busy draining abandoned XCTest
Expand Down Expand Up @@ -65,7 +63,7 @@ export function createSystemSurfaceForegroundProbe(
): SystemSurfaceForegroundProbe {
return async (device, input) => {
const session = await readySession(device, input);
if (!session) return 'unknown';
if (!session) return { foreground: 'unknown' };
// The reads go out together: each one is a runner round trip, which costs a network hop to a
// remote Simulator. The answer reads them in registry order, so a decisive state settles it
// without waiting for the hosts after it.
Expand Down
Loading