Files
Marcos HernanzandGitHub a80c0c0588 Cache immutable current task IDs outside the task-state lock (#96180)
## What changed

Introduce a cloneable `CurrentTaskStateHandle` that keeps the
execution's immutable `Option<TaskId>` beside the existing shared
`Arc<RwLock<CurrentTaskState>>`.

Global tasks, local tasks, detached futures, and top-level runs continue
to share the same mutable state and task-local scope. Only current-task
ID reads bypass the lock; all other state remains behind the existing
`RwLock`.

This was split from #96179 following review feedback. It is
source-independent and changes only
`turbopack/crates/turbo-tasks/src/manager.rs`.

## Why

`current_task_if_available` and `current_task` acquired a shared
standard-library `RwLock` on every native function call and tracked
read, even though the task ID is fixed for the entire execution. The
lock and its contention path remained visible in both HMR and production
profiles.

## Performance evidence

Measured at `0950e8ae05db20c33302f02257114f5b857edabc` as an incremental
change on top of the independently proposed immutable-read bypass in
#96179.

These measurements are directional evidence, not a precise standalone
estimate. Changes around 1–2% are difficult to measure reliably. The
#96179 and #96180 campaigns used separate run sets, and #96179's
absolute endpoint does not match this campaign's starting point; that
mismatch demonstrates cross-campaign noise. The percentages must not be
added, and the absolute values must not be compared across the two PRs.
The standalone GitHub aggregate benchmark also moved in the opposite
direction, reinforcing that build-level percentages are inconclusive at
this scale.

### Browser-driven HMR, 30-second B/C/C/B

- Evaluation: 19.772807 → 19.356650 ms (**-2.105%**)
- Commit: 20.632962 → 20.167726 ms (**-2.255%**)
- Both candidate runs beat both controls for both metrics.

### Production builds

- Matrix 1: **-1.804%**
- Complementary matrix 2: **-1.474%**
- Combined 24 campaigns: 1,641.839610 → 1,614.918617 ms (**-1.640%**)
- All six balanced blocks favored the candidate.
- Six-block mean: -1.636%, with 95% interval **[-2.718%, -0.555%]**.

### Causal profiles

- The `current_task_if_available` helper and contended read-lock symbols
disappeared from candidate profiles.
- HMR voluntary context switches fell 2.700%.
- Production voluntary context switches fell 2.576%.
- All eight accepted perf captures reported zero lost samples.

## Validation

On the exact standalone head
(`bce272743ff44021fda204d98924bf8519cd8c1d`):

- Complete `turbo-tasks` package tests and doctests
- Complete `turbo-tasks-backend` package tests, integrations, and
doctests
- `cargo fmt --check -p turbo-tasks -p turbo-tasks-backend`
- `cargo clippy -p turbo-tasks -p turbo-tasks-backend --all-targets --
-D warnings`
- `git diff --check`
- Conflict-free merge-tree check against current `canary` (`91c6309c52`)
2026-07-29 15:22:34 -04:00
..
2026-07-17 15:39:16 -07:00

Turbo Tasks

An incremental computation system that uses macros and types to automate the caching process.

For a high-level overview, start by reading Inside Turbopack: Building Faster by Building Less.

Turbo Tasks defines 4 primitives:

  • [Functions][macro@crate::function]: Units of execution, invalidation, and reexecution.
  • [Values][macro@crate::value]: Data created, stored, and returned by functions.
  • [Traits][macro@crate::value_trait]: Traits that define a set of functions on values.
  • [Collectibles][crate::TurboTasks::emit_collectible]: Values emitted in functions that bubble up the call graph and can be collected in parent functions. Collectibles are deduplicated by cell id equality.

It defines some derived elements from that:

  • Tasks: An instance of a function together with its arguments.
  • Vcs ("Value Cells"): References to locations associated with tasks where values are stored. The contents of a cell can change after the reexecution of a function due to invalidation. A Vc can be read to get [a read-only reference][crate::ReadRef] to the stored data, representing a snapshot of that cell at that point in time.

There are a few design patterns that are commonly used with Turbo Tasks:

  • [Singleton Pattern][crate::_singleton_pattern]: Use a private constructor function to ensure a 1:1 mapping between values and value cells.

Functions and Tasks

An example turbo-task function

[#[turbo_tasks::function]][crate::function]s are memoized functions within the build process. An instance of a function with arguments is called a task. They are responsible for:

  • Tracking Dependencies: Each task keeps track of its dependencies to determine when a recomputation is necessary. Dependencies are tracked when a [Vc<T>][crate::Vc] (Value Cell) is awaited.
  • Recomputing Changes: When a dependency changes, the affected tasks are automatically recomputed.
  • Parallel Execution: Every task is spawned as a Tokio task, which uses Tokio's multithreaded work-stealing executor.

Task Graph

An example of a task graph
These example call trees represent an initial (cold) execution, the “mark dirty” operation when a file has been changed, and the propagation from the leaf up to the root.

All tasks and their dependencies form a task graph.

This graph is crucial for invalidation propagation. When a task is invalidated, the changes propagate through the graph, triggering rebuilds where necessary.

Incremental Builds

Upon execution of functions, turbo-tasks will track which Vcs are read. Once any of these change, turbo-tasks will invalidate the task created from the function's execution and it will eventually be scheduled and reexecuted.

After initial execution, turbo-tasks employs a bottom-up approach for incremental rebuilds.

By rebuilding invalidated tasks, only the parts of the graph affected by changes are rebuilt, leaving untouched parts intact. No work is done for unchanged parts.