// @effect-diagnostics globalTimers:off - scheduling for the standalone check:changed script. const GiB = 1024 ** 3; export const positiveInteger = (value: string | undefined) => { const number = Number(value); return value !== undefined && Number.isInteger(number) && number > 0 ? number : undefined; }; // A typecheck or test step can take several cores and gigabytes of memory, so // allow one step per two cores and per 6 GiB of available memory. export const defaultConcurrency = (cores: number, availableBytes: number) => Math.max(1, Math.min(Math.floor(cores / 2), Math.floor(availableBytes / (6 * GiB)))); // A task holds `weight` slots, capped at the limit, so a step that runs its own // workers counts each one. Tasks start in order: a heavy task waiting for room // is never overtaken by lighter ones behind it. export const createLimiter = (concurrency: number) => { let active = 0; const queue: Array<{ slots: number; start: () => void }> = []; const startQueued = () => { while (queue.length > 0 && active + queue[0]!.slots <= concurrency) { const next = queue.shift()!; active += next.slots; next.start(); } }; return async (task: () => Promise, weight = 1): Promise => { const slots = Math.min(Math.max(weight, 1), concurrency); if (queue.length === 0 && active + slots <= concurrency) active += slots; else await new Promise((start) => queue.push({ slots, start })); try { return await task(); } finally { active -= slots; startQueued(); } }; }; export type StepOutcome = "pass" | "FAIL" | "TIMEOUT"; interface Child { stdout: NodeJS.ReadableStream | null; stderr: NodeJS.ReadableStream | null; on(event: "error", listener: (error: Error) => void): unknown; on(event: "close", listener: (status: number | null) => void): unknown; } // Node fires a delay above its 32-bit maximum after 1 ms instead. const MAX_TIMER_MS = 2 ** 31 - 1; // Collects a step's output and settles when it exits. Past timeoutMs it calls // kill, which must end the whole process tree, and reports TIMEOUT whatever the // exit status. export const awaitStep = (child: Child, timeoutMs: number, kill: () => void) => new Promise<{ outcome: StepOutcome; output: Buffer }>((resolve) => { const output: Array = []; let timedOut = false; const timer = setTimeout( () => { timedOut = true; kill(); }, Math.min(timeoutMs, MAX_TIMER_MS), ); child.stdout?.on("data", (chunk: Buffer) => output.push(chunk)); child.stderr?.on("data", (chunk: Buffer) => output.push(chunk)); // A failed spawn emits "error", then "close" with a nonzero code. child.on("error", (error) => output.push(Buffer.from(`${error.message}\n`))); child.on("close", (status) => { clearTimeout(timer); resolve({ outcome: timedOut ? "TIMEOUT" : status === 0 ? "pass" : "FAIL", output: Buffer.concat(output), }); }); });