Skip to content

Commit 284633e

Browse files
committed
ios: replace ad-hoc runner poll sleep with retry policy
1 parent 44d3d22 commit 284633e

1 file changed

Lines changed: 44 additions & 20 deletions

File tree

src/platforms/ios/runner-client.ts

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path from 'node:path';
44
import { fileURLToPath } from 'node:url';
55
import { AppError } from '../../utils/errors.ts';
66
import { runCmd, runCmdStreaming, runCmdBackground, type ExecResult, type ExecBackgroundResult } from '../../utils/exec.ts';
7-
import { withRetry } from '../../utils/retry.ts';
7+
import { Deadline, retryWithPolicy, withRetry } from '../../utils/retry.ts';
88
import type { DeviceInfo } from '../../utils/device.ts';
99
import net from 'node:net';
1010
import { bootFailureHint, classifyBootFailure } from '../boot-diagnostics.ts';
@@ -522,28 +522,52 @@ async function waitForRunner(
522522
timeoutMs: number = RUNNER_STARTUP_TIMEOUT_MS,
523523
): Promise<Response> {
524524
let endpoints = await resolveRunnerCommandEndpoints(device, port);
525-
let nextEndpointRefreshAt = Date.now() + 1_000;
526-
const start = Date.now();
527525
let lastError: unknown = null;
528-
while (Date.now() - start < timeoutMs) {
529-
if (device.kind === 'device' && Date.now() >= nextEndpointRefreshAt) {
530-
endpoints = await resolveRunnerCommandEndpoints(device, port);
531-
nextEndpointRefreshAt = Date.now() + 1_000;
532-
}
533-
for (const endpoint of endpoints) {
534-
try {
535-
const response = await fetchWithTimeout(endpoint, {
536-
method: 'POST',
537-
headers: { 'Content-Type': 'application/json' },
538-
body: JSON.stringify(command),
539-
}, 1_000);
540-
return response;
541-
} catch (err) {
542-
lastError = err;
543-
}
526+
const deadline = Deadline.fromTimeoutMs(timeoutMs);
527+
const maxAttempts = Math.max(1, Math.ceil(timeoutMs / 250));
528+
try {
529+
return await retryWithPolicy(
530+
async () => {
531+
if (device.kind === 'device') {
532+
endpoints = await resolveRunnerCommandEndpoints(device, port);
533+
}
534+
for (const endpoint of endpoints) {
535+
try {
536+
const response = await fetchWithTimeout(
537+
endpoint,
538+
{
539+
method: 'POST',
540+
headers: { 'Content-Type': 'application/json' },
541+
body: JSON.stringify(command),
542+
},
543+
1_000,
544+
);
545+
return response;
546+
} catch (err) {
547+
lastError = err;
548+
}
549+
}
550+
throw new AppError('COMMAND_FAILED', 'Runner endpoint probe failed', {
551+
port,
552+
endpoints,
553+
lastError: lastError ? String(lastError) : undefined,
554+
});
555+
},
556+
{
557+
maxAttempts,
558+
baseDelayMs: 100,
559+
maxDelayMs: 500,
560+
jitter: 0.2,
561+
shouldRetry: () => true,
562+
},
563+
{ deadline, phase: 'ios_runner_connect' },
564+
);
565+
} catch (error) {
566+
if (!lastError) {
567+
lastError = error;
544568
}
545-
await new Promise((resolve) => setTimeout(resolve, 100));
546569
}
570+
547571
if (device.kind === 'simulator') {
548572
const simResponse = await postCommandViaSimulator(device.id, port, command);
549573
return new Response(simResponse.body, { status: simResponse.status });

0 commit comments

Comments
 (0)