Repository navigation
Linux: cbm_system_info / cbm_default_worker_count don't respect cgroup CPU/memory limits #363
Description
Summary
When CBM runs inside a Linux container with cgroup CPU and/or memory
limits (the normal Kubernetes / Docker / Nomad / systemd-slice case),
cbm_system_info() and cbm_default_worker_count() report
node-level CPU count and RAM, not the cgroup's effective limits.
This produces three concrete operator-visible problems:
cbm_default_worker_count(initial=true)returns
sysconf(_SC_NPROCESSORS_ONLN), which on Linux is the number of
online host CPUs. A 1-vCPU container scheduled onto a 16-core
node spawns ~16 indexing workers, each with its own per-worker
buffers (AST stacks, tree-sitter parsers, slab allocators).
In a memory-constrained container this is the dominant OOMKill
driver — far more so thang_budget(which is observability-only;
cbm_mem_over_budget()andcbm_vmem_over_budget()are declared
but never called).mem.init budget_mb=…log line is computed from the host's
sysinfo(2).totalram, so a 2 GiB-limited pod on a 62 GB node
logsbudget_mb=31000, which is alarming and confuses incident
response. (Cosmetic, but compounds 1.)- The over-budget worker count also magnifies the blast radius of
Dump-phase crash on large TS monorepo (v0.6.1, darwin-arm64) — pipeline completes through semantic_edges then terminates abnormally before gbuf.dump #317 (dump-phase OOM crash on large TS monorepo) and Silent index corruption after rapid kill/restart cycles #334 (silent
index corruption after rapid kill/restart) — both of which we have
reproduced in the wild on Kubernetes pods.
Where in the source
Verified against main at HEAD 22153563cd1072b4f79e0e27113f6e0dea3abc1a:
src/foundation/system_info.cdetect_system_linux()usessysconf(_SC_NPROCESSORS_ONLN)for cores andsysinfo(2)for RAM, both host-scoped.src/foundation/system_info.ccbm_default_worker_count()consumes the host-scoped value with no cgroup adjustment.src/foundation/mem.candsrc/foundation/vmem.c—g_budgetis set frominfo.total_ram * 0.5, so the misleading log line in (2) above propagates from the same source.
Suggested fix shape
Two reads in detect_system_linux(), both with safe fallbacks to
the existing host-scoped values:
- CPU count — read cgroup v2
cpu.max(preferred) or v1
cpu.cfs_quota_us/cpu.cfs_period_us. If the result is
max/ unlimited / parse error, fall back to
sysconf(_SC_NPROCESSORS_ONLN). - Memory — read cgroup v2
memory.max(preferred) or v1
memory.limit_in_bytes. If the result ismax/ unlimited or
exceeds host total, fall back tosysinfo(2).totalram * mem_unit.
Both files live at well-known paths (/sys/fs/cgroup/... for v2,
/sys/fs/cgroup/<controller>/... for v1) and are simple text
reads. No new dependencies.
A reasonable cap for safety: min(cgroup_cpu, host_cpu) and
min(cgroup_mem, host_mem). This is robust against
mis-mounted-cgroups edge cases.
Compatible with existing env knobs
Once cgroup awareness lands, an explicit env-override remains useful
for ops who want to tune below cgroup limits (e.g. leave headroom
for sibling processes in the same container). A companion PR (filed
alongside this issue) adds CBM_WORKERS for that case. The two
together give: env > cgroup > host as the precedence chain,
matching the CBM_SQLITE_MMAP_SIZE precedent from commit 093707c.
Out of scope
- macOS, BSD, Windows containerization stories (none of them have
the same sysconf/sysinfo problem in practice). - mimalloc tuning. mimalloc respects its own arena settings; the
request here is just to compute the inputs CBM passes to its
worker scheduler and budget logger correctly.
Related
- Related crash issues that are made worse by overspawn in
containers: Dump-phase crash on large TS monorepo (v0.6.1, darwin-arm64) — pipeline completes through semantic_edges then terminates abnormally before gbuf.dump #317, Silent index corruption after rapid kill/restart cycles #334, Segmentation fault (core dumped) #336, Segmentation fault at start of lsp_cross pass on repositories with 200+ files #340. - Existing precedent for env-knob tuning: commit
093707c
(CBM_SQLITE_MMAP_SIZE). - Downstream context: this is being filed while integrating CBM as
a long-lived MCP-server process inside a 2 GiB Kubernetes pod.
Happy to iterate on or contribute the patch — let us know which
form you prefer.
Activity
feat(platform): cgroup-aware CPU/memory detection in detect_system_linux
cryptomaltese commentedon Jun 2, 2026
Reopen candidate — the cgroup-respect doesn't take effect under a
systemd --user --scopememory limit on v0.7.0.Running v0.7.0 inside an 8 GB transient scope,
mem.initstill reports the host RAM, not the cgroup limit:total_ram_mb=31826is the host's ~32 GB;budget_mb=15913is ~50% of host, not of the 8 GB cgroup. Outside the scope it logs the same numbers, i.e.cbm_system_infois reading host RAM regardless of the enclosing cgroup v2memory.max.Repro: the one-liner above (cgroup v2, systemd user manager). Expected: when the process's cgroup has a
memory.max/memory.highbelow host RAM, the budget should derive from that (e.g. read/sys/fs/cgroup/<leaf>/memory.maxfor the current PID's cgroup, not the root).Context: we run cm-mcp inside an 8 GB scope specifically to bound the no-file-size-cap index OOM. The kernel hard-kill still protects us, but the intended self-sizing from this issue would be the softer, better guard — and it isn't kicking in here.
Environment: v0.7.0, Linux cgroup v2, systemd --user.
yangsec888 commentedon Jun 3, 2026
@cryptomaltese the repro is real but it's a version-skew issue, not a bug in the cgroup code. The cgroup-aware detection landed on
main~16 hours after v0.7.0 was tagged:b133a20release: bump pkg/npm + pkg/pypi to v0.7.0d952238feat(platform): CBM_WORKERS env override for cbm_default_worker_counta5a3d1dfeat(platform): cgroup-aware CPU/memory detection in detect_system_linuxgit tag --contains a5a3d1dreturns empty — neither commit is in any tagged release yet, so a v0.7.0 binary will still read host RAM viasysinfo(2)exactly as you observed.Building from
mainat HEAD (currently22fe481) and re-running yoursystemd-run --user --scope -q -p MemoryMax=8G ...repro should showmem.initderiving from/sys/fs/cgroup/<leaf>/memory.maxinstead.@DeusData — would you be open to cutting v0.7.1 as a patch release that pulls in
d952238+a5a3d1d(plus anything else queued)? It would unblock containerized deployments without having to ship from source. Happy to help test the release candidate against our 2 GiB k8s pod setup.DeusData commentedon Jun 12, 2026
Confirmed, and thanks @yangsec888 for pinning down the version skew — the cgroup-aware sizing (d952238) missed the v0.7.0 tag by hours and first shipped in v0.8.0. If you can confirm correct worker counts under your cgroup limits on v0.8.1, I'll close this out.
yangsec888 commentedon Jun 13, 2026
Confirmed on v0.8.1 in our EKS pod (cgroup v2, limits: 2 CPU / 8 GiB; host: 8 CPU / ~31 GiB).
Memory (no override): mem.init budget_mb=4096 total_ram_mb=8192 — derives from the 8 GiB cgroup, not host RAM.
CPU, with CBM_WORKERS unset to isolate auto-detection (400-file repo):
pipeline.mode mode=parallel workers=2 files=400
parallel.extract.start files=400 workers=2
parallel.mem.budget total_mb=4096 per_worker_mb=2048
workers=2 matches the cgroup CPU quota rather than the 8 host CPUs. (In production we also pin CBM_WORKERS=2 explicitly, but the cgroup path lands on the same value on its own.)
Good to close from our side — thanks for the version-skew confirmation.
mayurpise commentedon Jun 23, 2026
Corroboration from the bare-metal (non-container) case — v0.8.1
Confirming this from the angle the issue lists as out-of-scope-ish: a developer running CBM directly on a workstation, no container, no cgroup limit set. The cgroup-read fix proposed here is correct and necessary for pods, but it won't help this case (there's no cgroup to read), and it's a common one — so flagging it for the same fix's design.
Repro (v0.8.1,
linux-amd64, 121 GB host, bare metal):total_ram_mbis hostMemTotal;budget_mbis 50% of it — matchesg_budget = info.total_ram * 0.5.systemd-run --user --scope -p MemoryMax=8Gstill logstotal_ram_mb=124610(host), as expected from thesysinfo(2)path you cite.CBM_MEM_BUDGET_MB,CBM_MEMORY_BUDGET_MB,CBM_MEM_BUDGET,CBM_BUDGET_MB,CBM_MAX_MEM_MB,CBM_RAM_PCT,CBM_MEM_PCT,GOMEMLIMIT— none movebudget_mb. (CBM_WORKERSfrom the companion PR is the right lever; just noting there's no memory-axis knob yet.)Operator-visible impact on bare metal: on a 64 GB workstation,
cbm_default_worker_count= full host nprocs and the 50%-of-RAM framing means a first index of a large repo (~50M LOC) climbed to ~30 GB peak and swap-thrashed the whole desktop into a hang — taking unrelated apps down with it. Consistent with your point that worker overspawn (notg_budget, which is observability-only) is the real OOM driver.Two asks for the bare-metal path, alongside the cgroup fix:
env > cgroup > host) on the memory axis too, not just CPU/workers — i.e. an explicit memory-budget override (or a configurable fraction) so workstation users can leave headroom for the rest of the machine when there's no cgroup to express it.Mitigation that works today for bare-metal users (no engine change needed): wrap the index call in a transient memory-bounded scope. The engine isn't cgroup-aware so it doesn't adopt the limit, but the kernel still enforces it —
MemoryHighthrottles via reclaim (slower, but the host stays responsive) andMemoryMaxis a hard ceiling:systemd-run --user --scope -q \ -p MemoryHigh=25% -p MemoryMax=30% \ -- codebase-memory-mcp cli index_repository '{"repo_path":"..."}'We've shipped this as the default in our downstream (Draft) —
index_repositorynow always runs inside a 25%-RAM scope withCBM_WORKERScapped, which fully resolved the hang. Sharing in case it helps other bare-metal users before the cgroup/env work lands. Happy to test a build with aCBM_MEM_BUDGET_MB-style knob.test(repro): memory-budget #363 + bounded RSS-soak #581
DeusData commentedon Jun 28, 2026
This issue now has a first-class, durable reproduction in the cumulative bug-reproduction suite landing in #667 — a test that pins the exact failing behaviour, so it's tracked and guarded against silent regression. Where #667 also lands the root-cause fix, that's called out in the PR; otherwise the reproduction makes the eventual fix straightforward and verifiable.
The reproduction goes live once #667 merges. (Posted from the release branch's QA pass.)
fix: 17 reproduce-first issue + new-bug fixes (per-case verified)
test(repro): refresh DeusData#363 memory-axis repro for shipped + cla…
yangsec888 commentedon Jul 8, 2026
Both axes of #363 are now addressed on
main:cbm_detect_cgroup_cpus()+ theCBM_WORKERSoverride (tested intest_platform.c).cbm_detect_cgroup_mem()applyingmin(cgroup, host), plus theCBM_MEM_BUDGET_MBoverride. The override is being hardened in feat(mem): pure resolver + strict parse + clamp for CBM_MEM_BUDGET_MB #954: the parse + clamp logic is extracted into a pure, testablecbm_mem_resolve_budget()with strict parsing (rejects trailing garbage / ERANGE / non-positive), clamp-to-effective-RAM, andsource=/mem.budget.clampedlogging.The
repro_issue363guard is green and has been refreshed to match the shipped, clamped semantics.This looks closeable — flagging for maintainer confirmation.
feat(mem): pure budget resolver with strict parse + clamp for CBM_MEM…
DeusData commentedon Aug 20, 2026
Thanks @yangsec888 for the detailed container report and follow-through, and thanks everyone who verified the behavior across Kubernetes, systemd scopes, and bare metal. Closing this as resolved: current main applies cgroup v1/v2 CPU and memory limits to system sizing, caps them against host capacity, supports CBM_WORKERS and CBM_MEM_BUDGET_MB overrides, and carries focused platform, budget, and repro_issue363 regression coverage. The original reporter also confirmed the automatic CPU and memory sizing under a constrained EKS pod.