diff --git a/scripts/tsdown-build.mts b/scripts/tsdown-build.mts index e987782be503..8b3b388ba30d 100644 --- a/scripts/tsdown-build.mts +++ b/scripts/tsdown-build.mts @@ -539,9 +539,15 @@ function relativeCgroupPath(mountRoot: string, cgroupPath: string) { if (mountRoot === "/") { return cgroupPath; } - // Inside a cgroup namespace the record is namespace-relative and reads "/", while the - // mount root stays the host subtree it was mounted from. That namespace root is exactly - // what this mount exposes at its mount point, so it resolves rather than failing closed. + // cgroup_namespaces(7): a mount inherited across a namespace can report a field-4 root of + // "/.." or another non-canonical path. Which cgroup such a view exposes is not derivable + // here, and guessing would size the build from an unrelated cgroup's limit. + if (mountRoot.split("/").includes("..")) { + return null; + } + // Otherwise a namespace-relative record reads "/" while the mount root stays the host + // subtree it was mounted from. That namespace root is what this mount exposes at its + // mount point, so it resolves rather than failing closed. if (cgroupPath === "/") { return "/"; } diff --git a/test/scripts/tsdown-build.test.ts b/test/scripts/tsdown-build.test.ts index 5933afa3326b..1b6119b7123f 100644 --- a/test/scripts/tsdown-build.test.ts +++ b/test/scripts/tsdown-build.test.ts @@ -453,6 +453,36 @@ describe("resolveTsdownBuildInvocation", () => { expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=4352"); }); + it("ignores an inherited namespace mount whose root is not derivable", () => { + // cgroup_namespaces(7): an inherited mount can report a field-4 root of "/..". Which + // cgroup it exposes is not derivable, so it must not be probed as if it were the + // process's own; the resolver falls back rather than sizing from an unrelated limit. + const cgroupFiles = new Map([ + ["/proc/self/cgroup", "0::/\n"], + ["/proc/self/mountinfo", "30 25 0:26 /.. /sys/fs/cgroup rw,nosuid - cgroup2 cgroup2 rw\n"], + ["/sys/fs/cgroup/memory.max", `${5 * 1024 * 1024 * 1024}\n`], + ["/proc/meminfo", `MemTotal: ${16 * 1024 * 1024} kB\n`], + ]); + + const result = resolveTsdownBuildInvocation({ + nodeExecPath: "/usr/bin/node", + npmExecPath: "/tmp/pnpm.cjs", + env: {}, + fs: { + readFileSync(filePath: string) { + const contents = cgroupFiles.get(filePath); + if (contents === undefined) { + throw new Error(`ENOENT: ${filePath}`); + } + return contents; + }, + }, + }); + + // Host MemTotal sizing, not the unrelated 5 GiB limit behind the inherited mount. + expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=12288"); + }); + it("caps the tsdown heap from a cgroup-namespace-relative record", () => { // Inside a container the record is namespace-relative ("/") while the mount root stays // the host subtree. Failing to resolve that pair skips the limit and falls back to