From 88cfe96ebfdd57fb18d6de9c4ec0d84071393aa1 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 29 Oct 2025 09:56:07 +0100 Subject: [PATCH 1/6] add env var to allow a single run of the test case --- .../crates/turbo-tasks-testing/src/run.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/turbopack/crates/turbo-tasks-testing/src/run.rs b/turbopack/crates/turbo-tasks-testing/src/run.rs index 0371850f0ee78..565c80e31606f 100644 --- a/turbopack/crates/turbo-tasks-testing/src/run.rs +++ b/turbopack/crates/turbo-tasks-testing/src/run.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, future::Future, sync::Arc}; +use std::{env, fmt::Debug, future::Future, sync::Arc}; use anyhow::Result; use turbo_tasks::{TurboTasksApi, trace::TraceRawVcs}; @@ -98,22 +98,28 @@ where F: Future> + Send + 'static, T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static, { + let single_run = env::var("SINGLE_RUN").is_ok(); let name = closure_to_name(&fut); let tt = registration.create_turbo_tasks(&name, true); println!("Run #1 (without cache)"); let start = std::time::Instant::now(); let first = fut(tt.clone()).await?; println!("Run #1 took {:?}", start.elapsed()); - for i in 2..10 { - println!("Run #{i} (with memory cache, same TurboTasks instance)"); - let start = std::time::Instant::now(); - let second = fut(tt.clone()).await?; - println!("Run #{i} took {:?}", start.elapsed()); - assert_eq!(first, second); + if !single_run { + for i in 2..10 { + println!("Run #{i} (with memory cache, same TurboTasks instance)"); + let start = std::time::Instant::now(); + let second = fut(tt.clone()).await?; + println!("Run #{i} took {:?}", start.elapsed()); + assert_eq!(first, second); + } } let start = std::time::Instant::now(); tt.stop_and_wait().await; println!("Stopping TurboTasks took {:?}", start.elapsed()); + if single_run { + return Ok(()); + } for i in 10..20 { let tt = registration.create_turbo_tasks(&name, false); println!("Run #{i} (with filesystem cache if available, new TurboTasks instance)"); From 225da4a305d941c9c862b7c7e9ae837780334488 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 29 Oct 2025 10:00:52 +0100 Subject: [PATCH 2/6] Turbopack: double execute tasks to check determinism --- .../turbo-tasks-backend/src/backend/mod.rs | 32 ++++++++--- .../src/backend/operation/update_cell.rs | 53 ++++++++++++++----- .../turbo-tasks-backend/tests/detached.rs | 3 ++ 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 3d1d9674a4543..bdfa633e75ee2 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -423,6 +423,7 @@ struct TaskExecutionCompletePrepareResult { pub new_children: FxHashSet, pub removed_data: Vec, pub is_now_immutable: bool, + pub no_output_set: bool, pub new_output: Option, pub output_dependent_tasks: SmallVec<[TaskId; 4]>, } @@ -1765,6 +1766,7 @@ impl TurboTasksBackendInner { new_children, mut removed_data, is_now_immutable, + no_output_set, new_output, output_dependent_tasks, }) = self.task_execution_completed_prepare( @@ -1809,6 +1811,7 @@ impl TurboTasksBackendInner { if self.task_execution_completed_finish( &mut ctx, task_id, + no_output_set, new_output, &mut removed_data, is_now_immutable, @@ -1843,6 +1846,7 @@ impl TurboTasksBackendInner { new_children: Default::default(), removed_data: Default::default(), is_now_immutable: false, + no_output_set: false, new_output: None, output_dependent_tasks: Default::default(), }); @@ -2021,6 +2025,7 @@ impl TurboTasksBackendInner { // Check if output need to be updated let current_output = get!(task, Output); + let no_output_set = current_output.is_none(); let new_output = match result { Ok(RawVc::TaskOutput(output_task_id)) => { if let Some(OutputValue::Output(current_task_id)) = current_output @@ -2092,6 +2097,7 @@ impl TurboTasksBackendInner { new_children, removed_data, is_now_immutable, + no_output_set, new_output, output_dependent_tasks, }) @@ -2232,6 +2238,7 @@ impl TurboTasksBackendInner { &self, ctx: &mut impl ExecuteContext<'_>, task_id: TaskId, + no_output_set: bool, new_output: Option, removed_data: &mut Vec, is_now_immutable: bool, @@ -2306,7 +2313,8 @@ impl TurboTasksBackendInner { None }; - let data_update = if old_dirty_state != new_dirty_state { + let dirty_changed = old_dirty_state != new_dirty_state; + let data_update = if dirty_changed { if let Some(new_dirty_state) = new_dirty_state { task.insert(CachedDataItem::Dirty { value: new_dirty_state, @@ -2353,17 +2361,29 @@ impl TurboTasksBackendInner { None }; - drop(task); - drop(old_content); + let reschedule = (dirty_changed || no_output_set) && !task_id.is_transient(); + if reschedule { + task.add_new(CachedDataItem::InProgress { + value: InProgressState::Scheduled { + done_event, + reason: TaskExecutionReason::Stale, + }, + }); + drop(task); + } else { + drop(task); - // Notify dependent tasks that are waiting for this task to finish - done_event.notify(usize::MAX); + // Notify dependent tasks that are waiting for this task to finish + done_event.notify(usize::MAX); + } + + drop(old_content); if let Some(data_update) = data_update { AggregationUpdateQueue::run(data_update, ctx); } - false + reschedule } fn task_execution_completed_cleanup(&self, ctx: &mut impl ExecuteContext<'_>, task_id: TaskId) { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 70dee0010a772..b915c44263700 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -2,7 +2,7 @@ use std::mem::take; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use turbo_tasks::{CellId, TaskId, TypedSharedReference, backend::CellContent}; +use turbo_tasks::{CellId, TaskId, TypedSharedReference, backend::CellContent, registry}; #[cfg(feature = "trace_task_dirty")] use crate::backend::operation::invalidate::TaskDirtyCause; @@ -13,7 +13,7 @@ use crate::{ AggregationUpdateQueue, ExecuteContext, Operation, TaskGuard, invalidate::make_task_dirty_internal, }, - storage::{get_many, remove}, + storage::{get, get_many, remove}, }, data::{CachedDataItem, CachedDataItemKey, CellRef}, }; @@ -41,17 +41,49 @@ pub enum UpdateCellOperation { impl UpdateCellOperation { pub fn run(task_id: TaskId, cell: CellId, content: CellContent, mut ctx: impl ExecuteContext) { + let content = if let CellContent(Some(new_content)) = content { + Some(new_content.into_typed(cell.type_id)) + } else { + None + }; + let mut task = ctx.task(task_id, TaskDataCategory::All); + let is_stateful = task.has_key(&CachedDataItemKey::Stateful {}); // We need to detect recomputation, because here the content has not actually changed (even // if it's not equal to the old content, as not all values implement Eq). We have to // assume that tasks are deterministic and pure. - let should_invalidate = ctx.should_track_dependencies() - && (task.has_key(&CachedDataItemKey::Dirty {}) || + let assume_unchanged = !ctx.should_track_dependencies() + || (!task.has_key(&CachedDataItemKey::Dirty {}) // This is a hack for the streaming hack. Stateful tasks are never recomputed, so this forces invalidation for them in case of this hack. - task.has_key(&CachedDataItemKey::Stateful {})); + && !is_stateful); + + let old_content = get!(task, CellData { cell }); + + if assume_unchanged { + if old_content.is_some() { + // Never update cells when recomputing if they already have a value. + // It's not expected that content changes during recomputation. + + // Check if this assumption holds. + if !is_stateful && content.as_ref() != old_content { + let task_description = ctx.get_task_description(task_id); + let cell_type = registry::get_value_type(cell.type_id).global_name; + println!( + "Task {} updated cell #{} (type: {}) while recomputing", + task_description, cell.index, cell_type + ); + } + return; + } else { + // Initial computation, or computation after a cell has been cleared. + // We can just set the content, but we don't want to notify dependent tasks, + // as we assume that content hasn't changed (deterministic tasks). + } + } else { + // When not recomputing, we need to notify dependent tasks if the content actually + // changes. - if should_invalidate { let dependent_tasks: SmallVec<[TaskId; 4]> = get_many!( task, CellDependent { cell: dependent_cell, task } @@ -78,12 +110,6 @@ impl UpdateCellOperation { drop(task); drop(old_content); - let content = if let CellContent(Some(new_content)) = content { - Some(new_content.into_typed(cell.type_id)) - } else { - None - }; - UpdateCellOperation::InvalidateWhenCellDependency { cell_ref: CellRef { task: task_id, @@ -101,8 +127,7 @@ impl UpdateCellOperation { // Fast path: We don't need to invalidate anything. // So we can just update the cell content. - let old_content = if let CellContent(Some(new_content)) = content { - let new_content = new_content.into_typed(cell.type_id); + let old_content = if let Some(new_content) = content { task.insert(CachedDataItem::CellData { cell, value: new_content, diff --git a/turbopack/crates/turbo-tasks-backend/tests/detached.rs b/turbopack/crates/turbo-tasks-backend/tests/detached.rs index 3e6a893c16181..cbd9888e92d28 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/detached.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/detached.rs @@ -18,6 +18,7 @@ static REGISTRATION: Registration = register!(); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_spawns_detached() -> anyhow::Result<()> { run_once(®ISTRATION, || async { + println!("test_spawns_detached"); // HACK: The watch channel we use has an incorrect implementation of `TraceRawVcs`, just // disable GC for the test so this can't cause any problems. prevent_gc(); @@ -73,7 +74,9 @@ async fn spawns_detached( sender: TransientInstance>>>, ) -> Vc<()> { tokio::spawn(turbo_tasks().detached_for_testing(Box::pin(async move { + println!("spawns_detached: waiting for notify"); notify.0.notified().await; + println!("spawns_detached: notified, sending value"); // creating cells after the normal lifetime of the task should be okay, as the parent task // is waiting on us before exiting! sender.0.send(Some(Vc::cell(42))).unwrap(); From 109642ec11df5120ceefd37fbee1da4a1926442f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 29 Oct 2025 15:30:10 +0100 Subject: [PATCH 3/6] skip error for cell = "new" types --- .../turbo-tasks-backend/src/backend/mod.rs | 6 +- .../src/backend/operation/update_cell.rs | 10 ++- .../crates/turbo-tasks-testing/src/lib.rs | 8 ++- turbopack/crates/turbo-tasks/src/backend.rs | 1 + turbopack/crates/turbo-tasks/src/manager.rs | 70 +++++++++++++------ .../crates/turbo-tasks/src/vc/cell_mode.rs | 4 +- .../crates/turbopack-node/src/evaluate.rs | 19 ++--- 7 files changed, 83 insertions(+), 35 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index bdfa633e75ee2..d8cad9436075f 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -2672,12 +2672,14 @@ impl TurboTasksBackendInner { task_id: TaskId, cell: CellId, content: CellContent, + never_equal: bool, turbo_tasks: &dyn TurboTasksBackendApi>, ) { operation::UpdateCellOperation::run( task_id, cell, content, + never_equal, self.execute_context(turbo_tasks), ); } @@ -3271,9 +3273,11 @@ impl Backend for TurboTasksBackend { task_id: TaskId, cell: CellId, content: CellContent, + never_equal: bool, turbo_tasks: &dyn TurboTasksBackendApi, ) { - self.0.update_task_cell(task_id, cell, content, turbo_tasks); + self.0 + .update_task_cell(task_id, cell, content, never_equal, turbo_tasks); } fn mark_own_task_as_finished( diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index b915c44263700..39db588c96e5f 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -40,7 +40,13 @@ pub enum UpdateCellOperation { } impl UpdateCellOperation { - pub fn run(task_id: TaskId, cell: CellId, content: CellContent, mut ctx: impl ExecuteContext) { + pub fn run( + task_id: TaskId, + cell: CellId, + content: CellContent, + never_equal: bool, + mut ctx: impl ExecuteContext, + ) { let content = if let CellContent(Some(new_content)) = content { Some(new_content.into_typed(cell.type_id)) } else { @@ -66,7 +72,7 @@ impl UpdateCellOperation { // It's not expected that content changes during recomputation. // Check if this assumption holds. - if !is_stateful && content.as_ref() != old_content { + if !is_stateful && !never_equal && content.as_ref() != old_content { let task_description = ctx.get_task_description(task_id); let cell_type = registry::get_value_type(cell.type_id).global_name; println!( diff --git a/turbopack/crates/turbo-tasks-testing/src/lib.rs b/turbopack/crates/turbo-tasks-testing/src/lib.rs index 34564c3e75c3d..ea7ad135d1c95 100644 --- a/turbopack/crates/turbo-tasks-testing/src/lib.rs +++ b/turbopack/crates/turbo-tasks-testing/src/lib.rs @@ -261,7 +261,13 @@ impl TurboTasksApi for VcStorage { .into_typed(index.type_id)) } - fn update_own_task_cell(&self, task: TaskId, index: CellId, content: CellContent) { + fn update_own_task_cell( + &self, + task: TaskId, + index: CellId, + content: CellContent, + _never_equal: bool, + ) { let mut map = self.cells.lock().unwrap(); let cell = map.entry((task, index)).or_default(); *cell = content; diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index 70f4147aa166a..393c53a460165 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -621,6 +621,7 @@ pub trait Backend: Sync + Send { task: TaskId, index: CellId, content: CellContent, + never_equal: bool, turbo_tasks: &dyn TurboTasksBackendApi, ); diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index f880f414f8943..0af2fde185ad9 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -155,7 +155,13 @@ pub trait TurboTasksApi: TurboTasksCallApi + Sync + Send { index: CellId, options: ReadCellOptions, ) -> Result; - fn update_own_task_cell(&self, task: TaskId, index: CellId, content: CellContent); + fn update_own_task_cell( + &self, + task: TaskId, + index: CellId, + content: CellContent, + never_equal: bool, + ); fn mark_own_task_as_finished(&self, task: TaskId); fn set_own_task_aggregation_number(&self, task: TaskId, aggregation_number: u32); fn mark_own_task_as_session_dependent(&self, task: TaskId); @@ -1354,8 +1360,15 @@ impl TurboTasksApi for TurboTasks { self.try_read_own_task_cell(task, index, options) } - fn update_own_task_cell(&self, task: TaskId, index: CellId, content: CellContent) { - self.backend.update_task_cell(task, index, content, self); + fn update_own_task_cell( + &self, + task: TaskId, + index: CellId, + content: CellContent, + never_equal: bool, + ) { + self.backend + .update_task_cell(task, index, content, never_equal, self); } fn connect_task(&self, task: TaskId) { @@ -1769,7 +1782,12 @@ impl CurrentCellRef { .ok(); let update = functor(cell_content.as_ref().and_then(|cc| cc.1.0.as_ref())); if let Some(update) = update { - tt.update_own_task_cell(self.current_task, self.index, CellContent(Some(update))) + tt.update_own_task_cell( + self.current_task, + self.index, + CellContent(Some(update)), + false, + ) } } @@ -1851,7 +1869,7 @@ impl CurrentCellRef { } /// Unconditionally updates the content of the cell. - pub fn update(&self, new_value: T) + pub fn update(&self, new_value: T, never_equal: bool) where T: VcValueType, { @@ -1862,6 +1880,7 @@ impl CurrentCellRef { CellContent(Some(SharedReference::new(triomphe::Arc::new( >::value_to_repr(new_value), )))), + never_equal, ) } @@ -1873,27 +1892,36 @@ impl CurrentCellRef { /// /// The [`SharedReference`] is expected to use the `>::Repr` type for its representation of the value. - pub fn update_with_shared_reference(&self, shared_ref: SharedReference) { + pub fn update_with_shared_reference(&self, shared_ref: SharedReference, never_equal: bool) { let tt = turbo_tasks(); - let content = tt - .read_own_task_cell( - self.current_task, - self.index, - ReadCellOptions { - // INVALIDATION: Reading our own cell must be untracked - tracking: ReadTracking::Untracked, - ..Default::default() - }, - ) - .ok(); - let update = if let Some(TypedCellContent(_, CellContent(Some(shared_ref_exp)))) = content { - // pointer equality (not value equality) - shared_ref_exp != shared_ref + let update = if !never_equal { + let content = tt + .read_own_task_cell( + self.current_task, + self.index, + ReadCellOptions { + // INVALIDATION: Reading our own cell must be untracked + tracking: ReadTracking::Untracked, + ..Default::default() + }, + ) + .ok(); + if let Some(TypedCellContent(_, CellContent(Some(shared_ref_exp)))) = content { + // pointer equality (not value equality) + shared_ref_exp != shared_ref + } else { + true + } } else { true }; if update { - tt.update_own_task_cell(self.current_task, self.index, CellContent(Some(shared_ref))) + tt.update_own_task_cell( + self.current_task, + self.index, + CellContent(Some(shared_ref)), + never_equal, + ) } } } diff --git a/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs b/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs index 6aabd4c956d03..009957c303fa3 100644 --- a/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs +++ b/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs @@ -37,7 +37,7 @@ where { fn cell(inner: VcReadTarget) -> Vc { let cell = find_cell_by_type(T::get_value_type_id()); - cell.update(>::target_to_value(inner)); + cell.update(>::target_to_value(inner), true); Vc { node: cell.into(), _t: PhantomData, @@ -47,7 +47,7 @@ where fn raw_cell(content: TypedSharedReference) -> RawVc { debug_assert_repr::(&content); let cell = find_cell_by_type(content.type_id); - cell.update_with_shared_reference(content.reference); + cell.update_with_shared_reference(content.reference, true); cell.into() } } diff --git a/turbopack/crates/turbopack-node/src/evaluate.rs b/turbopack/crates/turbopack-node/src/evaluate.rs index f9a2537d0985f..22f7e95164f5f 100644 --- a/turbopack/crates/turbopack-node/src/evaluate.rs +++ b/turbopack/crates/turbopack-node/src/evaluate.rs @@ -401,10 +401,10 @@ pub async fn custom_evaluate( // We initialize the cell with a stream that is open, but has no values. // The first [compute_evaluate_stream] pipe call will pick up that stream. let (sender, receiver) = unbounded(); - cell.update(JavaScriptEvaluation(JavaScriptStream::new_open( - vec![], - Box::new(receiver), - ))); + cell.update( + JavaScriptEvaluation(JavaScriptStream::new_open(vec![], Box::new(receiver))), + true, + ); let initial = Mutex::new(Some(sender)); // run the evaluation as side effect @@ -418,10 +418,13 @@ pub async fn custom_evaluate( // In cases when only [compute_evaluate_stream] is (re)executed, we need to // update the old stream with a new value. let (sender, receiver) = unbounded(); - cell.update(JavaScriptEvaluation(JavaScriptStream::new_open( - vec![], - Box::new(receiver), - ))); + cell.update( + JavaScriptEvaluation(JavaScriptStream::new_open( + vec![], + Box::new(receiver), + )), + true, + ); sender } }), From d342a7bbf6a65c03917cbfd14a2d4e1f28ad8732 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 30 Oct 2025 11:34:06 +0100 Subject: [PATCH 4/6] add verify_determinism feature --- turbopack/crates/turbo-tasks-backend/Cargo.toml | 1 + .../crates/turbo-tasks-backend/src/backend/mod.rs | 11 ++++++++++- .../src/backend/operation/update_cell.rs | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index 4513aafb16ca4..51f50ca4fc15b 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -19,6 +19,7 @@ no_fast_stale = [] verify_serialization = [] verify_aggregation_graph = [] verify_immutable = [] +verify_determinism = [] trace_aggregation_update = [] trace_find_and_schedule = [] trace_task_completion = [] diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index d8cad9436075f..b1df00c7b7fc5 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -423,6 +423,7 @@ struct TaskExecutionCompletePrepareResult { pub new_children: FxHashSet, pub removed_data: Vec, pub is_now_immutable: bool, + #[cfg(feature = "verify_determinism")] pub no_output_set: bool, pub new_output: Option, pub output_dependent_tasks: SmallVec<[TaskId; 4]>, @@ -1766,6 +1767,7 @@ impl TurboTasksBackendInner { new_children, mut removed_data, is_now_immutable, + #[cfg(feature = "verify_determinism")] no_output_set, new_output, output_dependent_tasks, @@ -1811,6 +1813,7 @@ impl TurboTasksBackendInner { if self.task_execution_completed_finish( &mut ctx, task_id, + #[cfg(feature = "verify_determinism")] no_output_set, new_output, &mut removed_data, @@ -1846,6 +1849,7 @@ impl TurboTasksBackendInner { new_children: Default::default(), removed_data: Default::default(), is_now_immutable: false, + #[cfg(feature = "verify_determinism")] no_output_set: false, new_output: None, output_dependent_tasks: Default::default(), @@ -2025,6 +2029,7 @@ impl TurboTasksBackendInner { // Check if output need to be updated let current_output = get!(task, Output); + #[cfg(feature = "verify_determinism")] let no_output_set = current_output.is_none(); let new_output = match result { Ok(RawVc::TaskOutput(output_task_id)) => { @@ -2097,6 +2102,7 @@ impl TurboTasksBackendInner { new_children, removed_data, is_now_immutable, + #[cfg(feature = "verify_determinism")] no_output_set, new_output, output_dependent_tasks, @@ -2238,7 +2244,7 @@ impl TurboTasksBackendInner { &self, ctx: &mut impl ExecuteContext<'_>, task_id: TaskId, - no_output_set: bool, + #[cfg(feature = "verify_determinism")] no_output_set: bool, new_output: Option, removed_data: &mut Vec, is_now_immutable: bool, @@ -2361,7 +2367,10 @@ impl TurboTasksBackendInner { None }; + #[cfg(feature = "verify_determinism")] let reschedule = (dirty_changed || no_output_set) && !task_id.is_transient(); + #[cfg(not(feature = "verify_determinism"))] + let reschedule = false; if reschedule { task.add_new(CachedDataItem::InProgress { value: InProgressState::Scheduled { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 39db588c96e5f..1ee65aa852814 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -44,7 +44,8 @@ impl UpdateCellOperation { task_id: TaskId, cell: CellId, content: CellContent, - never_equal: bool, + #[cfg(feature = "verify_determinism")] never_equal: bool, + #[cfg(not(feature = "verify_determinism"))] _never_equal: bool, mut ctx: impl ExecuteContext, ) { let content = if let CellContent(Some(new_content)) = content { @@ -72,6 +73,7 @@ impl UpdateCellOperation { // It's not expected that content changes during recomputation. // Check if this assumption holds. + #[cfg(feature = "verify_determinism")] if !is_stateful && !never_equal && content.as_ref() != old_content { let task_description = ctx.get_task_description(task_id); let cell_type = registry::get_value_type(cell.type_id).global_name; From cfd8cd7656a5df2e396232ccef113ab72b96c301 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 30 Oct 2025 14:00:30 +0100 Subject: [PATCH 5/6] fixup --- .../turbo-tasks-backend/src/backend/operation/update_cell.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 1ee65aa852814..6f2e25738304e 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -2,7 +2,7 @@ use std::mem::take; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use turbo_tasks::{CellId, TaskId, TypedSharedReference, backend::CellContent, registry}; +use turbo_tasks::{CellId, TaskId, TypedSharedReference, backend::CellContent}; #[cfg(feature = "trace_task_dirty")] use crate::backend::operation::invalidate::TaskDirtyCause; @@ -76,7 +76,7 @@ impl UpdateCellOperation { #[cfg(feature = "verify_determinism")] if !is_stateful && !never_equal && content.as_ref() != old_content { let task_description = ctx.get_task_description(task_id); - let cell_type = registry::get_value_type(cell.type_id).global_name; + let cell_type = turbo_tasks::registry::get_value_type(cell.type_id).global_name; println!( "Task {} updated cell #{} (type: {}) while recomputing", task_description, cell.index, cell_type From cb673586d78ac1874ffca798e257d9bbffc2ccc7 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 31 Oct 2025 10:10:44 +0100 Subject: [PATCH 6/6] review fixups --- .../turbo-tasks-backend/src/backend/mod.rs | 10 ++++---- .../src/backend/operation/update_cell.rs | 13 ++++++---- .../crates/turbo-tasks-testing/src/lib.rs | 4 ++-- turbopack/crates/turbo-tasks/src/backend.rs | 7 +++++- turbopack/crates/turbo-tasks/src/manager.rs | 24 +++++++++++-------- .../crates/turbo-tasks/src/vc/cell_mode.rs | 12 +++++++--- .../crates/turbopack-node/src/evaluate.rs | 8 +++---- 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index b1df00c7b7fc5..ca67a0a25f865 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -31,7 +31,7 @@ use turbo_tasks::{ TraitTypeId, TurboTasksBackendApi, ValueTypeId, backend::{ Backend, CachedTaskType, CellContent, TaskExecutionSpec, TransientTaskRoot, - TransientTaskType, TurboTasksExecutionError, TypedCellContent, + TransientTaskType, TurboTasksExecutionError, TypedCellContent, VerificationMode, }, event::{Event, EventListener}, message_queue::TimingEvent, @@ -2681,14 +2681,14 @@ impl TurboTasksBackendInner { task_id: TaskId, cell: CellId, content: CellContent, - never_equal: bool, + verification_mode: VerificationMode, turbo_tasks: &dyn TurboTasksBackendApi>, ) { operation::UpdateCellOperation::run( task_id, cell, content, - never_equal, + verification_mode, self.execute_context(turbo_tasks), ); } @@ -3282,11 +3282,11 @@ impl Backend for TurboTasksBackend { task_id: TaskId, cell: CellId, content: CellContent, - never_equal: bool, + verification_mode: VerificationMode, turbo_tasks: &dyn TurboTasksBackendApi, ) { self.0 - .update_task_cell(task_id, cell, content, never_equal, turbo_tasks); + .update_task_cell(task_id, cell, content, verification_mode, turbo_tasks); } fn mark_own_task_as_finished( diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 6f2e25738304e..5bd654fb19ca2 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -2,6 +2,8 @@ use std::mem::take; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; +#[cfg(not(feature = "verify_determinism"))] +use turbo_tasks::backend::VerificationMode; use turbo_tasks::{CellId, TaskId, TypedSharedReference, backend::CellContent}; #[cfg(feature = "trace_task_dirty")] @@ -44,8 +46,8 @@ impl UpdateCellOperation { task_id: TaskId, cell: CellId, content: CellContent, - #[cfg(feature = "verify_determinism")] never_equal: bool, - #[cfg(not(feature = "verify_determinism"))] _never_equal: bool, + #[cfg(feature = "verify_determinism")] verification_mode: VerificationMode, + #[cfg(not(feature = "verify_determinism"))] _verification_mode: VerificationMode, mut ctx: impl ExecuteContext, ) { let content = if let CellContent(Some(new_content)) = content { @@ -74,10 +76,13 @@ impl UpdateCellOperation { // Check if this assumption holds. #[cfg(feature = "verify_determinism")] - if !is_stateful && !never_equal && content.as_ref() != old_content { + if !is_stateful + && matches!(verification_mode, VerificationMode::EqualityCheck) + && content.as_ref() != old_content + { let task_description = ctx.get_task_description(task_id); let cell_type = turbo_tasks::registry::get_value_type(cell.type_id).global_name; - println!( + eprintln!( "Task {} updated cell #{} (type: {}) while recomputing", task_description, cell.index, cell_type ); diff --git a/turbopack/crates/turbo-tasks-testing/src/lib.rs b/turbopack/crates/turbo-tasks-testing/src/lib.rs index ea7ad135d1c95..b298f282c07bd 100644 --- a/turbopack/crates/turbo-tasks-testing/src/lib.rs +++ b/turbopack/crates/turbo-tasks-testing/src/lib.rs @@ -18,7 +18,7 @@ use tokio::sync::mpsc::Receiver; use turbo_tasks::{ CellId, ExecutionId, InvalidationReason, LocalTaskId, MagicAny, RawVc, ReadCellOptions, ReadOutputOptions, TaskId, TaskPersistence, TraitTypeId, TurboTasksApi, TurboTasksCallApi, - backend::{CellContent, TaskCollectiblesMap, TypedCellContent}, + backend::{CellContent, TaskCollectiblesMap, TypedCellContent, VerificationMode}, event::{Event, EventListener}, message_queue::CompilationEvent, test_helpers::with_turbo_tasks_for_testing, @@ -266,7 +266,7 @@ impl TurboTasksApi for VcStorage { task: TaskId, index: CellId, content: CellContent, - _never_equal: bool, + _verification_mode: VerificationMode, ) { let mut map = self.cells.lock().unwrap(); let cell = map.entry((task, index)).or_default(); diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index 393c53a460165..74d61e3ef546e 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -497,6 +497,11 @@ impl From for TurboTasksExecutionError { } } +pub enum VerificationMode { + EqualityCheck, + Skip, +} + pub trait Backend: Sync + Send { #[allow(unused_variables)] fn startup(&self, turbo_tasks: &dyn TurboTasksBackendApi) {} @@ -621,7 +626,7 @@ pub trait Backend: Sync + Send { task: TaskId, index: CellId, content: CellContent, - never_equal: bool, + verification_mode: VerificationMode, turbo_tasks: &dyn TurboTasksBackendApi, ); diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index 0af2fde185ad9..4d7e303bb4453 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -25,7 +25,7 @@ use crate::{ VcValueTrait, VcValueType, backend::{ Backend, CachedTaskType, CellContent, TaskCollectiblesMap, TaskExecutionSpec, - TransientTaskType, TurboTasksExecutionError, TypedCellContent, + TransientTaskType, TurboTasksExecutionError, TypedCellContent, VerificationMode, }, capture_future::CaptureFuture, event::{Event, EventListener}, @@ -160,7 +160,7 @@ pub trait TurboTasksApi: TurboTasksCallApi + Sync + Send { task: TaskId, index: CellId, content: CellContent, - never_equal: bool, + verification_mode: VerificationMode, ); fn mark_own_task_as_finished(&self, task: TaskId); fn set_own_task_aggregation_number(&self, task: TaskId, aggregation_number: u32); @@ -1365,10 +1365,10 @@ impl TurboTasksApi for TurboTasks { task: TaskId, index: CellId, content: CellContent, - never_equal: bool, + verification_mode: VerificationMode, ) { self.backend - .update_task_cell(task, index, content, never_equal, self); + .update_task_cell(task, index, content, verification_mode, self); } fn connect_task(&self, task: TaskId) { @@ -1786,7 +1786,7 @@ impl CurrentCellRef { self.current_task, self.index, CellContent(Some(update)), - false, + VerificationMode::EqualityCheck, ) } } @@ -1869,7 +1869,7 @@ impl CurrentCellRef { } /// Unconditionally updates the content of the cell. - pub fn update(&self, new_value: T, never_equal: bool) + pub fn update(&self, new_value: T, verification_mode: VerificationMode) where T: VcValueType, { @@ -1880,7 +1880,7 @@ impl CurrentCellRef { CellContent(Some(SharedReference::new(triomphe::Arc::new( >::value_to_repr(new_value), )))), - never_equal, + verification_mode, ) } @@ -1892,9 +1892,13 @@ impl CurrentCellRef { /// /// The [`SharedReference`] is expected to use the `>::Repr` type for its representation of the value. - pub fn update_with_shared_reference(&self, shared_ref: SharedReference, never_equal: bool) { + pub fn update_with_shared_reference( + &self, + shared_ref: SharedReference, + verification_mode: VerificationMode, + ) { let tt = turbo_tasks(); - let update = if !never_equal { + let update = if matches!(verification_mode, VerificationMode::EqualityCheck) { let content = tt .read_own_task_cell( self.current_task, @@ -1920,7 +1924,7 @@ impl CurrentCellRef { self.current_task, self.index, CellContent(Some(shared_ref)), - never_equal, + verification_mode, ) } } diff --git a/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs b/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs index 009957c303fa3..cffeed9d67c23 100644 --- a/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs +++ b/turbopack/crates/turbo-tasks/src/vc/cell_mode.rs @@ -1,7 +1,10 @@ use std::{any::type_name, marker::PhantomData}; use super::{read::VcRead, traits::VcValueType}; -use crate::{RawVc, Vc, manager::find_cell_by_type, task::shared_reference::TypedSharedReference}; +use crate::{ + RawVc, Vc, backend::VerificationMode, manager::find_cell_by_type, + task::shared_reference::TypedSharedReference, +}; type VcReadTarget = <::Read as VcRead>::Target; type VcReadRepr = <::Read as VcRead>::Repr; @@ -37,7 +40,10 @@ where { fn cell(inner: VcReadTarget) -> Vc { let cell = find_cell_by_type(T::get_value_type_id()); - cell.update(>::target_to_value(inner), true); + cell.update( + >::target_to_value(inner), + VerificationMode::Skip, + ); Vc { node: cell.into(), _t: PhantomData, @@ -47,7 +53,7 @@ where fn raw_cell(content: TypedSharedReference) -> RawVc { debug_assert_repr::(&content); let cell = find_cell_by_type(content.type_id); - cell.update_with_shared_reference(content.reference, true); + cell.update_with_shared_reference(content.reference, VerificationMode::Skip); cell.into() } } diff --git a/turbopack/crates/turbopack-node/src/evaluate.rs b/turbopack/crates/turbopack-node/src/evaluate.rs index 22f7e95164f5f..e495896bcce6b 100644 --- a/turbopack/crates/turbopack-node/src/evaluate.rs +++ b/turbopack/crates/turbopack-node/src/evaluate.rs @@ -16,8 +16,8 @@ use serde_json::Value as JsonValue; use turbo_rcstr::rcstr; use turbo_tasks::{ Completion, Effects, FxIndexMap, NonLocalValue, OperationVc, RawVc, ReadRef, ResolvedVc, - TaskInput, TryJoinIterExt, Vc, VcValueType, duration_span, fxindexmap, get_effects, - mark_finished, prevent_gc, trace::TraceRawVcs, util::SharedError, + TaskInput, TryJoinIterExt, Vc, VcValueType, backend::VerificationMode, duration_span, + fxindexmap, get_effects, mark_finished, prevent_gc, trace::TraceRawVcs, util::SharedError, }; use turbo_tasks_bytes::{Bytes, Stream}; use turbo_tasks_env::{EnvMap, ProcessEnv}; @@ -403,7 +403,7 @@ pub async fn custom_evaluate( let (sender, receiver) = unbounded(); cell.update( JavaScriptEvaluation(JavaScriptStream::new_open(vec![], Box::new(receiver))), - true, + VerificationMode::Skip, ); let initial = Mutex::new(Some(sender)); @@ -423,7 +423,7 @@ pub async fn custom_evaluate( vec![], Box::new(receiver), )), - true, + VerificationMode::Skip, ); sender }