From bf46b91f9a106084d1d4e8bb30cd6a849f1d33b5 Mon Sep 17 00:00:00 2001 From: Atirna <288419661+atirna@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:36:35 +0530 Subject: [PATCH 1/2] fix(mem): honor explicit worker budget override Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com> --- src/foundation/mem.c | 17 +++++++++++++---- tests/test_mem.c | 17 +++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/foundation/mem.c b/src/foundation/mem.c index c3462b11a..26a542104 100644 --- a/src/foundation/mem.c +++ b/src/foundation/mem.c @@ -248,10 +248,19 @@ cbm_mem_budget_t cbm_mem_resolve_budget(size_t total_ram, double ram_fraction, cbm_mem_budget_t cbm_mem_resolve_budget_capped(size_t total_ram, double ram_fraction, const char *budget_mb, size_t hard_cap_bytes) { cbm_mem_budget_t result = cbm_mem_resolve_budget(total_ram, ram_fraction, budget_mb); - if (hard_cap_bytes > 0 && (result.budget == 0 || result.budget > hard_cap_bytes)) { - result.budget = hard_cap_bytes; - result.source = "daemon_worker_cap"; - result.hard_capped = true; + /* CBM_MEM_BUDGET_MB is the user-facing emergency override for a single + * large index. Applying the daemon's equal-share cap after resolving it + * makes a requested raise indistinguishable from the default and leaves a + * worker stuck at a quarter of the host budget (#1654). The normal + * fraction-derived path remains capped, preserving aggregate admission for + * concurrent jobs; an explicit value remains clamped to detected RAM by + * cbm_mem_resolve_budget(). */ + if (result.source == NULL || strcmp(result.source, "CBM_MEM_BUDGET_MB") != 0) { + if (hard_cap_bytes > 0 && (result.budget == 0 || result.budget > hard_cap_bytes)) { + result.budget = hard_cap_bytes; + result.source = "daemon_worker_cap"; + result.hard_capped = true; + } } return result; } diff --git a/tests/test_mem.c b/tests/test_mem.c index 7b0e73d01..2453d13d1 100644 --- a/tests/test_mem.c +++ b/tests/test_mem.c @@ -490,7 +490,12 @@ TEST(resolve_budget_override_when_total_unknown) { PASS(); } -TEST(resolve_budget_worker_cap_preserves_lower_user_override) { +/* A user who explicitly raises CBM_MEM_BUDGET_MB must not be silently reduced + * by the daemon's capacity slice. The parent-level slice still protects the + * default path, but the explicit override is the documented escape hatch for a + * single large index (#1654). Before the fix, a 64 MiB request was reported as + * daemon_worker_cap at 16 MiB instead of honoring the requested 64 MiB. */ +TEST(resolve_budget_worker_cap_honors_explicit_override) { size_t total = 8192 * CBM_TEST_MB; size_t worker_cap = 16 * CBM_TEST_MB; cbm_mem_budget_t lower = cbm_mem_resolve_budget_capped(total, 0.5, "8", worker_cap); @@ -498,10 +503,10 @@ TEST(resolve_budget_worker_cap_preserves_lower_user_override) { ASSERT_STR_EQ(lower.source, "CBM_MEM_BUDGET_MB"); ASSERT_FALSE(lower.hard_capped); - cbm_mem_budget_t capped = cbm_mem_resolve_budget_capped(total, 0.5, "64", worker_cap); - ASSERT_EQ(capped.budget, worker_cap); - ASSERT_STR_EQ(capped.source, "daemon_worker_cap"); - ASSERT_TRUE(capped.hard_capped); + cbm_mem_budget_t raised = cbm_mem_resolve_budget_capped(total, 0.5, "64", worker_cap); + ASSERT_EQ(raised.budget, 64 * CBM_TEST_MB); + ASSERT_STR_EQ(raised.source, "CBM_MEM_BUDGET_MB"); + ASSERT_FALSE(raised.hard_capped); PASS(); } @@ -1288,7 +1293,7 @@ SUITE(mem) { RUN_TEST(resolve_budget_override_wins); RUN_TEST(resolve_budget_override_clamped_to_total); RUN_TEST(resolve_budget_override_when_total_unknown); - RUN_TEST(resolve_budget_worker_cap_preserves_lower_user_override); + RUN_TEST(resolve_budget_worker_cap_honors_explicit_override); RUN_TEST(resolve_budget_invalid_override_falls_back); RUN_TEST(resolve_budget_override_overflow_clamps_to_total); RUN_TEST(resolve_budget_override_overflow_total_unknown_caps); From 92812ae15ef20083cd5e5baab808179d371417c4 Mon Sep 17 00:00:00 2001 From: Atirna <288419661+atirna@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:30 +0530 Subject: [PATCH 2/2] fix(mem): treat CBM_MEM_BUDGET_MB as an aggregate ceiling Keep the source-string discriminator and still apply the parent equal-share cap so N workers cannot oversubscribe the host. Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com> --- src/foundation/mem.c | 23 ++++++++++++----------- src/foundation/mem.h | 2 +- tests/test_mem.c | 23 ++++++++++++++--------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/foundation/mem.c b/src/foundation/mem.c index 133fd4060..61d13fe91 100644 --- a/src/foundation/mem.c +++ b/src/foundation/mem.c @@ -248,19 +248,20 @@ cbm_mem_budget_t cbm_mem_resolve_budget(size_t total_ram, double ram_fraction, cbm_mem_budget_t cbm_mem_resolve_budget_capped(size_t total_ram, double ram_fraction, const char *budget_mb, size_t hard_cap_bytes) { cbm_mem_budget_t result = cbm_mem_resolve_budget(total_ram, ram_fraction, budget_mb); - /* CBM_MEM_BUDGET_MB is the user-facing emergency override for a single - * large index. Applying the daemon's equal-share cap after resolving it - * makes a requested raise indistinguishable from the default and leaves a - * worker stuck at a quarter of the host budget (#1654). The normal - * fraction-derived path remains capped, preserving aggregate admission for - * concurrent jobs; an explicit value remains clamped to detected RAM by - * cbm_mem_resolve_budget(). */ - if (result.source == NULL || strcmp(result.source, "CBM_MEM_BUDGET_MB") != 0) { - if (hard_cap_bytes > 0 && (result.budget == 0 || result.budget > hard_cap_bytes)) { - result.budget = hard_cap_bytes; + /* The parent already divided the aggregate budget (env override or + * ram_fraction) across job slots. That per-slot share is the hard cap: + * N workers × a per-worker absolute override would oversubscribe the host + * (#1654). A lower explicit value still wins. Keep CBM_MEM_BUDGET_MB as + * the source when the env discriminator fired so the ceiling is visible + * as the user's aggregate, not as a silent daemon_worker_cap rewrite. */ + if (hard_cap_bytes > 0 && (result.budget == 0 || result.budget > hard_cap_bytes)) { + bool explicit_override = + result.source != NULL && strcmp(result.source, "CBM_MEM_BUDGET_MB") == 0; + result.budget = hard_cap_bytes; + if (!explicit_override) { result.source = "daemon_worker_cap"; - result.hard_capped = true; } + result.hard_capped = true; } return result; } diff --git a/src/foundation/mem.h b/src/foundation/mem.h index a2ae37376..bbd9d769f 100644 --- a/src/foundation/mem.h +++ b/src/foundation/mem.h @@ -31,7 +31,7 @@ void cbm_mem_init_with_cap(double ram_fraction, size_t hard_cap_bytes); * the caller never re-parses the env string. */ typedef struct { size_t budget; /* resolved budget in bytes */ - const char *source; /* log token: "ram_fraction" | "CBM_MEM_BUDGET_MB" */ + const char *source; /* "ram_fraction" | "CBM_MEM_BUDGET_MB" | "daemon_worker_cap" */ bool clamped; /* override was valid but exceeded total_ram → clamped down */ bool invalid; /* override was present but unparseable / out-of-range / ≤0 */ bool hard_capped; /* internal worker hard cap reduced the resolved budget */ diff --git a/tests/test_mem.c b/tests/test_mem.c index 8b3abc567..1ded83dad 100644 --- a/tests/test_mem.c +++ b/tests/test_mem.c @@ -516,12 +516,12 @@ TEST(resolve_budget_override_when_total_unknown) { PASS(); } -/* A user who explicitly raises CBM_MEM_BUDGET_MB must not be silently reduced - * by the daemon's capacity slice. The parent-level slice still protects the - * default path, but the explicit override is the documented escape hatch for a - * single large index (#1654). Before the fix, a 64 MiB request was reported as - * daemon_worker_cap at 16 MiB instead of honoring the requested 64 MiB. */ -TEST(resolve_budget_worker_cap_honors_explicit_override) { +/* CBM_MEM_BUDGET_MB is an aggregate ceiling the parent divides across job + * slots. A lower explicit value still wins; a raise is clipped to the per-slot + * share so N workers cannot oversubscribe the host (#1654). The source stays + * CBM_MEM_BUDGET_MB so the clip is the user's aggregate, not a silent + * daemon_worker_cap rewrite of a fraction-derived default. */ +TEST(resolve_budget_worker_cap_preserves_lower_user_override) { size_t total = 8192 * CBM_TEST_MB; size_t worker_cap = 16 * CBM_TEST_MB; cbm_mem_budget_t lower = cbm_mem_resolve_budget_capped(total, 0.5, "8", worker_cap); @@ -530,9 +530,14 @@ TEST(resolve_budget_worker_cap_honors_explicit_override) { ASSERT_FALSE(lower.hard_capped); cbm_mem_budget_t raised = cbm_mem_resolve_budget_capped(total, 0.5, "64", worker_cap); - ASSERT_EQ(raised.budget, 64 * CBM_TEST_MB); + ASSERT_EQ(raised.budget, worker_cap); ASSERT_STR_EQ(raised.source, "CBM_MEM_BUDGET_MB"); - ASSERT_FALSE(raised.hard_capped); + ASSERT_TRUE(raised.hard_capped); + + cbm_mem_budget_t fraction = cbm_mem_resolve_budget_capped(total, 0.5, NULL, worker_cap); + ASSERT_EQ(fraction.budget, worker_cap); + ASSERT_STR_EQ(fraction.source, "daemon_worker_cap"); + ASSERT_TRUE(fraction.hard_capped); PASS(); } @@ -1320,7 +1325,7 @@ SUITE(mem) { RUN_TEST(resolve_budget_override_wins); RUN_TEST(resolve_budget_override_clamped_to_total); RUN_TEST(resolve_budget_override_when_total_unknown); - RUN_TEST(resolve_budget_worker_cap_honors_explicit_override); + RUN_TEST(resolve_budget_worker_cap_preserves_lower_user_override); RUN_TEST(resolve_budget_invalid_override_falls_back); RUN_TEST(resolve_budget_override_overflow_clamps_to_total); RUN_TEST(resolve_budget_override_overflow_total_unknown_caps);