Files
scriptc/tests/corpus/native-worker-stack-overflow.ts
Chris Tate 3bcf761f42 Throw a catchable RangeError on deep recursion
Native executables used to terminate with a signal when recursion
exhausted the stack. Functions that can take part in unbounded recursion
now compare the stack pointer against a per-stack guard on entry and
throw Node's `RangeError: Maximum call stack size exceeded` through the
ordinary exception path, so catch handlers, finally blocks, worker error
events and the uncaught-error exit code behave as they do under Node.

- The checked set is every function on a cycle of the call graph, where
  operations that can reenter compiled code indirectly (indirect calls,
  callbacks, dynamic and library operations) reach closure targets and
  class members. Non-recursive functions, including leaf callbacks, stay
  check-free, and the may-throw analysis accounts for the new throw.
- The runtime installs the guard per context (main thread from the stack
  rlimit, workers from their thread attributes) and per async fiber, with
  a reserve for the throw, unwinding and error reporting. Worker threads
  now get an explicit 8 MiB stack.
- Scalar call results pass through the unwind path as its dummy value, so
  calls in tail position keep tail-call and recursion elimination.
2026-10-09 16:55:35 -05:00

36 lines
1.1 KiB
TypeScript

import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
function deep(n: number): number {
return n === 0 ? 0 : deep(n - 1) + 1;
}
function endless(n: number): number {
return endless(n + 1) + 1;
}
if (isMainThread) {
for (const mode of ["caught", "uncaught"]) {
await new Promise<void>((resolve) => {
const worker = new Worker(new URL(import.meta.url), { workerData: mode });
worker.on("message", (text: string) => console.log(mode, "message", text));
worker.on("error", (error: Error) =>
console.log(mode, "error", error.name, error.message, error instanceof RangeError),
);
worker.on("exit", (code: number) => {
console.log(mode, "exit", code);
resolve();
});
});
}
console.log("main", deep(3000));
} else if (workerData === "caught") {
try {
endless(0);
} catch (e) {
parentPort!.postMessage(`${e instanceof RangeError} ${(e as Error).message}`);
}
parentPort!.postMessage(`still running ${deep(3000)}`);
} else {
parentPort!.postMessage(`before ${deep(2000)}`);
endless(0);
}