Record caller cancellation versus the owned deadline once and share that provenance through helper execution, scenario waits and finalization. Preserve independent failures and partial reports, consolidate scenario failure handling and dispose listeners on every exit. Fixes #5056
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { AnalysisCleanupLease } from "../artifacts/AnalysisCleanupLease.js";
|
|
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
|
import { mkdir, mkdtemp, rm, symlink } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { SWIFTC_INTERRUPT } from "../process/DarwinProcessRunTokenReader.js";
|
|
import { execFileOutput } from "../process/ExecFileOutput.js";
|
|
import { safeParseJson } from "../domain/safeJson.js";
|
|
import type { NativeUiHelper } from "./NativeUiObservation.js";
|
|
import { NATIVE_UI_HELPER_MAX_BUFFER } from "./NativeUiOutputBudget.js";
|
|
import { NATIVE_UI_DEADLINE_MS } from "./NativeUiInterruptionControl.js";
|
|
|
|
export interface NativeUiHelperRuntime {
|
|
readonly invoke: NativeUiHelper;
|
|
readonly cleanupLease: AnalysisCleanupLease;
|
|
readonly signal: AbortSignal;
|
|
close(): Promise<void>;
|
|
resources(): readonly string[];
|
|
}
|
|
|
|
/** Lazily compile one owned helper per observation/scenario and remove its compiler cache. */
|
|
export const createNativeUiHelperRuntime = (
|
|
environment: Readonly<NodeJS.ProcessEnv>,
|
|
): NativeUiHelperRuntime => {
|
|
const selectedEnvironment = snapshotEnvironment(environment);
|
|
const deadlineSignal = AbortSignal.timeout(NATIVE_UI_DEADLINE_MS);
|
|
const operations = new ArtifactResourceScope();
|
|
let root: string | undefined;
|
|
let preparation: Promise<string> | undefined;
|
|
const cleanupLease = new AnalysisCleanupLease(async () => {
|
|
await operations.close();
|
|
if (root !== undefined) {
|
|
await rm(root, { recursive: true, force: true });
|
|
root = undefined;
|
|
preparation = undefined;
|
|
}
|
|
});
|
|
const prepare = async (signal: AbortSignal): Promise<string> => {
|
|
root = await mkdtemp(join(tmpdir(), "rea-native-ui-"));
|
|
const output = join(root, "observer");
|
|
const main = join(root, "main.swift");
|
|
await symlink(
|
|
fileURLToPath(
|
|
new URL("../../bridge/native/ReaNativeUI.swift", import.meta.url),
|
|
),
|
|
main,
|
|
);
|
|
// Keep swiftc's intermediate objects inside the removable root.
|
|
const compilerTemporary = join(root, "tmp");
|
|
await mkdir(compilerTemporary);
|
|
await execFileOutput(
|
|
"/usr/bin/xcrun",
|
|
[
|
|
"swiftc",
|
|
"-module-cache-path",
|
|
join(root, "modules"),
|
|
fileURLToPath(
|
|
new URL(
|
|
"../../bridge/native/NativeUIChildren.swift",
|
|
import.meta.url,
|
|
),
|
|
),
|
|
main,
|
|
"-o",
|
|
output,
|
|
],
|
|
{
|
|
timeout: 60_000,
|
|
maxBuffer: 1024 * 1024,
|
|
stopSignal: SWIFTC_INTERRUPT,
|
|
env: { ...selectedEnvironment, TMPDIR: compilerTemporary },
|
|
signal,
|
|
},
|
|
);
|
|
return output;
|
|
};
|
|
const invoke: NativeUiHelper = (parameters, callerSignal) =>
|
|
operations.run(async () => {
|
|
const signal =
|
|
callerSignal === undefined
|
|
? deadlineSignal
|
|
: AbortSignal.any([callerSignal, deadlineSignal]);
|
|
preparation ??= prepare(signal);
|
|
const executable = await preparation;
|
|
const output = await execFileOutput(
|
|
executable,
|
|
[JSON.stringify(parameters)],
|
|
{
|
|
timeout: 30_000,
|
|
maxBuffer: NATIVE_UI_HELPER_MAX_BUFFER,
|
|
stopSignal: "SIGTERM",
|
|
env: selectedEnvironment,
|
|
signal,
|
|
},
|
|
);
|
|
const parsed = safeParseJson(output.stdout);
|
|
if (!parsed.ok)
|
|
throw new Error(
|
|
`Native helper returned invalid JSON: ${parsed.error}`,
|
|
{
|
|
cause: parsed.cause,
|
|
},
|
|
);
|
|
return parsed.value;
|
|
});
|
|
return {
|
|
invoke,
|
|
cleanupLease,
|
|
signal: deadlineSignal,
|
|
close: () => cleanupLease.release(),
|
|
resources: () => (root === undefined ? [] : [root]),
|
|
};
|
|
};
|