|
1 | 1 | /* |
2 | | - * repro_issue363.c — Reproduce-first case for OPEN bug #363. |
| 2 | + * repro_issue363.c — Regression guard for bug #363 (both axes now FIXED). |
3 | 3 | * |
4 | 4 | * Issue: #363 — "Linux: cbm_system_info / cbm_default_worker_count don't |
5 | 5 | * respect cgroup CPU/memory limits" |
6 | 6 | * |
7 | | - * ROOT CAUSE (two distinct axes): |
| 7 | + * Both axes of #363 have shipped; this case is now a permanent GREEN guard: |
8 | 8 | * |
9 | 9 | * CPU axis — FIXED in v0.8.0 (commit a5a3d1d). |
10 | 10 | * cbm_detect_cgroup_cpus() reads /sys/fs/cgroup/cpu.max (v2) or |
11 | | - * .../cpu/cpu.cfs_quota_us + .../cpu/cpu.cfs_period_us (v1) and the |
12 | | - * result is used by detect_system_linux() in system_info.c:226. |
13 | | - * cbm_default_worker_count() also honours the CBM_WORKERS env override |
14 | | - * (commit d952238). Both are thoroughly tested in test_platform.c. |
| 11 | + * .../cpu/cpu.cfs_quota_us + .../cpu/cpu.cfs_period_us (v1); the result |
| 12 | + * feeds detect_system_linux(). cbm_default_worker_count() also honours the |
| 13 | + * CBM_WORKERS env override (commit d952238). Both tested in test_platform.c. |
15 | 14 | * |
16 | | - * Memory axis — STILL OPEN (confirmed by reporter @mayurpise in the last |
17 | | - * open comment on #363, 2026-06-25). |
18 | | - * cbm_detect_cgroup_mem() similarly reads /sys/fs/cgroup/memory.max (v2) |
19 | | - * or .../memory/memory.limit_in_bytes (v1), and detect_system_linux() |
20 | | - * uses it (system_info.c:229). BUT: there is NO env-override knob on |
21 | | - * the memory axis. The CPU axis has CBM_WORKERS; the memory side has |
22 | | - * nothing. On a bare-metal host with no enclosing cgroup, users cannot |
23 | | - * cap cbm_mem_init's budget without wrapping the process in a cgroup |
24 | | - * scope (as @mayurpise's workaround shows). |
| 15 | + * Memory axis — FIXED. |
| 16 | + * cbm_detect_cgroup_mem() reads /sys/fs/cgroup/memory.max (v2) or |
| 17 | + * .../memory/memory.limit_in_bytes (v1); detect_system_linux() applies |
| 18 | + * min(cgroup, host). The missing env knob — the "EXACT OPEN GAP" this |
| 19 | + * repro was filed for — now exists: cbm_mem_init() reads CBM_MEM_BUDGET_MB |
| 20 | + * and routes it through the pure cbm_mem_resolve_budget() (explicit |
| 21 | + * override > implicit detection, mirroring CBM_WORKERS). |
25 | 22 | * |
26 | | - * EXACT OPEN GAP: |
27 | | - * A CBM_MEM_BUDGET_MB environment variable (analogous to CBM_WORKERS) that |
28 | | - * cbm_mem_init() checks before computing g_budget from info.total_ram. |
29 | | - * If set to a valid integer N, cbm_mem_init() should set |
30 | | - * g_budget = N * 1024 * 1024, honouring it regardless of cgroup or host RAM. |
31 | | - * |
32 | | - * WHY THIS TEST IS RED: |
33 | | - * cbm_mem_init() (src/foundation/mem.c) reads cbm_system_info().total_ram |
34 | | - * and multiplies by ram_fraction. It does NOT call cbm_safe_getenv for |
35 | | - * CBM_MEM_BUDGET_MB — the override path does not exist. Setting |
36 | | - * CBM_MEM_BUDGET_MB=4096 has no effect; cbm_mem_budget() returns a value |
37 | | - * derived from host RAM (or cgroup RAM when inside a container), not from |
38 | | - * the env var. The assertion ASSERT_EQ(cbm_mem_budget(), 4096*1024*1024) |
39 | | - * therefore fails on any host whose cgroup or physical RAM != exactly 4 GiB. |
40 | | - * |
41 | | - * ROOT CAUSE LOCATION: |
42 | | - * src/foundation/mem.c, cbm_mem_init(), after the mimalloc option block |
43 | | - * (currently around line 126): |
44 | | - * cbm_system_info_t info = cbm_system_info(); |
45 | | - * g_budget = (size_t)((double)info.total_ram * ram_fraction); |
46 | | - * The fix is to insert a cbm_safe_getenv("CBM_MEM_BUDGET_MB", ...) lookup |
47 | | - * BEFORE this line and, if valid, set g_budget directly without involving |
48 | | - * info.total_ram — mirroring the CBM_WORKERS pattern in |
49 | | - * cbm_default_worker_count() (system_info.c:290). |
50 | | - * |
51 | | - * INTENDED FIX: |
52 | | - * 1. In cbm_mem_init(): read CBM_MEM_BUDGET_MB; if set to a valid positive |
53 | | - * integer, use that value (in bytes) as g_budget and log it. |
54 | | - * 2. Test: set CBM_MEM_BUDGET_MB=4096, call cbm_mem_init(0.5), assert |
55 | | - * cbm_mem_budget() == 4096 * 1024 * 1024. This test goes GREEN when |
56 | | - * the override is wired. |
57 | | - * 3. Complementary: on Linux, confirm cbm_system_info().total_ram is capped |
58 | | - * by the cgroup memory limit when present — already covered in |
59 | | - * test_platform.c via cbm_detect_cgroup_mem() unit tests, but an |
60 | | - * integration path via cbm_system_info() is untestable without a seam |
61 | | - * that lets callers override the hardcoded "/sys/fs/cgroup" root in |
62 | | - * detect_system_linux() (system_info.c:229). |
| 23 | + * IMPORTANT — clamp semantics: |
| 24 | + * cbm_mem_resolve_budget() clamps the override to effective (cgroup/host) RAM |
| 25 | + * so it can never claim more than the box has. So a *large* override is NOT |
| 26 | + * honoured verbatim on a small host — it clamps to total_ram. This guard |
| 27 | + * therefore uses a small budget (REPRO363_BUDGET_MB) that is at/below any |
| 28 | + * realistic host's RAM, so it is honoured exactly and the assertion is stable |
| 29 | + * on every runner. The clamp behaviour itself is unit-tested separately in |
| 30 | + * tests/test_mem.c (resolve_budget_override_clamped_to_total). |
63 | 31 | * |
64 | 32 | * NOTE on cbm_mem_init() caching: |
65 | | - * g_budget is initialised once via atomic_compare_exchange_strong. |
66 | | - * The test must run in a process where cbm_mem_init() has NOT been called |
67 | | - * yet, OR the test must reset g_initialized — neither is supported today. |
68 | | - * The repro works as written because the repro runner does not call |
69 | | - * cbm_mem_init() before this suite. If the initialisation guard is an |
70 | | - * issue, the fix also needs a cbm_mem_reset_for_test() hook (test-only, |
71 | | - * guarded by CBM_TEST_HOOKS or similar). |
| 33 | + * g_budget is initialised once via atomic_compare_exchange_strong, so this |
| 34 | + * guard relies on running before any suite that calls cbm_mem_init(); the |
| 35 | + * repro runner does not init the budget before this suite. |
72 | 36 | */ |
73 | 37 |
|
74 | 38 | #include "test_framework.h" |
|
77 | 41 | #include <stdint.h> |
78 | 42 | #include <stdlib.h> |
79 | 43 |
|
80 | | -#define REPRO363_BUDGET_MB 4096UL |
| 44 | +/* Deliberately small so the resolver honours it exactly (never clamps) on any |
| 45 | + * host — see the clamp-semantics note above. */ |
| 46 | +#define REPRO363_BUDGET_MB 128UL |
81 | 47 | #define REPRO363_BUDGET_BYTES (REPRO363_BUDGET_MB * 1024UL * 1024UL) |
82 | 48 |
|
83 | 49 | /* |
84 | 50 | * repro_issue363_mem_budget_env_override |
85 | 51 | * |
86 | | - * Precondition: CBM_MEM_BUDGET_MB=4096 is set before cbm_mem_init() is |
87 | | - * called. The budget should be 4096 MiB regardless of host RAM or cgroup. |
88 | | - * |
89 | | - * RED condition (current code): |
90 | | - * cbm_mem_init() ignores CBM_MEM_BUDGET_MB entirely; cbm_mem_budget() |
91 | | - * returns host-RAM * fraction, not 4 GiB. The assertion fires unless the |
92 | | - * test runner happens to be on a machine whose effective RAM is exactly |
93 | | - * 8 GiB with fraction=0.5 — essentially never. |
94 | | - * |
95 | | - * GREEN condition (after fix): |
96 | | - * cbm_mem_init() reads CBM_MEM_BUDGET_MB, finds "4096", sets |
97 | | - * g_budget = 4096 * 1024 * 1024. The assertion passes on any machine. |
| 52 | + * Precondition: CBM_MEM_BUDGET_MB is set before the process's first |
| 53 | + * cbm_mem_init(). The budget must equal the override (honoured exactly, since |
| 54 | + * it is below any host's effective RAM), proving the env knob is wired. |
98 | 55 | */ |
99 | 56 | TEST(repro_issue363_mem_budget_env_override) { |
100 | | - cbm_setenv("CBM_MEM_BUDGET_MB", "4096", 1); |
| 57 | + cbm_setenv("CBM_MEM_BUDGET_MB", "128", 1); |
101 | 58 |
|
102 | 59 | cbm_mem_init(0.5); |
103 | 60 |
|
104 | 61 | size_t budget = cbm_mem_budget(); |
105 | 62 |
|
106 | 63 | cbm_unsetenv("CBM_MEM_BUDGET_MB"); |
107 | 64 |
|
108 | | - /* |
109 | | - * RED on current code: budget derives from host/cgroup RAM, not the env |
110 | | - * var. On any machine where effective RAM != 8192 MiB this fails. |
111 | | - * GREEN once CBM_MEM_BUDGET_MB is wired in cbm_mem_init(). |
112 | | - */ |
113 | 65 | ASSERT_EQ((long long)budget, (long long)REPRO363_BUDGET_BYTES); |
114 | 66 |
|
115 | 67 | PASS(); |
|
0 commit comments