mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
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`)
This commit is contained in:
@@ -4,6 +4,7 @@ use std::{
|
||||
future::Future,
|
||||
hash::{BuildHasher, BuildHasherDefault},
|
||||
mem::take,
|
||||
ops::Deref,
|
||||
panic::AssertUnwindSafe,
|
||||
pin::Pin,
|
||||
process::abort,
|
||||
@@ -465,7 +466,7 @@ enum ScheduledTask {
|
||||
ty: LocalTaskSpec,
|
||||
persistence: TaskPersistence,
|
||||
local_task_id: LocalTaskId,
|
||||
global_task_state: Arc<RwLock<CurrentTaskState>>,
|
||||
global_task_state: CurrentTaskStateHandle,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
@@ -576,12 +577,48 @@ impl CurrentTaskState {
|
||||
}
|
||||
}
|
||||
|
||||
/// A shareable current-task state handle with the immutable task ID cached
|
||||
/// outside the lock. The rest of the state is mutated by global and local
|
||||
/// tasks, but the task ID is fixed for the lifetime of an execution.
|
||||
#[derive(Clone)]
|
||||
struct CurrentTaskStateHandle {
|
||||
inner: Arc<CurrentTaskStateInner>,
|
||||
}
|
||||
|
||||
struct CurrentTaskStateInner {
|
||||
current_task_id: Option<TaskId>,
|
||||
state: RwLock<CurrentTaskState>,
|
||||
}
|
||||
|
||||
impl CurrentTaskStateHandle {
|
||||
fn new(state: CurrentTaskState) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(CurrentTaskStateInner {
|
||||
current_task_id: state.task_id,
|
||||
state: RwLock::new(state),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_task_id(&self) -> Option<TaskId> {
|
||||
self.inner.current_task_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CurrentTaskStateHandle {
|
||||
type Target = RwLock<CurrentTaskState>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner.state
|
||||
}
|
||||
}
|
||||
|
||||
// TODO implement our own thread pool and make these thread locals instead
|
||||
task_local! {
|
||||
/// The current TurboTasks instance
|
||||
static TURBO_TASKS: Arc<dyn TurboTasksApi>;
|
||||
|
||||
static CURRENT_TASK_STATE: Arc<RwLock<CurrentTaskState>>;
|
||||
static CURRENT_TASK_STATE: CurrentTaskStateHandle;
|
||||
|
||||
/// Temporarily suppresses the eventual consistency check in top-level tasks.
|
||||
/// This is used by strongly consistent reads to allow them to succeed in top-level tasks.
|
||||
@@ -698,11 +735,11 @@ impl<B: Backend + 'static> TurboTasks<B> {
|
||||
self.begin_foreground_job();
|
||||
// it's okay for execution ids to overflow and wrap, they're just used for an assert
|
||||
let execution_id = self.execution_id_factory.wrapping_get();
|
||||
let current_task_state = Arc::new(RwLock::new(CurrentTaskState::new_temporary(
|
||||
let current_task_state = CurrentTaskStateHandle::new(CurrentTaskState::new_temporary(
|
||||
execution_id,
|
||||
TaskPriority::initial(),
|
||||
true, // in_top_level_task
|
||||
)));
|
||||
));
|
||||
|
||||
let result = TURBO_TASKS
|
||||
.scope(
|
||||
@@ -856,7 +893,7 @@ impl<B: Backend + 'static> TurboTasks<B> {
|
||||
let mut gts_write = gts.write().unwrap();
|
||||
let local_task_id = gts_write.local_tasks.create(task_type);
|
||||
(
|
||||
Arc::clone(gts),
|
||||
gts.clone(),
|
||||
gts_write.execution_id,
|
||||
gts_write.priority,
|
||||
local_task_id,
|
||||
@@ -1185,12 +1222,13 @@ impl<B: Backend> Executor<TurboTasks<B>, ScheduledTask, TaskPriority> for TurboT
|
||||
// it's okay for execution ids to overflow and wrap, they're just used
|
||||
// for an assert
|
||||
let execution_id = this.execution_id_factory.wrapping_get();
|
||||
let current_task_state = Arc::new(RwLock::new(CurrentTaskState::new(
|
||||
task_id,
|
||||
execution_id,
|
||||
priority,
|
||||
false, // in_top_level_task
|
||||
)));
|
||||
let current_task_state =
|
||||
CurrentTaskStateHandle::new(CurrentTaskState::new(
|
||||
task_id,
|
||||
execution_id,
|
||||
priority,
|
||||
false, // in_top_level_task
|
||||
));
|
||||
let single_execution_future = async {
|
||||
if this.stopped.load(Ordering::Acquire) {
|
||||
this.backend.task_execution_canceled(task_id, &*this);
|
||||
@@ -1629,7 +1667,7 @@ async fn wait_for_local_tasks() {
|
||||
}
|
||||
|
||||
pub(crate) fn current_task_if_available(from: &str) -> Option<TaskId> {
|
||||
match CURRENT_TASK_STATE.try_with(|ts| ts.read().unwrap().task_id) {
|
||||
match CURRENT_TASK_STATE.try_with(|ts| ts.current_task_id()) {
|
||||
Ok(id) => id,
|
||||
Err(_) => panic!(
|
||||
"{from} can only be used in the context of a turbo_tasks task execution or \
|
||||
@@ -1639,7 +1677,7 @@ pub(crate) fn current_task_if_available(from: &str) -> Option<TaskId> {
|
||||
}
|
||||
|
||||
pub(crate) fn current_task(from: &str) -> TaskId {
|
||||
match CURRENT_TASK_STATE.try_with(|ts| ts.read().unwrap().task_id) {
|
||||
match CURRENT_TASK_STATE.try_with(|ts| ts.current_task_id()) {
|
||||
Ok(Some(id)) => id,
|
||||
Ok(None) | Err(_) => {
|
||||
panic!("{from} can only be used in the context of a turbo_tasks task execution")
|
||||
|
||||
Reference in New Issue
Block a user