next.js
78f16256 - turbo-tasks-backend: improve print_cache_item_size instrumentation (#91742)

Commit
42 days ago
turbo-tasks-backend: improve print_cache_item_size instrumentation (#91742) ### What? Fixes a compilation hang that occurred when the `print_cache_item_size` debug feature was enabled, and cleans up the surrounding instrumentation code. **Root cause of the hang (double lock / deadlock):** During `persist_snapshot`, the storage iterates over all modified tasks. For each task it calls: ```rust let inner = self.shard.storage.map.get(&task_id).unwrap(); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // acquires a READ lock on the DashMap shard for task_id let item = (self.shard.process)(task_id, &inner, &mut self.buffer); ``` The `process` closure receives `&TaskStorage` while that **read lock is still held**. Inside the closure, the old code called: ```rust self.get_task_name(task_id, turbo_tasks) ``` `get_task_name` creates an `ExecuteContext`, then calls `ctx.task(task_id, TaskDataCategory::Data)`, which calls `storage.access_mut(task_id)`, which calls `self.map.entry(key)`. That `entry()` call tries to acquire a **write lock on the same DashMap shard** — which is already read-locked on the same thread. `parking_lot::RwLock` is not reentrant, so the thread blocks forever waiting for its own read lock to be released. **Fix:** the `&TaskStorage` reference (`inner`) is already in scope inside the closure. Call `inner.get_persistent_task_type()` directly instead of going through `get_task_name`, which avoids any additional lock acquisition. **Additional changes in this PR:** 1. **Make compressed-size reporting opt-in** — split `print_cache_item_size` into two Cargo features. The base feature now shows uncompressed sizes only (no lz4 overhead). A new `print_cache_item_size_with_compressed` feature re-enables compressed-size reporting for when you need it. 2. **Extract helpers to eliminate duplication** — the output block repeated `#[cfg]`-gated `FormatSizes { … }` struct literals six times and duplicated the `task_name` computation twice. Extract: - `FormatSizes` struct + `impl Display` (with `#[cfg]` isolated inside) - `TaskCacheStats::task_name(storage)` — single source for the grouping key - `TaskCacheStats::sort_key()` — encapsulates the `#[cfg]`-switched primary sort field - `TaskCacheStats::format_total/data/avg_data/meta/avg_meta()` — each returning `FormatSizes` ### Why? The compilation was hanging and unusable. The lock ordering bug was non-obvious because the re-entrant acquisition happened across an abstraction boundary (`get_task_name` hiding the `access_mut` call). ### How? Avoid the redundant lock by using the `&TaskStorage` already provided to the closure. Pure refactor for everything else — no behaviour change for any given feature combination. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
Author
Parents
Loading