Skip to content

Commit 610531a

Browse files
authored
Merge pull request DeusData#861 from DeusData/distill/776-linux-rss-undercount
fix(mem): use real /proc RSS on Linux (mimalloc current_rss undercounts, blinding backpressure)
2 parents 9987a4e + 19296d7 commit 610531a

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

src/foundation/mem.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,37 @@ void cbm_mem_init(double ram_fraction) {
175175
}
176176

177177
size_t cbm_mem_rss(void) {
178+
#if defined(__linux__)
179+
/* Linux: mimalloc's _mi_prim_process_info() (vendored/mimalloc/src/prim/
180+
* unix/prim.c) never sets pinfo->current_rss on Linux — it only sets
181+
* peak_rss (from getrusage's ru_maxrss). current_rss therefore keeps
182+
* mi_process_info()'s default of pinfo.current_commit: mimalloc's OWN
183+
* committed-page counter, which this project deliberately tunes low via
184+
* mi_option_arena_eager_commit=0 + purge_decommits=1 + purge_delay=0
185+
* (cbm_mem_init) to reduce upfront memory. So on Linux "current_rss" is a
186+
* low-biased mimalloc-internal metric, not true RSS: under concurrent
187+
* large-file parsing it can read a few MB while real RSS is multiple GB,
188+
* silently blinding cbm_mem_over_budget()'s backpressure to real memory
189+
* pressure (small-but-nonzero, so the `current_rss > 0` guard below never
190+
* catches it). os_rss() reads /proc/self/statm — authoritative OS RSS,
191+
* unaffected by mimalloc's accounting — so it is the PRIMARY source on
192+
* Linux, not a last-resort fallback. macOS/Windows are unaffected:
193+
* mimalloc sets current_rss correctly there via task_info /
194+
* GetProcessMemoryInfo. */
195+
size_t proc_rss = os_rss();
196+
if (proc_rss > 0) {
197+
return proc_rss;
198+
}
199+
/* Extremely unlikely (/proc unavailable) — fall through to mimalloc. */
200+
#endif
178201
size_t current_rss = 0;
179202
size_t peak_rss = 0;
180203
mi_process_info(NULL, NULL, NULL, &current_rss, &peak_rss, NULL, NULL, NULL);
181204
if (current_rss > 0) {
182205
return current_rss;
183206
}
184-
/* Fallback for ASan builds (MI_OVERRIDE=0) */
207+
/* Fallback for ASan builds (MI_OVERRIDE=0) and any platform where
208+
* mimalloc's current_rss is unavailable/zero. */
185209
return os_rss();
186210
}
187211

tests/test_mem.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
#include <stdatomic.h>
1818
#include <sys/stat.h>
19+
#include <mimalloc.h>
20+
#ifndef _WIN32
21+
#include <sys/mman.h>
22+
#endif
1923

2024
/* ASan detection — mimalloc MI_OVERRIDE=0 under ASan, mi_process_info
2125
* may return 0 for RSS. Tests that depend on accurate RSS must skip. */
@@ -206,6 +210,69 @@ TEST(mem_collect_no_crash) {
206210
PASS();
207211
}
208212

213+
/* Reproduce-first guard for the Linux cbm_mem_rss() undercount (distilled
214+
* from #776's 132460f5).
215+
*
216+
* On Linux, mimalloc's mi_process_info() never sets current_rss
217+
* (vendored/mimalloc/src/prim/unix/prim.c only fills peak_rss from
218+
* getrusage's ru_maxrss); current_rss silently keeps mi_process_info()'s
219+
* default of pinfo.current_commit — mimalloc's OWN committed-page counter
220+
* (stats.c:555). The UNFIXED cbm_mem_rss() returns that counter whenever it is
221+
* nonzero, so on Linux it reports mimalloc-committed bytes, NOT true RSS. The
222+
* FIXED code reads /proc/self/statm (os_rss) as the primary source → true RSS.
223+
*
224+
* The guard makes the two quantities DIVERGE deterministically:
225+
* 1. mi_malloc() a small block (kept live) so mimalloc's committed counter is
226+
* a small POSITIVE value — this both defeats the UNFIXED `current_rss > 0`
227+
* fallback guard AND pins the reported value low. mi_malloc always routes
228+
* through mimalloc regardless of MI_OVERRIDE, so this works in the ASan
229+
* test-runner (MI_OVERRIDE=0) too.
230+
* 2. Grow TRUE process RSS by ~256MB via a raw anonymous mmap — memory
231+
* mimalloc's committed counter never sees, but /proc/self/statm does.
232+
* On UNFIXED Linux, cbm_mem_rss() then returns the ~few-MB committed counter
233+
* (< 128MB) → this assertion FAILS (RED). On FIXED Linux it returns the /proc
234+
* RSS (>= 256MB) → GREEN.
235+
*
236+
* macOS/Windows set current_rss from task_info/GetProcessMemoryInfo, which DO
237+
* include the mapped+touched region, so cbm_mem_rss() is accurate there both
238+
* before and after the fix — this passes on those platforms either way. The
239+
* RED therefore manifests only on the Linux CI leg, which is exactly where the
240+
* production undercount bit (backpressure/ceiling blinded). */
241+
TEST(mem_rss_reflects_external_resident_memory) {
242+
cbm_mem_init(0.5);
243+
244+
/* (1) Pin mimalloc's committed-page counter to a small positive value. */
245+
const size_t warm = (size_t)1 * 1024 * 1024; /* 1 MB via mimalloc */
246+
void *mi_buf = mi_malloc(warm);
247+
ASSERT_NOT_NULL(mi_buf);
248+
memset(mi_buf, 0x11, warm);
249+
250+
const size_t region = (size_t)256 * 1024 * 1024; /* 256 MB true RSS */
251+
const size_t threshold = (size_t)128 * 1024 * 1024; /* generous half */
252+
253+
#ifdef _WIN32
254+
/* Windows current_rss (WorkingSetSize) is accurate; a plain resident
255+
* allocation is reflected regardless of allocator. No Linux undercount. */
256+
void *big = malloc(region);
257+
ASSERT_NOT_NULL(big);
258+
memset(big, 0x5A, region);
259+
size_t rss = cbm_mem_rss();
260+
ASSERT_GTE(rss, threshold);
261+
free(big);
262+
#else
263+
/* (2) Raw mmap bypasses mimalloc entirely: its committed counter does NOT
264+
* grow, but the true RSS does — this is what exposes the Linux undercount. */
265+
void *big = mmap(NULL, region, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
266+
ASSERT_TRUE(big != MAP_FAILED);
267+
memset(big, 0x5A, region); /* fault every page in → resident */
268+
size_t rss = cbm_mem_rss();
269+
ASSERT_GTE(rss, threshold);
270+
munmap(big, region);
271+
#endif
272+
mi_free(mi_buf);
273+
PASS();
274+
}
275+
209276
TEST(mem_collect_rss_still_positive) {
210277
cbm_mem_init(0.5);
211278
cbm_mem_collect();
@@ -829,6 +896,7 @@ SUITE(mem) {
829896
RUN_TEST(mem_rss_positive);
830897
RUN_TEST(mem_peak_rss_gte_rss);
831898
RUN_TEST(mem_rss_increases_after_alloc);
899+
RUN_TEST(mem_rss_reflects_external_resident_memory);
832900
RUN_TEST(mem_collect_no_crash);
833901
RUN_TEST(mem_collect_rss_still_positive);
834902
/* Memory pressure simulation */

0 commit comments

Comments
 (0)