Files
next.js/turbopack/crates/turbo-tasks-malloc/Cargo.toml
b9cd44892c Add TurboMalloc::thread_park() to flush and collect on thread park (#92804)
### What?

Adds `TurboMalloc::thread_park()` — a new method on `TurboMalloc` that
calls `libmimalloc_sys::mi_collect(false)` to process mimalloc's lazy
free lists when a Tokio worker thread parks.

Registers it as an `on_thread_park` callback in every Tokio runtime that
already registers `on_thread_stop`:
- `turbopack-cli/src/main.rs`
- `turbopack-cli/benches/small_apps.rs`
- `next-napi-bindings/src/lib.rs`
- `next-build-test/src/main.rs`
- `turbo-tasks-backend/fuzz/src/graph.rs`

### Why?

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 that `mi_collect(true)` would
trigger.

### How?

- `thread_park()` only calls `mi_collect(false)` — it intentionally does
**not** call `flush()`. `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 in `thread_stop()`.
- `thread_park()` is feature-gated behind `cfg(all(feature =
"custom_allocator", not(target_family = "wasm")))` and is a no-op
otherwise.
- `libmimalloc-sys` is added as a direct optional dependency under
`[target.'cfg(not(target_family = "wasm"))'.dependencies]` (it was
already a transitive dep via `mimalloc`) to access `mi_collect`
directly, since `mimalloc`'s `use ffi::*` is not a public re-export.
- In each `on_thread_park` callback, `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.

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Tobias Koppers <[email protected]>
Co-authored-by: Claude <[email protected]>
2026-04-15 03:43:39 +02:00

35 lines
853 B
TOML

[package]
name = "turbo-tasks-malloc"
version = "0.1.0"
description = "A wrapper around mimalloc or the system allocator that tracks allocations"
license = "MIT"
edition = "2024"
autobenches = false
[lib]
bench = false
[dependencies]
[target.'cfg(not(target_family = "wasm"))'.dependencies]
libmimalloc-sys = { version = "0.1.44", features = [
"extended",
], optional = true }
[target.'cfg(not(any(target_os = "linux", target_family = "wasm")))'.dependencies]
mimalloc = { version = "0.1.48", features = [
"v3",
"extended",
], optional = true }
[target.'cfg(all(target_os = "linux", not(target_family = "wasm")))'.dependencies]
mimalloc = { version = "0.1.48", features = [
"v3",
"extended",
"local_dynamic_tls",
], optional = true }
[features]
custom_allocator = ["dep:mimalloc", "dep:libmimalloc-sys"]
default = ["custom_allocator"]