From 271276be81c4913aa98970b6843744a94ab542db Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:22:07 -0600 Subject: [PATCH 1/2] Make System.gc collect under the java-web command, which node starts without --expose-gc. --- src/node.ts | 11 +++++++++++ test/programs.test.ts | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/node.ts b/src/node.ts index 71f9c31..86d05c0 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,16 @@ 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; + setFlagsFromString("--expose-gc"); + g.gc = runInNewContext("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..436893b 100644 --- a/test/programs.test.ts +++ b/test/programs.test.ts @@ -56,6 +56,13 @@ for (const file of files) { }); } +// the java-web command, which node starts without --expose-gc +test("System.gc collects under the command", () => { + const ref = spawnSync(hotspot("java"), ["-cp", out, "Refs"], { encoding: "utf8" }); + 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(" ") : []; From f8cf6839ec2f867c4fb225bf5f197174d5955eb4 Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:30:58 -0600 Subject: [PATCH 2/2] Leave V8 flags alone when they are frozen or gc is renamed, and require Refs to run in the command test. --- src/node.ts | 6 +++++- test/programs.test.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/node.ts b/src/node.ts index 86d05c0..b182677 100644 --- a/src/node.ts +++ b/src/node.ts @@ -186,8 +186,12 @@ const bridges = new WeakMap(); 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"); - g.gc = runInNewContext("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 { diff --git a/test/programs.test.ts b/test/programs.test.ts index 436893b..4c848b7 100644 --- a/test/programs.test.ts +++ b/test/programs.test.ts @@ -57,8 +57,9 @@ for (const file of files) { } // the java-web command, which node starts without --expose-gc -test("System.gc collects under the command", () => { +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 }); });