fix(runner): bound Rosetta interpreter build probes at 30 seconds

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-29 16:05:33 -05:00
1 parent d3077a061f
commit bd44f41e9e
4 files changed
+36 -4

No files matched your search

@@ -0,0 +1,6 @@
/** Build-time probes only; provider/session admission deadlines are unchanged. */
export function buildNodeStartupTimeout(platform = process.platform, architecture = process.arch) {
// A freshly copied x64 Node can spend more than 10s in Rosetta's first launch.
// Keep this bounded and target-specific instead of weakening other probes.
return platform === "darwin" && architecture === "x64" ? 30_000 : 10_000;
}
@@ -0,0 +1,24 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { buildNodeStartupTimeout } from "./build-node-startup-timeout.mjs";
test("cold Rosetta Node startup has a bounded build-only allowance", () => {
assert.equal(buildNodeStartupTimeout("darwin", "x64"), 30_000);
for (const [platform, architecture] of [["darwin", "arm64"], ["linux", "x64"], ["linux", "arm64"], ["win32", "x64"]]) {
assert.equal(buildNodeStartupTimeout(platform, architecture), 10_000);
}
assert.equal(buildNodeStartupTimeout(), buildNodeStartupTimeout(process.platform, process.arch));
});
test("both pack and private Pi Node probes use the allowance without changing other operation deadlines", () => {
const pack = readFileSync(new URL("./build-provider-pack.mjs", import.meta.url), "utf8");
const pi = readFileSync(new URL("./materialize-pi-distribution.mjs", import.meta.url), "utf8");
assert.match(pack, /spawnSync\(stableNodeCommand, \["--version"\], \{[^}]*env: \{[^}]*\}[^}]*timeout: buildNodeStartupTimeout\(\)/);
for (const probe of ['run(node, ["--version"]', 'run(node, ["-p", "process.versions.undici"]', 'run(copiedNode, ["--version"]']) {
const line = pi.split("\n").find(line => line.includes(probe));
assert.ok(line?.includes("timeout: buildNodeStartupTimeout()"), probe);
}
assert.match(pi, /run\("git", \["apply", "--check", patchPath\], \{[^}]*timeout: 10_000/);
assert.match(pi, /run\("\/usr\/bin\/otool", \["-L", copiedNode\], \{ env: \{\}, timeout: 10_000/);
});
@@ -19,6 +19,7 @@ import { dirname, join, relative, resolve } from "node:path";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { parseProviderPackArguments, materializeCandidateProviderPack } from "./candidate-provider-pack.mjs";
import { buildNodeStartupTimeout } from "./build-node-startup-timeout.mjs";
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const workspaceRoot = resolve(packageRoot, "../..");
@@ -165,7 +166,7 @@ try {
copyFileSync(process.execPath, stableNodeCommand);
chmodSync(stableNodeCommand, 0o755);
const relocatedNode = spawnSync(stableNodeCommand, ["--version"], {
cwd: temporaryRoot, env: { PATH: "/usr/bin:/bin" }, encoding: "utf8", timeout: 10_000,
cwd: temporaryRoot, env: { PATH: "/usr/bin:/bin" }, encoding: "utf8", timeout: buildNodeStartupTimeout(),
});
if (relocatedNode.status !== 0 || relocatedNode.stdout.trim() !== `v${process.versions.node}`) {
throw new Error("Provider pack Node is not portable after relocation; build with a standalone Node distribution");
@@ -8,6 +8,7 @@ import { promisify } from "node:util";
import { gunzipSync } from "node:zlib";
import { PI_DISTRIBUTION_CLOSURE_SHA256 } from "../src/drivers/acpx/pi-closure-pins.ts";
import { PI_NODE_DISTRIBUTIONS, PI_NODE_VERSION } from "../src/drivers/acpx/pi-node-pins.ts";
import { buildNodeStartupTimeout } from "./build-node-startup-timeout.mjs";
import { QUALIFIED_ACPX_PROFILES } from "../src/drivers/acpx/qualified-profiles.ts";
import { inventoryPiRuntimeFiles, verifyPiRuntimeManifest } from "../src/drivers/acpx/pi-verified-runtime.ts";
@@ -208,9 +209,9 @@ export async function materializePiDistribution({ outputRoot, nodeExecutable, np
node = join(nodeRoot, "node");
}
if (hash(await readFile(node)) !== nodePin.executableSha256 || (await lstat(node)).size !== nodePin.executableSize) throw new Error("Pi Node executable does not match its target release pin");
const version = (await run(node, ["--version"], { env: {}, timeout: 10_000 })).stdout.trim();
const version = (await run(node, ["--version"], { env: {}, timeout: buildNodeStartupTimeout() })).stdout.trim();
if (version !== `v${PI_NODE_VERSION}`) throw new Error("Pi distribution requires exact pinned Node version");
if ((await run(node, ["-p", "process.versions.undici"], { env: {}, timeout: 10_000 })).stdout.trim() !== PI_DISTRIBUTION_PINS.nodeBundledUndici) throw new Error("Pi Node bundled Undici differs from its reviewed security pin");
if ((await run(node, ["-p", "process.versions.undici"], { env: {}, timeout: buildNodeStartupTimeout() })).stdout.trim() !== PI_DISTRIBUTION_PINS.nodeBundledUndici) throw new Error("Pi Node bundled Undici differs from its reviewed security pin");
await mkdir(runtimeRoot);
await Promise.all(["package.json", "package-lock.json"].map((name) => copyFile(join(lockDirectory, name), join(runtimeRoot, name))));
await writeFile(join(runtimeRoot, ".npmrc"), "registry=https://registry.npmjs.org/\nignore-scripts=true\naudit=false\nfund=false\n");
@@ -244,7 +245,7 @@ export async function materializePiDistribution({ outputRoot, nodeExecutable, np
? (await run("/usr/bin/otool", ["-L", copiedNode], { env: {}, timeout: 10_000 })).stdout
: (await run("ldd", [copiedNode], { env: { PATH: "/usr/bin:/bin" }, timeout: 10_000 })).stdout;
assertPiNodeSystemDependencies(dependencyListing, process.platform);
if ((await run(copiedNode, ["--version"], { env: {}, timeout: 10_000 })).stdout.trim() !== version) throw new Error("Copied Pi Node cannot execute after relocation");
if ((await run(copiedNode, ["--version"], { env: {}, timeout: buildNodeStartupTimeout() })).stdout.trim() !== version) throw new Error("Copied Pi Node cannot execute after relocation");
await rm(buildHome, { recursive: true });
// npm-generated .bin links and hidden lock metadata are not package payload
// files. No launch uses PATH; excluding them makes the closure regular-only.