Files
OmniRoute/scripts/build/runtime-env.mjs
diegosouzapw 4b6d6c7670 fix(docker): honor OMNIROUTE_MEMORY_MB heap limit in standalone launcher (#2939)
The Docker image bakes NODE_OPTIONS=--max-old-space-size=256, and the standalone
launcher (scripts/dev/run-standalone.mjs, the Docker CMD) spawned 'node
server.js' without overriding it — so the server inherited the 256 MB cap and
OOMed randomly under load or with a large SQLite DB. `omniroute serve` already
honored OMNIROUTE_MEMORY_MB but the Docker path did not.

Add a shared resolveMaxOldSpaceMb() helper (OMNIROUTE_MEMORY_MB, default 512,
clamped [64,16384]) and have the launcher append --max-old-space-size to the
child NODE_OPTIONS (a trailing flag wins, overriding the baked 256 without
clobbering other flags). Update the .env.example doc to reflect the 512 default.
2026-05-31 09:15:55 -03:00

74 lines
2.4 KiB
JavaScript

import { spawn } from "node:child_process";
export function parsePort(value, fallback) {
const parsed = Number.parseInt(String(value), 10);
return Number.isFinite(parsed) && parsed > 0 && parsed <= 65535 ? parsed : fallback;
}
/**
* Resolve the V8 heap ceiling (MB) for the server process from
* `OMNIROUTE_MEMORY_MB`, mirroring `omniroute serve`. Clamped to [64, 16384];
* invalid/unset → fallback (512). The Docker image bakes
* NODE_OPTIONS=--max-old-space-size=256, which OOMs under load / large DBs
* (#2939) — the standalone launcher uses this to override that cap.
* @param {string | number | undefined | null} value
* @param {number} [fallback]
*/
export function resolveMaxOldSpaceMb(value, fallback = 512) {
const parsed = Number.parseInt(String(value), 10);
return Number.isFinite(parsed) && parsed >= 64 && parsed <= 16384 ? parsed : fallback;
}
/**
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [fromEnv]
* Defaults to process.env. Pass bootstrap `merged` so project `.env` PORT applies before spawn.
*/
export function resolveRuntimePorts(fromEnv = process.env) {
const basePort = parsePort(fromEnv.PORT || "20128", 20128);
const apiPort = parsePort(fromEnv.API_PORT || String(basePort), basePort);
const dashboardPort = parsePort(fromEnv.DASHBOARD_PORT || String(basePort), basePort);
return { basePort, apiPort, dashboardPort };
}
export function withRuntimePortEnv(env, runtimePorts) {
const { basePort, apiPort, dashboardPort } = runtimePorts;
return {
...env,
OMNIROUTE_PORT: String(basePort),
PORT: String(dashboardPort),
DASHBOARD_PORT: String(dashboardPort),
API_PORT: String(apiPort),
};
}
export function sanitizeColorEnv(env = {}) {
const sanitized = { ...env };
// Node warns when both FORCE_COLOR and NO_COLOR are set.
// Prefer NO_COLOR in test tooling to avoid noisy process warnings.
if (typeof sanitized.FORCE_COLOR !== "undefined" && typeof sanitized.NO_COLOR !== "undefined") {
delete sanitized.FORCE_COLOR;
}
return sanitized;
}
export function spawnWithForwardedSignals(command, args, options = {}) {
const child = spawn(command, args, options);
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 0);
});
process.on("SIGINT", () => child.kill("SIGINT"));
process.on("SIGTERM", () => child.kill("SIGTERM"));
return child;
}