diff --git a/src/node.ts b/src/node.ts index 71f9c31..b182677 100644 --- a/src/node.ts +++ b/src/node.ts @@ -4,6 +4,8 @@ import { isAssertionFlag, parseAssertions } from "./assertions.ts"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import { setFlagsFromString } from "node:v8"; +import { runInNewContext } from "node:vm"; import { fileURLToPath, pathToFileURL } from "node:url"; import { VFS } from "./fs.ts"; import type { Backend, Stat } from "./fs.ts"; @@ -180,7 +182,20 @@ function fixedInput(data: Uint8Array) { const bridges = new WeakMap(); // The host file system is the VFS root, with the JDK image mounted at /jdk. +// System.gc() calls globalThis.gc, which node defines only under --expose-gc +function exposeGc() { + const g = globalThis as { gc?: unknown }; + if (typeof g.gc === "function") return; + // V8 aborts on a flag change once flags are frozen + if (/(^|\s)--freeze-flags-after-init\b/.test([...process.execArgv, process.env.NODE_OPTIONS ?? ""].join(" "))) return; + setFlagsFromString("--expose-gc"); + // absent when --expose-gc-as gave it another name + const gc = runInNewContext("typeof gc === 'function' ? gc : undefined"); + if (gc) g.gc = gc; +} + function nodeVm(o: NodeRunOptions): JVM { + exposeGc(); const vfs = new VFS(); vfs.mount("/", new NodeBackend("/", true)); // built as dist/node.js, it reads the packaged image next to it diff --git a/test/programs.test.ts b/test/programs.test.ts index 448e6cc..4c848b7 100644 --- a/test/programs.test.ts +++ b/test/programs.test.ts @@ -56,6 +56,14 @@ for (const file of files) { }); } +// the java-web command, which node starts without --expose-gc +test("System.gc collects under the command", { skip: !files.includes("Refs.java") }, () => { + const ref = spawnSync(hotspot("java"), ["-cp", out, "Refs"], { encoding: "utf8" }); + assert.equal(ref.status, 0); + const got = spawnSync(process.execPath, [join(root, "src", "node.ts"), "-cp", out, "Refs"], { encoding: "utf8" }); + assert.deepEqual({ stdout: got.stdout, code: got.status }, { stdout: ref.stdout, code: ref.status }); +}); + function flags(file: string, tool: string): string[] { const m = new RegExp(`^// ${tool}: (.*)$`, "m").exec(readFileSync(join(src, file), "utf8")); return m ? m[1].split(" ") : [];