| 257 | } |
| 258 | |
| 259 | void cbm_mem_init_with_cap(double ram_fraction, size_t hard_cap_bytes) { |
| 260 | int expected = 0; |
| 261 | if (!atomic_compare_exchange_strong(&g_initialized, &expected, 1)) { |
| 262 | return; |
| 263 | } |
| 264 | |
| 265 | if (ram_fraction <= 0.0 || ram_fraction > MAX_RAM_FRACTION) { |
| 266 | ram_fraction = DEFAULT_RAM_FRACTION; |
| 267 | } |
| 268 | |
| 269 | /* Reduce upfront memory: don't eagerly commit arenas. |
| 270 | * Force decommit on purge (MADV_FREE_REUSABLE on macOS) so RSS |
| 271 | * drops immediately instead of staying high until memory pressure. */ |
| 272 | #ifdef _WIN32 |
| 273 | /* Decisive check: is the allocator still "preloading"? If so its arena |
| 274 | * machinery — including every purge path — is inert, and completing init |
| 275 | * here is what turns the options above from decoration into behaviour. */ |
| 276 | if (_mi_preloading()) { |
| 277 | _mi_auto_process_init(); |
| 278 | if (_mi_preloading()) { |
| 279 | /* Arena creation and every purge path stay disabled while this is |
| 280 | * set, so the process would grow until the OS killed it. Refuse. */ |
| 281 | mem_setup_fatal("preloading", |
| 282 | "allocator remained in preloading state after explicit process " |
| 283 | "init: arena creation and purging are disabled, memory would " |
| 284 | "never be returned to the OS"); |
| 285 | } |
| 286 | cbm_log_warn("mem.allocator.preloading_completed", "still_preloading", "false", "detail", |
| 287 | "allocator was still preloading, so arena creation and purging were " |
| 288 | "disabled; process init completed explicitly"); |
| 289 | } else { |
| 290 | cbm_log_info("mem.allocator.preloading", "state", "already_complete"); |
| 291 | } |
| 292 | #endif |
| 293 | |
| 294 | mem_option_set_verified(mi_option_arena_eager_commit, 0, "arena_eager_commit"); |
| 295 | mem_option_set_verified(mi_option_purge_decommits, SKIP_ONE, "purge_decommits"); |
| 296 | mem_option_set_verified(mi_option_purge_delay, 0, "purge_delay"); /* immediate */ |
| 297 | /* v3 (#832): reclaim abandoned pages on ANY thread's free (=1), restoring the |
| 298 | * v2 behaviour. mimalloc v3 defaults page_reclaim_on_free=0, so pages a worker |
| 299 | * thread abandons at exit are NOT reclaimed when the main thread later frees |
| 300 | * their blocks (and mi_collect cannot touch abandoned pages) — RSS then |
| 301 | * ratchets across repeated in-process index cycles. The supervised subprocess |
| 302 | * is the primary cure (the child returns 100% RSS on exit); this is the |
| 303 | * in-process fallback for any path that stays in-process (kill switch, |
| 304 | * spawn-fail degrade, embedders). */ |
| 305 | mem_option_set_verified(mi_option_page_reclaim_on_free, 1, "page_reclaim_on_free"); |
| 306 | |
| 307 | /* Every option above is inert unless ordinary malloc actually reaches this |
| 308 | * allocator. That silently stopped being true on Windows — mimalloc's |
| 309 | * static override is gated on _MSC_VER, which clang/MinGW never defines — |
| 310 | * and nothing checked, so a long-lived daemon ratcheted committed memory |
| 311 | * for months (#581). Probe it once, out loud: a real malloc asked whether |
| 312 | * mimalloc owns it. Anything but true means the tuning here is decoration |
| 313 | * and freed pages will not come back. |
| 314 | * |
| 315 | * ...but "anything but true" only means that where the build actually ASKED |
| 316 | * for a global override: prod builds on Windows and Linux. Everywhere else — |