|
16 | 16 |
|
17 | 17 | #include <stdatomic.h> |
18 | 18 | #include <sys/stat.h> |
| 19 | +#include <mimalloc.h> |
| 20 | +#ifndef _WIN32 |
| 21 | +#include <sys/mman.h> |
| 22 | +#endif |
19 | 23 |
|
20 | 24 | /* ASan detection — mimalloc MI_OVERRIDE=0 under ASan, mi_process_info |
21 | 25 | * may return 0 for RSS. Tests that depend on accurate RSS must skip. */ |
@@ -206,6 +210,69 @@ TEST(mem_collect_no_crash) { |
206 | 210 | PASS(); |
207 | 211 | } |
208 | 212 |
|
| 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 | + |
209 | 276 | TEST(mem_collect_rss_still_positive) { |
210 | 277 | cbm_mem_init(0.5); |
211 | 278 | cbm_mem_collect(); |
@@ -829,6 +896,7 @@ SUITE(mem) { |
829 | 896 | RUN_TEST(mem_rss_positive); |
830 | 897 | RUN_TEST(mem_peak_rss_gte_rss); |
831 | 898 | RUN_TEST(mem_rss_increases_after_alloc); |
| 899 | + RUN_TEST(mem_rss_reflects_external_resident_memory); |
832 | 900 | RUN_TEST(mem_collect_no_crash); |
833 | 901 | RUN_TEST(mem_collect_rss_still_positive); |
834 | 902 | /* Memory pressure simulation */ |
|
0 commit comments