mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
91 lines
4.4 KiB
TypeScript
91 lines
4.4 KiB
TypeScript
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
// The Docker publish workflow builds on GitHub-hosted runners (ubuntu-24.04 and
|
||
// ubuntu-24.04-arm): 4 vCPU, 16 GB RAM. Every Next page-data worker AND the
|
||
// parent `next build` process are separate OS processes, so the budget has to
|
||
// cover all of them, not just the workers.
|
||
//
|
||
// With 7 workers × 6144 MB the runner ran out and buildkit failed the step with
|
||
// `ResourceExhausted: ... cannot allocate memory`, right after "Collecting page
|
||
// data using 7 workers" — every Docker publish since 2026-08-22 23:14 UTC.
|
||
// Lowering to 2 workers (#10060 / PR #11419) was not enough: it modeled the
|
||
// per-process peak as an INFERENCE (`WORKER_PEAK_MB = 2560`, derived only from
|
||
// "7 workers didn't fit") and assumed the parent process tracked the V8 heap
|
||
// ceiling (`OMNIROUTE_BUILD_MEMORY_MB`) rather than its own RSS. The owner's
|
||
// live VPS reproduction (issue #7518, dmesg OOM-killer report, 2026-08-24)
|
||
// measured the real number directly: `next-build (v16) ... anon-rss:4522744kB`
|
||
// (~4.5 GB) per process, independent of the NODE_OPTIONS heap flag — Turbopack
|
||
// itself is native/Rust and compiles outside the V8 heap. With 2 workers that
|
||
// keeps the publish pipeline failing at "Collecting page data using 2 workers"
|
||
// (run 32907937950, 2026-08-25).
|
||
//
|
||
// This pins the budget on the MEASURED figure, applied uniformly to every
|
||
// process (parent + workers), so raising the worker count has to be a
|
||
// deliberate change that re-does the arithmetic, not a one-line bump that
|
||
// silently reds the publish pipeline again.
|
||
|
||
const RUNNER_MEMORY_MB = 16 * 1024;
|
||
// Leave room for buildkit, the snapshotter and page cache.
|
||
const HEADROOM_FRACTION = 0.75;
|
||
// Measured (not inferred) peak RSS for a single Next/Turbopack build process —
|
||
// parent or page-data worker alike — from the dmesg OOM-killer report above.
|
||
// If a future build OOMs again, re-measure via dmesg before raising this
|
||
// number — do not weaken the budget with another guess.
|
||
const MEASURED_PROCESS_RSS_MB = 4500;
|
||
|
||
const dockerfile = readFileSync(
|
||
fileURLToPath(new URL("../../Dockerfile", import.meta.url)),
|
||
"utf8"
|
||
);
|
||
|
||
function readArgDefault(name: string): number {
|
||
const match = dockerfile.match(new RegExp(`^ARG ${name}=(\\d+)$`, "m"));
|
||
assert.ok(match, `Dockerfile no longer declares ARG ${name}`);
|
||
return Number(match![1]);
|
||
}
|
||
|
||
test("the Docker build's worker pool is derived from OMNIROUTE_BUILD_WORKERS", () => {
|
||
// assert.ok(boolean), not assert.match — a failing assert.match dumps the
|
||
// whole Dockerfile into the report.
|
||
assert.ok(
|
||
/^ENV CIRCLE_NODE_TOTAL=\$\{OMNIROUTE_BUILD_WORKERS\}$/m.test(dockerfile),
|
||
"CIRCLE_NODE_TOTAL must stay wired to the build arg so a big builder can raise it"
|
||
);
|
||
assert.ok(
|
||
/^ENV NODE_OPTIONS="--max-old-space-size=\$\{OMNIROUTE_BUILD_MEMORY_MB\}"$/m.test(dockerfile),
|
||
"the build heap ceiling must stay wired to OMNIROUTE_BUILD_MEMORY_MB"
|
||
);
|
||
});
|
||
|
||
test("worker count × measured per-process RSS fits a 16 GB GitHub runner", () => {
|
||
const workerPool = readArgDefault("OMNIROUTE_BUILD_WORKERS");
|
||
|
||
// Next derives `workers = CIRCLE_NODE_TOTAL - 1`.
|
||
const workers = workerPool - 1;
|
||
assert.ok(workers >= 1, `CIRCLE_NODE_TOTAL=${workerPool} leaves no build workers`);
|
||
|
||
// Every process — the parent `next build` process AND each page-data
|
||
// worker — is budgeted at the measured per-process RSS floor (see the file
|
||
// banner comment). The V8 heap ceiling (OMNIROUTE_BUILD_MEMORY_MB) bounds
|
||
// JS allocations but not Turbopack's native/Rust memory, so it cannot stand
|
||
// in for the parent process's real RSS.
|
||
const processes = workers + 1;
|
||
const worstCaseMb = processes * MEASURED_PROCESS_RSS_MB;
|
||
const budgetMb = RUNNER_MEMORY_MB * HEADROOM_FRACTION;
|
||
assert.ok(
|
||
worstCaseMb <= budgetMb,
|
||
`${processes} processes (1 parent + ${workers} workers) × ${MEASURED_PROCESS_RSS_MB} MB ` +
|
||
`measured RSS = ${worstCaseMb} MB exceeds the ${budgetMb} MB budget on a ` +
|
||
`${RUNNER_MEMORY_MB} MB runner — the Docker publish step dies with "ResourceExhausted: ` +
|
||
`cannot allocate memory" during page-data collection`
|
||
);
|
||
});
|
||
|
||
test("the worker pool does not oversubscribe the runner's 4 vCPU", () => {
|
||
const workers = readArgDefault("OMNIROUTE_BUILD_WORKERS") - 1;
|
||
assert.ok(workers <= 4, `${workers} workers oversubscribe a 4 vCPU runner`);
|
||
});
|