mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
fix(scripts): resolve cgroup-namespace-relative records to their mount
ClawSweeper P1 on d6fe49dd3f: inside a cgroup namespace /proc/self/cgroup
reports the namespace root ("0::/") while mountinfo field 4 stays the host
subtree the cgroupfs was mounted from ("/docker/<id>"). relativeCgroupPath then
found no prefix match and returned null; because a memory record had already
been seen, the root probe was skipped and the build fell back to host MemTotal.
A constrained container therefore missed its own budget entirely.
That namespace root is exactly what the mount exposes at its mount point, so it
resolves to "/" rather than failing closed.
Regression test fails pre-fix: a "0::/" record against a /docker/2f1a9c mount
root with a 5 GiB memory.max yields --max-old-space-size=12288 before the fix
and 4352 after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -539,6 +539,12 @@ 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.
|
||||
if (cgroupPath === "/") {
|
||||
return "/";
|
||||
}
|
||||
if (cgroupPath === mountRoot) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@@ -453,6 +453,38 @@ describe("resolveTsdownBuildInvocation", () => {
|
||||
expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=4352");
|
||||
});
|
||||
|
||||
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
|
||||
// host memory, which is the opposite of what a constrained container needs.
|
||||
const cgroupFiles = new Map([
|
||||
["/proc/self/cgroup", "0::/\n"],
|
||||
[
|
||||
"/proc/self/mountinfo",
|
||||
"30 25 0:26 /docker/2f1a9c /sys/fs/cgroup rw,nosuid - cgroup2 cgroup2 rw\n",
|
||||
],
|
||||
["/sys/fs/cgroup/memory.max", `${5 * 1024 * 1024 * 1024}\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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 5 GiB container budget minus the 768 MiB build headroom.
|
||||
expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=4352");
|
||||
});
|
||||
|
||||
it("caps the tsdown heap when the cgroup mount point is octal-escaped in mountinfo", () => {
|
||||
const slicePath = "/user.slice/user-999.slice/user@999.service";
|
||||
// The kernel escapes a space in the mount point as \040. Matching the field
|
||||
|
||||
Reference in New Issue
Block a user