mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
fix(cli): prefer IPv4 DNS for spawned servers (#9209)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake).
This commit is contained in:
committed by
GitHub
parent
e317385ba8
commit
103bab99d5
@@ -16,7 +16,7 @@ import {
|
||||
resolveMaxOldSpaceMb,
|
||||
calibrateHeapFallbackMb,
|
||||
buildServerNodeOptions,
|
||||
buildNodeHeapArgs,
|
||||
buildNodeRuntimeArgs,
|
||||
} from "../../../scripts/build/runtime-env.mjs";
|
||||
import { resolveTlsOptions } from "../../../scripts/dev/tls-options.mjs";
|
||||
|
||||
@@ -269,7 +269,7 @@ function runDaemon(serverJs, env, memoryLimit, dashboardPort, apiPort) {
|
||||
// heap via NODE_OPTIONS (a CLI arg would shadow/override their value).
|
||||
const server = spawn(
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
[...(process.versions.bun ? [] : buildNodeHeapArgs(process.env, memoryLimit)), serverJs],
|
||||
process.versions.bun ? [serverJs] : buildNodeRuntimeArgs(process.env, memoryLimit, serverJs),
|
||||
{
|
||||
cwd: APP_DIR,
|
||||
env,
|
||||
@@ -289,7 +289,7 @@ function runWithoutRecovery(serverJs, env, memoryLimit, dashboardPort, apiPort,
|
||||
// heap via NODE_OPTIONS (a CLI arg would shadow/override their value).
|
||||
const server = spawn(
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
[...(process.versions.bun ? [] : buildNodeHeapArgs(process.env, memoryLimit)), serverJs],
|
||||
process.versions.bun ? [serverJs] : buildNodeRuntimeArgs(process.env, memoryLimit, serverJs),
|
||||
{
|
||||
cwd: APP_DIR,
|
||||
env,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
computeRestartDelayMs,
|
||||
waitUntilPortFree,
|
||||
} from "./supervisorPolicy.mjs";
|
||||
import { buildNodeHeapArgs } from "../../../scripts/build/runtime-env.mjs";
|
||||
import { buildNodeRuntimeArgs } from "../../../scripts/build/runtime-env.mjs";
|
||||
import { stopProcessGracefully } from "../../../src/shared/platform/windowsProcess.ts";
|
||||
import {
|
||||
isFatalInstrumentationHookFailure,
|
||||
@@ -47,7 +47,6 @@ export class ServerSupervisor {
|
||||
// #5238: skip the explicit CLI --max-old-space-size when the user pinned the
|
||||
// heap via NODE_OPTIONS (a CLI arg would shadow/override their value). The
|
||||
// calibrated heap is already carried by env.NODE_OPTIONS either way.
|
||||
const heapArgs = buildNodeHeapArgs(process.env, this.memoryLimit);
|
||||
// #6321: stdout used to be discarded (`"ignore"`) whenever `--log`/OMNIROUTE_SHOW_LOG
|
||||
// wasn't set (the default) — any debug/pino output written to stdout vanished
|
||||
// silently, so a boot that never becomes ready looked like a dead hang with zero
|
||||
@@ -55,7 +54,9 @@ export class ServerSupervisor {
|
||||
// stderr so a readiness timeout can surface what the child actually printed.
|
||||
this.child = spawn(
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
[...(process.versions.bun ? [] : heapArgs), this.serverPath],
|
||||
process.versions.bun
|
||||
? [this.serverPath]
|
||||
: buildNodeRuntimeArgs(process.env, this.memoryLimit, this.serverPath),
|
||||
{
|
||||
cwd: dirname(this.serverPath),
|
||||
env: this.env,
|
||||
|
||||
1
changelog.d/fixes/9209-cli-ipv4-first-dns.md
Normal file
1
changelog.d/fixes/9209-cli-ipv4-first-dns.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(cli):** prefer IPv4 DNS for spawned Node servers. (thanks @dsitmilis)
|
||||
@@ -86,6 +86,20 @@ export function buildNodeHeapArgs(env = process.env, memoryLimit) {
|
||||
return envHasExplicitHeapFlag(env) ? [] : [`${MAX_OLD_SPACE_FLAG}=${memoryLimit}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the complete argument list for spawning the Node.js server runtime.
|
||||
* Prefer IPv4 DNS results before starting the application so undici does not
|
||||
* stall on hosts whose IPv6 route silently drops outbound connections.
|
||||
*
|
||||
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
||||
* @param {number} memoryLimit — calibrated V8 heap ceiling (MB)
|
||||
* @param {string} serverPath — standalone server entrypoint
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function buildNodeRuntimeArgs(env = process.env, memoryLimit, serverPath) {
|
||||
return ["--dns-result-order=ipv4first", ...buildNodeHeapArgs(env, memoryLimit), serverPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [fromEnv]
|
||||
* Defaults to process.env. Pass bootstrap `merged` so project `.env` PORT applies before spawn.
|
||||
|
||||
81
tests/unit/cli-ipv4-first-dns-2699.test.ts
Normal file
81
tests/unit/cli-ipv4-first-dns-2699.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { createRequire, syncBuiltinESMExports } from "node:module";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { buildNodeRuntimeArgs } from "../../scripts/build/runtime-env.mjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const childProcess = require("node:child_process");
|
||||
const originalSpawn = childProcess.spawn;
|
||||
|
||||
test.afterEach(() => {
|
||||
childProcess.spawn = originalSpawn;
|
||||
syncBuiltinESMExports();
|
||||
});
|
||||
|
||||
test("Node server runtime prefers IPv4 DNS before the server entrypoint", () => {
|
||||
assert.deepEqual(buildNodeRuntimeArgs({}, 2048, "/app/server.js"), [
|
||||
"--dns-result-order=ipv4first",
|
||||
"--max-old-space-size=2048",
|
||||
"/app/server.js",
|
||||
]);
|
||||
});
|
||||
|
||||
test("Node server runtime preserves an explicit heap setting while preferring IPv4", () => {
|
||||
assert.deepEqual(
|
||||
buildNodeRuntimeArgs(
|
||||
{ NODE_OPTIONS: "--enable-source-maps --max-old-space-size=8192" },
|
||||
512,
|
||||
"/app/server.js"
|
||||
),
|
||||
["--dns-result-order=ipv4first", "/app/server.js"]
|
||||
);
|
||||
});
|
||||
|
||||
test("ServerSupervisor starts Node with IPv4-first DNS", async () => {
|
||||
const spawnCalls: Array<{ command: string; args: string[] }> = [];
|
||||
const child = Object.assign(new EventEmitter(), {
|
||||
pid: 2699,
|
||||
stdout: new EventEmitter(),
|
||||
stderr: new EventEmitter(),
|
||||
});
|
||||
childProcess.spawn = (command: string, args: string[]) => {
|
||||
spawnCalls.push({ command, args });
|
||||
return child;
|
||||
};
|
||||
syncBuiltinESMExports();
|
||||
|
||||
const dataDir = mkdtempSync(join(tmpdir(), "omniroute-ipv4-first-"));
|
||||
const previousDataDir = process.env.DATA_DIR;
|
||||
process.env.DATA_DIR = dataDir;
|
||||
|
||||
try {
|
||||
const moduleUrl = pathToFileURL(
|
||||
join(process.cwd(), "bin/cli/runtime/processSupervisor.mjs")
|
||||
).href;
|
||||
const { ServerSupervisor } = await import(`${moduleUrl}?ipv4-first=${Date.now()}`);
|
||||
const supervisor = new ServerSupervisor({
|
||||
serverPath: "/app/server.js",
|
||||
env: {},
|
||||
memoryLimit: 2048,
|
||||
});
|
||||
|
||||
supervisor.start();
|
||||
|
||||
assert.deepEqual(spawnCalls, [
|
||||
{
|
||||
command: "node",
|
||||
args: ["--dns-result-order=ipv4first", "--max-old-space-size=2048", "/app/server.js"],
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
if (previousDataDir === undefined) delete process.env.DATA_DIR;
|
||||
else process.env.DATA_DIR = previousDataDir;
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user