Add TurboMalloc::thread_park() to flush and collect on thread park#92804
Merged
Conversation
…ct on thread park mimalloc defers freeing memory until N more allocations happen. Parked tokio worker threads don't allocate, so deferred frees accumulate. Calling mi_collect(true) on park forces mimalloc to process deferred frees and return memory to the OS. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
lukesandberg
approved these changes
Apr 14, 2026
Contributor
Tests Passed |
Per review feedback: collect deferred mimalloc frees after the SWC atom store purge so that the atoms freed by GC are also reclaimed in the same collection pass. Co-Authored-By: Claude <noreply@anthropic.com>
Per review: mi_collect(false) processes deferred frees without triggering syscalls or globally synchronized operations, which is sufficient for reclaiming memory freed by parked threads and less expensive than force=true. Co-Authored-By: Claude <noreply@anthropic.com>
Merging this PR will not alter performance
Comparing Footnotes
|
…iant The trace server assumes thread allocation counters only go up. flush() resets the thread-local buffer to zero (transferring the value to the global atomic), which violates that assumption. thread_park() only needs mi_collect() to process deferred frees. Co-Authored-By: Claude <noreply@anthropic.com>
Contributor
Stats from current PR✅ No significant changes detected📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📎 Tarball URL |
This was referenced Apr 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Adds
TurboMalloc::thread_park()— a new method onTurboMallocthat callslibmimalloc_sys::mi_collect(false)to process mimalloc's lazy free lists when a Tokio worker thread parks.Registers it as an
on_thread_parkcallback in every Tokio runtime that already registerson_thread_stop:turbopack-cli/src/main.rsturbopack-cli/benches/small_apps.rsnext-napi-bindings/src/lib.rsnext-build-test/src/main.rsturbo-tasks-backend/fuzz/src/graph.rsWhy?
mimalloc uses deferred freeing: memory freed by one thread is not reclaimed immediately but is queued until that thread performs N more allocations. Tokio worker threads that are parked (waiting for work) don't allocate, so deferred frees accumulate indefinitely — memory is never returned to the OS until the thread wakes up and does real work.
Calling
mi_collect(false)on park processes the lazy free lists, allowing mimalloc to reclaim that memory, while avoiding the syscalls and globally synchronized operations thatmi_collect(true)would trigger.How?
thread_park()only callsmi_collect(false)— it intentionally does not callflush().flush()transfers the thread-local allocation counter to the global atomic and resets it to zero, which would violate the trace server's assumption that per-thread counters are monotonically increasing. Counter sync remains exclusively inthread_stop().thread_park()is feature-gated behindcfg(all(feature = "custom_allocator", not(target_family = "wasm")))and is a no-op otherwise.libmimalloc-sysis added as a direct optional dependency under[target.'cfg(not(target_family = "wasm"))'.dependencies](it was already a transitive dep viamimalloc) to accessmi_collectdirectly, sincemimalloc'suse ffi::*is not a public re-export.on_thread_parkcallback,TurboMalloc::thread_park()is called after the existing SWC atom store GC, so atoms freed by that GC pass are also eligible for reclamation in the same collection.