Skip to content
Open
12 changes: 11 additions & 1 deletion src/foundation/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +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);
/* 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;
result.source = "daemon_worker_cap";
if (!explicit_override) {
result.source = "daemon_worker_cap";
}
result.hard_capped = true;
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion src/foundation/mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
18 changes: 14 additions & 4 deletions tests/test_mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,11 @@ TEST(resolve_budget_override_when_total_unknown) {
PASS();
}

/* 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;
Expand All @@ -524,10 +529,15 @@ 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, worker_cap);
ASSERT_STR_EQ(raised.source, "CBM_MEM_BUDGET_MB");
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();
}

Expand Down
Loading