From 80be86f73a372884d43b50e23e77c7b09104e01a Mon Sep 17 00:00:00 2001 From: yangsec888 <3275355+yangsec888@users.noreply.github.com> Date: Tue, 19 May 2026 10:02:29 -0400 Subject: [PATCH] feat(platform): CBM_WORKERS env override for cbm_default_worker_count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a single env-knob, CBM_WORKERS, that explicitly sets the worker count returned by cbm_default_worker_count(). When unset, behaviour is unchanged. In containerized deployments, cbm_default_worker_count(initial=true) returns sysconf(_SC_NPROCESSORS_ONLN) (host-CPU count, not the container's CPU quota). On a 1-vCPU pod scheduled onto a 16-core node cbm spawns ~16 indexing workers, each with its own per-worker buffers — the dominant OOMKill driver in container deployments. The broader ask (full cgroup awareness for cbm_system_info / mem.init budget logs) lives in a sibling issue; this knob is the smaller, lower-risk path that ships independently. Same precedence shape as the existing CBM_* env overrides: explicit override > implicit detection. Range clamped to [1, 256]; invalid values are ignored and the function logs `workers.env.invalid` at WARN before falling through to the sysconf-derived default. Files touched: - src/foundation/system_info.c — env probe + clamp + warn-and-fall-through - tests/test_platform.c — 3 new TEST cases covering override, invalid fallback, and unset baseline - README.md — CBM_WORKERS row in the Environment Variables table Related: cgroup-aware detection issue (filed alongside this PR). --- README.md | 1 + src/foundation/system_info.c | 18 +++++++++++- tests/test_platform.c | 55 ++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 713b4c56..f60beeed 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ codebase-memory-mcp config reset auto_index # reset to default | `CBM_CACHE_DIR` | `~/.cache/codebase-memory-mcp` | Override the database storage directory. All project indexes and config are stored here. | | `CBM_DIAGNOSTICS` | `false` | Set to `1` or `true` to enable periodic diagnostics output to `/tmp/cbm-diagnostics-.json`. | | `CBM_DOWNLOAD_URL` | *(GitHub releases)* | Override the download URL for updates. Used for testing or self-hosted deployments. | +| `CBM_WORKERS` | *(detected)* | Override the parallel-indexing worker count returned by `cbm_default_worker_count`. Useful inside containers where `sysconf(_SC_NPROCESSORS_ONLN)` reports host CPUs rather than the cgroup's effective quota. Range 1–256; invalid values are ignored with a warning. | ```bash # Store indexes in a custom directory diff --git a/src/foundation/system_info.c b/src/foundation/system_info.c index e5ba8a65..ab80cb4e 100644 --- a/src/foundation/system_info.c +++ b/src/foundation/system_info.c @@ -10,9 +10,11 @@ */ #include "foundation/constants.h" -enum { DEFAULT_CORES = 1, MIN_WORKERS = 1 }; +enum { DEFAULT_CORES = 1, MIN_WORKERS = 1, CBM_WORKERS_MAX = 256 }; +#include "foundation/log.h" #include "foundation/platform.h" #include // uint64_t +#include // strtol #include #ifdef _WIN32 @@ -168,6 +170,20 @@ cbm_system_info_t cbm_system_info(void) { } int cbm_default_worker_count(bool initial) { + /* CBM_WORKERS env override (clamped to [1, CBM_WORKERS_MAX]). + * Useful inside containers where sysconf(_SC_NPROCESSORS_ONLN) + * reports host CPUs rather than the cgroup's effective CPU quota. + * Same precedence shape as other CBM_* env overrides: + * explicit override > implicit detection. */ + char buf[CBM_SZ_32]; + if (cbm_safe_getenv("CBM_WORKERS", buf, sizeof(buf), NULL) != NULL) { + long n = strtol(buf, NULL, CBM_DECIMAL_BASE); + if (n >= MIN_WORKERS && n <= CBM_WORKERS_MAX) { + return (int)n; + } + cbm_log_warn("workers.env.invalid", "value", buf, "fallback", "sysconf"); + } + cbm_system_info_t info = cbm_system_info(); if (initial) { /* Use all cores for initial indexing — user is waiting */ diff --git a/tests/test_platform.c b/tests/test_platform.c index 7a502ccc..030423a2 100644 --- a/tests/test_platform.c +++ b/tests/test_platform.c @@ -3,6 +3,7 @@ */ #include "test_framework.h" #include "../src/foundation/platform.h" +#include /* setenv, unsetenv */ #include TEST(platform_now_ns) { @@ -68,6 +69,57 @@ TEST(platform_mmap_nonexistent) { PASS(); } +/* + * CBM_WORKERS env override for cbm_default_worker_count. + * + * Containers running cbm on a host with more CPUs than the cgroup's + * effective quota currently see ~host_cpu workers spawned because + * sysconf(_SC_NPROCESSORS_ONLN) is not cgroup-aware (see GitHub + * issue for the cgroup-detection ask). CBM_WORKERS is the smaller, + * explicit-override path that ships independently. + */ +TEST(platform_default_workers_env_override) { + setenv("CBM_WORKERS", "4", 1); + int n = cbm_default_worker_count(true); + ASSERT_EQ(n, 4); + /* initial=false should also honor the explicit override. */ + int m = cbm_default_worker_count(false); + ASSERT_EQ(m, 4); + unsetenv("CBM_WORKERS"); + PASS(); +} + +TEST(platform_default_workers_env_invalid) { + /* Out-of-range values (< 1 or > 256) and non-numeric strings + * fall back to the sysconf-derived default. */ + int baseline = cbm_default_worker_count(true); + ASSERT_GT(baseline, 0); + + setenv("CBM_WORKERS", "0", 1); + ASSERT_EQ(cbm_default_worker_count(true), baseline); + + setenv("CBM_WORKERS", "-1", 1); + ASSERT_EQ(cbm_default_worker_count(true), baseline); + + setenv("CBM_WORKERS", "9999", 1); + ASSERT_EQ(cbm_default_worker_count(true), baseline); + + setenv("CBM_WORKERS", "not-a-number", 1); + ASSERT_EQ(cbm_default_worker_count(true), baseline); + + unsetenv("CBM_WORKERS"); + PASS(); +} + +TEST(platform_default_workers_env_unset) { + /* When CBM_WORKERS is unset the result matches today's behaviour + * (info.total_cores for initial=true, perf_cores-1 for false). */ + unsetenv("CBM_WORKERS"); + cbm_system_info_t info = cbm_system_info(); + ASSERT_EQ(cbm_default_worker_count(true), info.total_cores); + PASS(); +} + SUITE(platform) { RUN_TEST(platform_now_ns); RUN_TEST(platform_now_ms); @@ -77,4 +129,7 @@ SUITE(platform) { RUN_TEST(platform_file_size); RUN_TEST(platform_mmap); RUN_TEST(platform_mmap_nonexistent); + RUN_TEST(platform_default_workers_env_override); + RUN_TEST(platform_default_workers_env_invalid); + RUN_TEST(platform_default_workers_env_unset); }