Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/foundation/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
17 changes: 11 additions & 6 deletions tests/test_mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,18 +516,23 @@ 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);
ASSERT_EQ(lower.budget, 8 * CBM_TEST_MB);
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();
}

Expand Down Expand Up @@ -1315,7 +1320,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);
Expand Down
Loading