next.js
025f16a0 - [turbopack] Simplify local task tracking (#93478)

Commit
78 days ago
[turbopack] Simplify local task tracking (#93478) ### What? Replace the `FuturesUnordered<Either<JoinHandle, ...>>` that tracks a global task's in-flight local tasks with a small in-place counter+`Event` bundled into a new `LocalTaskTracker` that lives directly on `CurrentTaskState`. ### Why? `wait_for_local_tasks` only needs a barrier — "have all in-flight local tasks completed?" — not a stream of outputs. `FuturesUnordered` was overkill for that, and its supporting machinery cost an allocation per parent task plus an allocation per local task, plus repeated lock acquisitions per registration and per completion. Per parent task execution, this PR removes: - **One `FuturesUnordered` allocation.** The lazy `Option<FuturesUnordered<…>>` is gone; the counter+event is inline on `CurrentTaskState`. - **One `tokio::sync::oneshot::channel()` allocation per local task.** `priority_runner::schedule_with_join_handle` (and `JoinHandle`) is deleted; local tasks now use plain `schedule`. - **One intrusive `FuturesUnordered::Task` node allocation per local task.** - **One `RwLock<CurrentTaskState>` write-lock acquisition per local task spawn.** The pre-existing `create_local_task` write lock now also bumps the in-flight counter, so we no longer take a separate lock to push into the tracker. - **One `RwLock<CurrentTaskState>` write-lock acquisition per local task completion.** The pre-existing `Scheduled → Done` write lock now also decrements the counter, notifies per-task waiters, and (if the count hit zero) notifies the collective wait event — all under one lock. Net effect on hot paths: - **Spawn**: was 2 allocations + 2 locks; now 0 allocations + 1 lock (the existing `create_local_task` lock). - **Wait when nothing was spawned**: a single read-lock that finds `in_flight == 0` and returns. No allocation, no listener, no await. ### How? - New `LocalTaskTracker` ([`turbopack/crates/turbo-tasks/src/local_task_tracker.rs`](turbopack/crates/turbo-tasks/src/local_task_tracker.rs)) bundles: - `tasks: Vec<LocalTask>` (was a separate field on `CurrentTaskState`), - `in_flight: u32` (plain integer; the surrounding `RwLock` provides synchronization), - `done: Event` (notified on transitions to zero). - `CurrentTaskState::local_tasks` is now `LocalTaskTracker` instead of `Vec<LocalTask>`. The wait-group is part of the same struct, so a parent task that doesn't spawn any local tasks pays nothing beyond the inline fields. - `LocalTaskTracker::create` pushes a new `Scheduled` entry and increments the counter. - `LocalTaskTracker::complete` does the `Scheduled → Done` swap, fires the per-task `done_event` (waking `try_read_local_output` waiters), decrements the counter, and notifies the collective `done` event if the count hit zero — all while holding one write lock. - `wait_for_local_tasks` is the standard double-check pattern (snapshot the counter, register a listener, re-check, await) against the tracker. No loop after the await — the codebase contract is that no new local tasks (or detached test futures) can be registered after the parent's user body returns, except from inside an already-in-flight scope, so a single notify is sufficient. - `spawn_detached_for_testing` uses a tiny `register_detached` / `dec_in_flight` pair (test path; no RAII guard, panics abort upstream). - `priority_runner::schedule_with_join_handle`, `JoinHandle`, the `Option<Sender<()>>` field on `HeapItem`, and the matching `tx.send(())` in `WorkerFuture::poll` are all deleted; nothing else used them. Unit tests for the tracker cover balanced create/complete, balanced detached register/dec, and that `complete` wakes per-task listeners. The existing detached integration tests (including the nested `spawn_detached_for_testing` case in `detached.rs`) still pass. <!-- NEXT_JS_LLM_PR -->
Author
Parents
Loading