From e9e374961432d46f26e2387a448da78c138d4657 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 30 Jan 2026 14:39:37 +0100 Subject: [PATCH 1/6] Store task error as pointer to the source error --- .../src/next_api/project.rs | 4 +- .../src/next_api/turbopack_ctx.rs | 2 +- .../crates/turbo-tasks-backend/Cargo.toml | 2 +- .../turbo-tasks-backend/src/backend/mod.rs | 115 ++++++++++++++---- .../src/backend/operation/mod.rs | 7 +- .../src/backend/storage_schema.rs | 2 +- .../crates/turbo-tasks-backend/src/data.rs | 8 +- .../crates/turbo-tasks-backend/src/error.rs | 114 +++++++++++++++++ .../crates/turbo-tasks-backend/src/lib.rs | 1 + .../crates/turbo-tasks-testing/src/lib.rs | 16 ++- turbopack/crates/turbo-tasks/src/backend.rs | 108 ++++++++++++---- turbopack/crates/turbo-tasks/src/manager.rs | 23 ++-- 12 files changed, 336 insertions(+), 66 deletions(-) create mode 100644 turbopack/crates/turbo-tasks-backend/src/error.rs diff --git a/crates/next-napi-bindings/src/next_api/project.rs b/crates/next-napi-bindings/src/next_api/project.rs index b9283fe5b696..ef75e8c8e207 100644 --- a/crates/next-napi-bindings/src/next_api/project.rs +++ b/crates/next-napi-bindings/src/next_api/project.rs @@ -37,8 +37,8 @@ use tracing_subscriber::{Registry, layer::SubscriberExt, util::SubscriberInitExt use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ Effects, FxIndexSet, NonLocalValue, OperationValue, OperationVc, PrettyPrintError, ReadRef, - ResolvedVc, TaskInput, TransientInstance, TryJoinIterExt, TurboTasksApi, UpdateInfo, Vc, - get_effects, + ResolvedVc, TaskInput, TransientInstance, TryJoinIterExt, TurboTasksApi, TurboTasksCallApi, + UpdateInfo, Vc, get_effects, message_queue::{CompilationEvent, Severity}, trace::TraceRawVcs, }; diff --git a/crates/next-napi-bindings/src/next_api/turbopack_ctx.rs b/crates/next-napi-bindings/src/next_api/turbopack_ctx.rs index 01011b2abab4..771b1ed2d2bb 100644 --- a/crates/next-napi-bindings/src/next_api/turbopack_ctx.rs +++ b/crates/next-napi-bindings/src/next_api/turbopack_ctx.rs @@ -18,7 +18,7 @@ use owo_colors::OwoColorize; use serde::Serialize; use terminal_hyperlink::Hyperlink; use turbo_tasks::{ - PrettyPrintError, TurboTasks, TurboTasksApi, + PrettyPrintError, TurboTasks, TurboTasksCallApi, backend::TurboTasksExecutionError, message_queue::{CompilationEvent, Severity}, }; diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index 62461cbf2de2..d0675598a003 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -13,7 +13,7 @@ bench = false workspace = true [features] -default = [] +default = ["print_cache_item_size"] print_cache_item_size = ["dep:lzzzz"] no_fast_stale = [] verify_serialization = [] diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 934744bb5ba4..312ddcda12b5 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -28,10 +28,12 @@ use turbo_bincode::{TurboBincodeBuffer, new_turbo_bincode_decoder, new_turbo_bin use turbo_tasks::{ CellId, FxDashMap, RawVc, ReadCellOptions, ReadCellTracking, ReadConsistency, ReadOutputOptions, ReadTracking, SharedReference, TRANSIENT_TASK_BIT, TaskExecutionReason, - TaskId, TaskPriority, TraitTypeId, TurboTasksBackendApi, ValueTypeId, + TaskId, TaskPriority, TraitTypeId, TurboTasksBackendApi, TurboTasksPanic, ValueTypeId, backend::{ Backend, CachedTaskType, CellContent, TaskExecutionSpec, TransientTaskType, - TurboTasksExecutionError, TypedCellContent, VerificationMode, + TurboTaskContextError, TurboTaskLocalContextError, TurboTasksError, + TurboTasksExecutionError, TurboTasksExecutionErrorMessage, TypedCellContent, + VerificationMode, }, event::{Event, EventDescription, EventListener}, message_queue::TimingEvent, @@ -39,7 +41,6 @@ use turbo_tasks::{ scope::scope_and_block, task_statistics::TaskStatisticsApi, trace::TraceRawVcs, - turbo_tasks, util::{IdFactoryWithReuse, good_chunk_size, into_chunks}, }; @@ -66,6 +67,7 @@ use crate::{ ActivenessState, CellRef, CollectibleRef, CollectiblesRef, Dirtyness, InProgressCellState, InProgressState, InProgressStateInner, OutputValue, TransientTask, }, + error::TaskError, utils::{ arc_or_owned::ArcOrOwned, chunked_vec::ChunkedVec, @@ -387,6 +389,64 @@ impl TurboTasksBackendInner { self.task_statistics .map(|stats| stats.increment_cache_miss(task_type.native_fn)); } + + fn task_error_to_turbo_tasks_execution_error( + &self, + error: &TaskError, + ctx: &mut impl ExecuteContext<'_>, + ) -> TurboTasksExecutionError { + match error { + TaskError::Panic(panic) => TurboTasksExecutionError::Panic(panic.clone()), + TaskError::Error(item) => TurboTasksExecutionError::Error(Arc::new(TurboTasksError { + message: item.message.clone(), + source: item + .source + .as_ref() + .map(|e| self.task_error_to_turbo_tasks_execution_error(e, ctx)), + })), + TaskError::LocalTaskContext(local_task_context) => { + TurboTasksExecutionError::LocalTaskContext(Arc::new(TurboTaskLocalContextError { + name: local_task_context.name.clone(), + source: local_task_context + .source + .as_ref() + .map(|e| self.task_error_to_turbo_tasks_execution_error(e, ctx)), + })) + } + TaskError::TaskChain(chain) => { + let task_id = chain.last().unwrap(); + let error = { + let task = ctx.task(*task_id, TaskDataCategory::Meta); + if let Some(OutputValue::Error(error)) = task.get_output().clone() { + Some(error.clone()) + } else { + None + } + }; + let error = error.map_or_else( + || { + TurboTasksExecutionError::Panic(Arc::new(TurboTasksPanic { + message: TurboTasksExecutionErrorMessage::PIISafe(Cow::Borrowed( + "Error no longer available", + )), + location: None, + })) + }, + |e| self.task_error_to_turbo_tasks_execution_error(&e, ctx), + ); + let mut current_error = error; + for &task_id in chain.iter().rev() { + current_error = + TurboTasksExecutionError::TaskContext(Arc::new(TurboTaskContextError { + task_id, + source: Some(current_error), + turbo_tasks: ctx.turbo_tasks(), + })); + } + current_error + } + } + } } pub(crate) struct OperationGuard<'a, B: BackingStorage> { @@ -715,11 +775,9 @@ impl TurboTasksBackendInner { let result = match output { OutputValue::Cell(cell) => Ok(Ok(RawVc::TaskCell(cell.task, cell.cell))), OutputValue::Output(task) => Ok(Ok(RawVc::TaskOutput(*task))), - OutputValue::Error(error) => Err(error - .with_task_context(task.get_task_type(), Some(task_id)) - .into()), + OutputValue::Error(error) => Err(error.clone()), }; - if let Some(mut reader_task) = reader_task + if let Some(mut reader_task) = reader_task.take() && options.tracking.should_track(result.is_err()) && (!task.immutable() || cfg!(feature = "verify_immutable")) { @@ -759,9 +817,15 @@ impl TurboTasksBackendInner { drop(reader_task); queue.execute(&mut ctx); + } else { + drop(task); } - return result; + return result.map_err(|error| { + self.task_error_to_turbo_tasks_execution_error(&error, &mut ctx) + .with_task_context(task_id, turbo_tasks.pin()) + .into() + }); } drop(reader_task); @@ -962,6 +1026,7 @@ impl TurboTasksBackendInner { &self, parent_span: Option, reason: &str, + turbo_tasks: &dyn TurboTasksBackendApi>, ) -> Option<(Instant, bool)> { let snapshot_span = tracing::trace_span!(parent: parent_span.clone(), "snapshot", reason = reason) @@ -1087,7 +1152,7 @@ impl TurboTasksBackendInner { if let Some(ref m) = meta { task_cache_stats .lock() - .entry(self.debug_get_task_name(task_id)) + .entry(self.get_task_name(task_id, turbo_tasks)) .or_default() .add_counts(m); } @@ -1107,7 +1172,7 @@ impl TurboTasksBackendInner { if inner.flags.meta_modified() { task_cache_stats .lock() - .entry(self.debug_get_task_name(task_id)) + .entry(self.get_task_name(task_id, turbo_tasks)) .or_default() .add_counts(&inner); } @@ -1143,7 +1208,7 @@ impl TurboTasksBackendInner { #[cfg(feature = "print_cache_item_size")] task_cache_stats .lock() - .entry(self.debug_get_task_name(task_id)) + .entry(self.get_task_name(task_id, turbo_tasks)) .or_default() .add_meta(&meta); Some(meta) @@ -1163,7 +1228,7 @@ impl TurboTasksBackendInner { #[cfg(feature = "print_cache_item_size")] task_cache_stats .lock() - .entry(self.debug_get_task_name(task_id)) + .entry(self.get_task_name(task_id, turbo_tasks)) .or_default() .add_data(&data); Some(data) @@ -1317,7 +1382,7 @@ impl TurboTasksBackendInner { let elapsed = start.elapsed(); // avoid spamming the event queue with information about fast operations if elapsed > Duration::from_secs(10) { - turbo_tasks().send_compilation_event(Arc::new(TimingEvent::new( + turbo_tasks.send_compilation_event(Arc::new(TimingEvent::new( "Finished writing to filesystem cache".to_string(), elapsed, ))); @@ -1366,7 +1431,7 @@ impl TurboTasksBackendInner { self.verify_aggregation_graph(turbo_tasks, false); } if self.should_persist() { - self.snapshot_and_persist(Span::current().into(), "stop"); + self.snapshot_and_persist(Span::current().into(), "stop", turbo_tasks); } drop_contents(&self.task_cache); self.storage.drop_contents(); @@ -1718,15 +1783,19 @@ impl TurboTasksBackendInner { } } - #[allow(dead_code)] - fn debug_get_task_name(&self, task_id: TaskId) -> String { - let task = self.storage.access_mut(task_id); + fn get_task_name( + &self, + task_id: TaskId, + turbo_tasks: &dyn TurboTasksBackendApi>, + ) -> String { + let mut ctx = self.execute_context(turbo_tasks); + let task = ctx.task(task_id, TaskDataCategory::Data); if let Some(value) = task.get_persistent_task_type() { value.to_string() } else if let Some(value) = task.get_transient_task_type() { value.to_string() } else { - "unknown".to_string() + panic!("Every task must have a type\n{:#?}", task); } } @@ -2180,11 +2249,11 @@ impl TurboTasksBackendInner { } Err(err) => { if let Some(OutputValue::Error(old_error)) = current_output - && old_error == &err + && **old_error == err { None } else { - Some(OutputValue::Error(err)) + Some(OutputValue::Error(Arc::new((&err).into()))) } } }; @@ -2715,7 +2784,7 @@ impl TurboTasksBackendInner { } let this = self.clone(); - let snapshot = this.snapshot_and_persist(None, reason); + let snapshot = this.snapshot_and_persist(None, reason, turbo_tasks); if let Some((snapshot_start, new_data)) = snapshot { last_snapshot = snapshot_start; if !new_data { @@ -3527,6 +3596,10 @@ impl Backend for TurboTasksBackend { fn is_tracking_dependencies(&self) -> bool { self.0.options.dependency_tracking } + + fn get_task_name(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) -> String { + self.0.get_task_name(task, turbo_tasks) + } } enum DebugTraceTransientTask { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index 433d99fb6320..b6fe148b3fe9 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -16,7 +16,7 @@ use std::{ use bincode::{Decode, Encode}; use turbo_tasks::{ CellId, FxIndexMap, TaskExecutionReason, TaskId, TaskPriority, TurboTasksBackendApi, - TypedSharedReference, backend::CachedTaskType, + TurboTasksCallApi, TypedSharedReference, backend::CachedTaskType, }; use crate::{ @@ -93,6 +93,7 @@ pub trait ExecuteContext<'e>: Sized { fn suspending_requested(&self) -> bool; fn should_track_dependencies(&self) -> bool; fn should_track_activeness(&self) -> bool; + fn turbo_tasks(&self) -> Arc; } pub trait ChildExecuteContext<'e>: Send + Sized { @@ -637,6 +638,10 @@ where fn should_track_activeness(&self) -> bool { self.backend.should_track_activeness() } + + fn turbo_tasks(&self) -> Arc { + self.turbo_tasks.pin() + } } struct ChildExecuteContextImpl<'e, B: BackingStorage> { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs index 9f0032536f3d..11bdcec9388a 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs @@ -1010,7 +1010,7 @@ mod tests { fn test_schema_size() { assert_eq!( size_of::(), - 144, + 136, "TaskStorage size changed! If this is intentional, update this test." ); assert_eq!( diff --git a/turbopack/crates/turbo-tasks-backend/src/data.rs b/turbopack/crates/turbo-tasks-backend/src/data.rs index 670c0ccb8bad..b50ffdb97edc 100644 --- a/turbopack/crates/turbo-tasks-backend/src/data.rs +++ b/turbopack/crates/turbo-tasks-backend/src/data.rs @@ -1,6 +1,7 @@ use std::{ fmt::{self, Debug, Display}, pin::Pin, + sync::Arc, }; use anyhow::Result; @@ -9,10 +10,12 @@ use parking_lot::Mutex; use rustc_hash::FxHashSet; use turbo_tasks::{ CellId, RawVc, TaskExecutionReason, TaskId, TaskPriority, TraitTypeId, - backend::{TransientTaskRoot, TurboTasksExecutionError}, + backend::TransientTaskRoot, event::{Event, EventDescription, EventListener}, }; +use crate::error::TaskError; + // this traits are needed for the transient variants of `CachedDataItem` // transient variants are never cloned or compared macro_rules! transient_traits { @@ -78,8 +81,9 @@ impl CollectiblesRef { pub enum OutputValue { Cell(CellRef), Output(TaskId), - Error(TurboTasksExecutionError), + Error(Arc), } + impl OutputValue { /// Returns true if this output value references a transient task. /// diff --git a/turbopack/crates/turbo-tasks-backend/src/error.rs b/turbopack/crates/turbo-tasks-backend/src/error.rs new file mode 100644 index 000000000000..e6858bedb717 --- /dev/null +++ b/turbopack/crates/turbo-tasks-backend/src/error.rs @@ -0,0 +1,114 @@ +use std::sync::Arc; + +use bincode::{Decode, Encode}; +use smallvec::SmallVec; +use turbo_rcstr::RcStr; +use turbo_tasks::{ + TaskId, TurboTasksPanic, + backend::{TurboTasksExecutionError, TurboTasksExecutionErrorMessage}, +}; + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct TaskErrorItem { + pub message: TurboTasksExecutionErrorMessage, + pub source: Option, +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct LocalTaskContext { + pub name: RcStr, + pub source: Option, +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub enum TaskError { + Panic(Arc), + Error(Box), + LocalTaskContext(Box), + TaskChain(SmallVec<[TaskId; 4]>), +} + +impl From<&TurboTasksExecutionError> for TaskError { + fn from(value: &TurboTasksExecutionError) -> Self { + match value { + TurboTasksExecutionError::Panic(panic) => TaskError::Panic(panic.clone()), + TurboTasksExecutionError::Error(error) => TaskError::Error(Box::new(TaskErrorItem { + message: error.message.clone(), + source: error.source.as_ref().map(|e| e.into()), + })), + TurboTasksExecutionError::LocalTaskContext(local_task_context) => { + TaskError::LocalTaskContext(Box::new(LocalTaskContext { + name: local_task_context.name.clone(), + source: local_task_context.source.as_ref().map(|e| e.into()), + })) + } + TurboTasksExecutionError::TaskContext(task_context) => { + let mut chain = SmallVec::new(); + chain.push(task_context.task_id); + let mut current_error = task_context.source.as_ref(); + while let Some(error) = current_error { + match error { + TurboTasksExecutionError::TaskContext(task_context) => { + chain.push(task_context.task_id); + current_error = task_context.source.as_ref(); + } + _ => { + return TaskError::TaskChain(chain); + } + } + } + TaskError::TaskChain(chain) + } + } + } +} + +fn eq_option(this: &Option, other: &Option) -> bool { + match (this, other) { + (Some(this), Some(other)) => this == other, + (None, None) => true, + _ => false, + } +} + +impl PartialEq for TaskError { + fn eq(&self, other: &TurboTasksExecutionError) -> bool { + match (self, other) { + (TaskError::Panic(this), TurboTasksExecutionError::Panic(other)) => this == other, + (TaskError::Error(this), TurboTasksExecutionError::Error(other)) => { + this.message == other.message && eq_option(&this.source, &other.source) + } + ( + TaskError::LocalTaskContext(this), + TurboTasksExecutionError::LocalTaskContext(other), + ) => this.name == other.name && eq_option(&this.source, &other.source), + (TaskError::TaskChain(chain), TurboTasksExecutionError::TaskContext(other)) => { + if chain.is_empty() { + return false; + } + if chain[0] != other.task_id { + return false; + } + let mut current_source = other.source.as_ref(); + for &task_id in &chain[1..] { + match current_source { + Some(other) => match other { + TurboTasksExecutionError::TaskContext(task_context) => { + if task_context.task_id != task_id { + return false; + } + current_source = task_context.source.as_ref(); + } + _ => return false, + }, + None => return false, + } + } + // TaskError will stop at the last task in the chain (this is a pointer), so we do + // not compare further. + true + } + _ => false, + } + } +} diff --git a/turbopack/crates/turbo-tasks-backend/src/lib.rs b/turbopack/crates/turbo-tasks-backend/src/lib.rs index 417449a840c5..974c91da16b9 100644 --- a/turbopack/crates/turbo-tasks-backend/src/lib.rs +++ b/turbopack/crates/turbo-tasks-backend/src/lib.rs @@ -7,6 +7,7 @@ mod backend; mod backing_storage; mod data; mod database; +mod error; mod kv_backing_storage; mod utils; diff --git a/turbopack/crates/turbo-tasks-testing/src/lib.rs b/turbopack/crates/turbo-tasks-testing/src/lib.rs index 44642d404197..8316792fb498 100644 --- a/turbopack/crates/turbo-tasks-testing/src/lib.rs +++ b/turbopack/crates/turbo-tasks-testing/src/lib.rs @@ -155,6 +155,16 @@ impl TurboTasksCallApi for VcStorage { ) { unreachable!() } + + /// Should not be called on the testing VcStorage. These methods are only implemented for + /// structs with access to a `MessageQueue` like `TurboTasks`. + fn send_compilation_event(&self, _event: Arc) { + unimplemented!() + } + + fn get_task_name(&self, task: TaskId) -> String { + format!("Task({})", task) + } } impl TurboTasksApi for VcStorage { @@ -316,12 +326,6 @@ impl TurboTasksApi for VcStorage { unimplemented!() } - /// Should not be called on the testing VcStorage. These methods are only implemented for - /// structs with access to a `MessageQueue` like `TurboTasks`. - fn send_compilation_event(&self, _event: Arc) { - unimplemented!() - } - fn is_tracking_dependencies(&self) -> bool { false } diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index 23ddbf215f45..105225b8b7f0 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -12,6 +12,8 @@ use anyhow::{Result, anyhow}; use auto_hash_map::AutoMap; use bincode::{ Decode, Encode, + de::Decoder, + enc::Encoder, error::{DecodeError, EncodeError}, impl_borrow_decode, }; @@ -26,10 +28,10 @@ use turbo_rcstr::RcStr; use crate::{ RawVc, ReadCellOptions, ReadOutputOptions, ReadRef, SharedReference, TaskId, TaskIdSet, - TaskPriority, TraitRef, TraitTypeId, TurboTasksPanic, ValueTypeId, VcValueTrait, VcValueType, - event::EventListener, macro_helpers::NativeFunction, magic_any::MagicAny, - manager::TurboTasksBackendApi, raw_vc::CellId, registry, - task::shared_reference::TypedSharedReference, task_statistics::TaskStatisticsApi, + TaskPriority, TraitRef, TraitTypeId, TurboTasksCallApi, TurboTasksPanic, ValueTypeId, + VcValueTrait, VcValueType, event::EventListener, macro_helpers::NativeFunction, + magic_any::MagicAny, manager::TurboTasksBackendApi, raw_vc::CellId, registry, + task::shared_reference::TypedSharedReference, task_statistics::TaskStatisticsApi, turbo_tasks, }; pub type TransientTaskRoot = @@ -273,7 +275,7 @@ pub type TaskCollectiblesMap = AutoMap, // Structurally and functionally similar to Cow<&'static, str> but explicitly notes the importance // of non-static strings potentially containing PII (Personal Identifiable Information). -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub enum TurboTasksExecutionErrorMessage { PIISafe(#[bincode(with = "turbo_bincode::owned_cow")] Cow<'static, str>), NonPIISafe(String), @@ -294,28 +296,82 @@ pub struct TurboTasksError { pub source: Option, } -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +#[derive(Clone)] pub struct TurboTaskContextError { - pub task: RcStr, - #[cfg(feature = "task_id_details")] - pub task_id: Option, + pub turbo_tasks: Arc, + pub task_id: TaskId, + pub source: Option, +} + +impl PartialEq for TurboTaskContextError { + fn eq(&self, other: &Self) -> bool { + self.task_id == other.task_id && self.source == other.source + } +} +impl Eq for TurboTaskContextError {} + +impl Encode for TurboTaskContextError { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + Encode::encode(&self.task_id, encoder)?; + Encode::encode(&self.source, encoder)?; + Ok(()) + } +} + +impl Decode for TurboTaskContextError { + fn decode>(decoder: &mut D) -> Result { + let task_id = Decode::decode(decoder)?; + let source = Decode::decode(decoder)?; + let turbo_tasks = turbo_tasks(); + Ok(Self { + turbo_tasks, + task_id, + source, + }) + } +} + +impl_borrow_decode!(TurboTaskContextError); + +impl Debug for TurboTaskContextError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TurboTaskContextError") + .field("task_id", &self.task_id) + .field("source", &self.source) + .finish() + } +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct TurboTaskLocalContextError { + pub name: RcStr, pub source: Option, } -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub enum TurboTasksExecutionError { Panic(Arc), Error(Arc), TaskContext(Arc), + LocalTaskContext(Arc), } impl TurboTasksExecutionError { - pub fn with_task_context(&self, task: impl Display, _task_id: Option) -> Self { + pub fn with_task_context( + self, + task_id: TaskId, + turbo_tasks: Arc, + ) -> Self { TurboTasksExecutionError::TaskContext(Arc::new(TurboTaskContextError { - task: RcStr::from(task.to_string()), - #[cfg(feature = "task_id_details")] - task_id: _task_id, - source: Some(self.clone()), + task_id, + turbo_tasks, + source: Some(self), + })) + } + pub fn with_local_task_context(self, name: String) -> Self { + TurboTasksExecutionError::LocalTaskContext(Arc::new(TurboTaskLocalContextError { + name: RcStr::from(name), + source: Some(self), })) } } @@ -330,6 +386,9 @@ impl Error for TurboTasksExecutionError { TurboTasksExecutionError::TaskContext(context_error) => { context_error.source.as_ref().map(|s| s as &dyn Error) } + TurboTasksExecutionError::LocalTaskContext(context_error) => { + context_error.source.as_ref().map(|s| s as &dyn Error) + } } } } @@ -342,15 +401,16 @@ impl Display for TurboTasksExecutionError { write!(f, "{}", error.message) } TurboTasksExecutionError::TaskContext(context_error) => { - #[cfg(feature = "task_id_details")] - if let Some(task_id) = context_error.task_id { - return write!( - f, - "Execution of {} ({}) failed", - context_error.task, task_id - ); + let task_id = context_error.task_id; + let name = context_error.turbo_tasks.get_task_name(task_id); + if cfg!(feature = "task_id_details") { + write!(f, "Execution of {name} ({}) failed", task_id) + } else { + write!(f, "Execution of {name} failed") } - write!(f, "Execution of {} failed", context_error.task) + } + TurboTasksExecutionError::LocalTaskContext(context_error) => { + write!(f, "Execution of {} failed", context_error.name) } } } @@ -566,4 +626,6 @@ pub trait Backend: Sync + Send { fn task_statistics(&self) -> &TaskStatisticsApi; fn is_tracking_dependencies(&self) -> bool; + + fn get_task_name(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) -> String; } diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index 8813b9278657..db83fe649f59 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -94,6 +94,10 @@ pub trait TurboTasksCallApi: Sync + Send { future: Pin> + Send + 'static>>, ) -> Pin> + Send>>; fn start_once_process(&self, future: Pin + Send + 'static>>); + + fn send_compilation_event(&self, event: Arc); + + fn get_task_name(&self, task: TaskId) -> String; } /// A type-erased subset of [`TurboTasks`] stored inside a thread local when we're in a turbo task @@ -190,7 +194,6 @@ pub trait TurboTasksApi: TurboTasksCallApi + Sync + Send { &self, event_types: Option>, ) -> Receiver>; - fn send_compilation_event(&self, event: Arc); // Returns true if TurboTasks is configured to track dependencies. fn is_tracking_dependencies(&self) -> bool; @@ -1274,7 +1277,7 @@ impl Executor, ScheduledTask, TaskPriority> for TurboT Ok(raw_vc) => OutputContent::Link(raw_vc), Err(err) => OutputContent::Error( TurboTasksExecutionError::from(err) - .with_task_context(task_type, None), + .with_local_task_context(task_type.to_string()), ), }; @@ -1374,6 +1377,16 @@ impl TurboTasksCallApi for TurboTasks { fn start_once_process(&self, future: Pin + Send + 'static>>) { self.start_once_process(future) } + + fn send_compilation_event(&self, event: Arc) { + if let Err(e) = self.compilation_events.send(event) { + tracing::warn!("Failed to send compilation event: {e}"); + } + } + + fn get_task_name(&self, task: TaskId) -> String { + self.backend.get_task_name(task, self) + } } impl TurboTasksApi for TurboTasks { @@ -1580,12 +1593,6 @@ impl TurboTasksApi for TurboTasks { self.compilation_events.subscribe(event_types) } - fn send_compilation_event(&self, event: Arc) { - if let Err(e) = self.compilation_events.send(event) { - tracing::warn!("Failed to send compilation event: {e}"); - } - } - fn is_tracking_dependencies(&self) -> bool { self.backend.is_tracking_dependencies() } From c74ad658dda58400aaf1dafd72fbb5f01a06689f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 3 Feb 2026 12:41:43 +0100 Subject: [PATCH 2/6] clippy --- .../turbo-tasks-backend/src/backend/mod.rs | 2 +- .../crates/turbo-tasks-backend/src/error.rs | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 312ddcda12b5..7d494c9299a8 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -417,7 +417,7 @@ impl TurboTasksBackendInner { let task_id = chain.last().unwrap(); let error = { let task = ctx.task(*task_id, TaskDataCategory::Meta); - if let Some(OutputValue::Error(error)) = task.get_output().clone() { + if let Some(OutputValue::Error(error)) = task.get_output() { Some(error.clone()) } else { None diff --git a/turbopack/crates/turbo-tasks-backend/src/error.rs b/turbopack/crates/turbo-tasks-backend/src/error.rs index e6858bedb717..804e38a0e34a 100644 --- a/turbopack/crates/turbo-tasks-backend/src/error.rs +++ b/turbopack/crates/turbo-tasks-backend/src/error.rs @@ -91,17 +91,15 @@ impl PartialEq for TaskError { } let mut current_source = other.source.as_ref(); for &task_id in &chain[1..] { - match current_source { - Some(other) => match other { - TurboTasksExecutionError::TaskContext(task_context) => { - if task_context.task_id != task_id { - return false; - } - current_source = task_context.source.as_ref(); - } - _ => return false, - }, - None => return false, + if let Some(TurboTasksExecutionError::TaskContext(task_context)) = + current_source + { + if task_context.task_id != task_id { + return false; + } + current_source = task_context.source.as_ref(); + } else { + return false; } } // TaskError will stop at the last task in the chain (this is a pointer), so we do From c53e162210eefaad7ba80b59e08b17f7ba855761 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Mon, 9 Feb 2026 13:27:54 +0100 Subject: [PATCH 3/6] Revert accidental default feature flag for print_cache_item_size --- turbopack/crates/turbo-tasks-backend/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index d0675598a003..62461cbf2de2 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -13,7 +13,7 @@ bench = false workspace = true [features] -default = ["print_cache_item_size"] +default = [] print_cache_item_size = ["dep:lzzzz"] no_fast_stale = [] verify_serialization = [] From b6cd9ffabb3f312335a4077621335bcc9a3199fc Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 10 Feb 2026 08:36:12 +0100 Subject: [PATCH 4/6] add comments --- .../turbo-tasks-backend/src/backend/mod.rs | 4 +++ .../crates/turbo-tasks-backend/src/error.rs | 26 +++++++++++++++++++ turbopack/crates/turbo-tasks/src/backend.rs | 12 +++++++++ turbopack/crates/turbo-tasks/src/manager.rs | 2 ++ 4 files changed, 44 insertions(+) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 7d494c9299a8..e2e55a33f107 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -390,6 +390,10 @@ impl TurboTasksBackendInner { .map(|stats| stats.increment_cache_miss(task_type.native_fn)); } + /// Reconstructs a full [`TurboTasksExecutionError`] from the compact [`TaskError`] storage + /// representation. For [`TaskError::TaskChain`], this looks up the source error from the last + /// task's output and rebuilds the nested `TaskContext` wrappers with `TurboTasksCallApi` + /// references for lazy name resolution. fn task_error_to_turbo_tasks_execution_error( &self, error: &TaskError, diff --git a/turbopack/crates/turbo-tasks-backend/src/error.rs b/turbopack/crates/turbo-tasks-backend/src/error.rs index 804e38a0e34a..563e097b120d 100644 --- a/turbopack/crates/turbo-tasks-backend/src/error.rs +++ b/turbopack/crates/turbo-tasks-backend/src/error.rs @@ -1,3 +1,12 @@ +//! Compact error representation for storage in the turbo-tasks backend. +//! +//! [`TurboTasksExecutionError`] contains the whole chain of errors. That is expensive to store and +//! we would duplicate error messages for many tasks. [`TaskError`] is a backend-internal +//! representation that replaces nested [`TurboTasksExecutionError::TaskContext`] chains with a flat +//! list of [`TaskId`]s (`TaskChain`), and omits the actual error message behind them. The actual +//! error content is recovered at read time by looking up the task's output, and task names are +//! resolved lazily via `TurboTasksCallApi::get_task_name`. + use std::sync::Arc; use bincode::{Decode, Encode}; @@ -8,26 +17,39 @@ use turbo_tasks::{ backend::{TurboTasksExecutionError, TurboTasksExecutionErrorMessage}, }; +/// An error with a message and an optional cause. #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct TaskErrorItem { pub message: TurboTasksExecutionErrorMessage, pub source: Option, } +/// Context for a local task that failed. #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct LocalTaskContext { pub name: RcStr, pub source: Option, } +/// Compact, serializable representation of a task execution error. +/// +/// `TaskContext` chains are collapsed into a flat [`TaskChain`](TaskError::TaskChain) of +/// [`TaskId`]s. The source error is not stored in the chain; it is recovered by looking up the +/// output of the last task in the chain. #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub enum TaskError { Panic(Arc), Error(Box), LocalTaskContext(Box), + /// A chain of task IDs representing nested `TaskContext` wrappers. The last element is the + /// innermost task whose output holds the actual error. Earlier elements are outer tasks that + /// propagated the error. The source error is not stored here. The task id acts as a pointer to + /// the originating task's output. TaskChain(SmallVec<[TaskId; 4]>), } +/// Converts a [`TurboTasksExecutionError`] into the compact [`TaskError`] representation. +/// Nested `TaskContext` chains are flattened into a single [`TaskError::TaskChain`]. impl From<&TurboTasksExecutionError> for TaskError { fn from(value: &TurboTasksExecutionError) -> Self { match value { @@ -63,6 +85,7 @@ impl From<&TurboTasksExecutionError> for TaskError { } } +/// Compares optional errors across the two representations. fn eq_option(this: &Option, other: &Option) -> bool { match (this, other) { (Some(this), Some(other)) => this == other, @@ -71,6 +94,9 @@ fn eq_option(this: &Option, other: &Option) } } +/// Cross-type equality used to detect whether a task's stored error has changed (to avoid +/// unnecessary dirty-flagging). For `TaskChain`, only the task ID chain is compared — the source +/// error content is not checked because the chain acts as a pointer to the originating task. impl PartialEq for TaskError { fn eq(&self, other: &TurboTasksExecutionError) -> bool { match (self, other) { diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index 105225b8b7f0..4dd8698fd460 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -296,6 +296,9 @@ pub struct TurboTasksError { pub source: Option, } +/// Error context indicating that a task's execution failed. Stores a `task_id` and a reference to +/// the `TurboTasksCallApi` so that the task name can be resolved lazily at display time (via +/// [`TurboTasksCallApi::get_task_name`]) rather than eagerly at error creation time. #[derive(Clone)] pub struct TurboTaskContextError { pub turbo_tasks: Arc, @@ -342,6 +345,8 @@ impl Debug for TurboTaskContextError { } } +/// Error context for a local task that failed. Unlike [`TurboTaskContextError`], +/// this stores the task name directly since local tasks don't have a [`TaskId`]. #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct TurboTaskLocalContextError { pub name: RcStr, @@ -357,6 +362,8 @@ pub enum TurboTasksExecutionError { } impl TurboTasksExecutionError { + /// Wraps this error in a [`TaskContext`](TurboTasksExecutionError::TaskContext) layer + /// identifying the normal task that encountered the error. pub fn with_task_context( self, task_id: TaskId, @@ -368,6 +375,9 @@ impl TurboTasksExecutionError { source: Some(self), })) } + + /// Wraps this error in a [`LocalTaskContext`](TurboTasksExecutionError::LocalTaskContext) layer + /// identifying the local task that encountered the error. pub fn with_local_task_context(self, name: String) -> Self { TurboTasksExecutionError::LocalTaskContext(Arc::new(TurboTaskLocalContextError { name: RcStr::from(name), @@ -627,5 +637,7 @@ pub trait Backend: Sync + Send { fn is_tracking_dependencies(&self) -> bool; + /// Returns a human-readable name for the given task. Used by error display formatting + /// to lazily resolve task names instead of storing them eagerly in error objects. fn get_task_name(&self, task: TaskId, turbo_tasks: &dyn TurboTasksBackendApi) -> String; } diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index db83fe649f59..acf07cbb83d5 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -95,8 +95,10 @@ pub trait TurboTasksCallApi: Sync + Send { ) -> Pin> + Send>>; fn start_once_process(&self, future: Pin + Send + 'static>>); + /// Sends a compilation event to subscribers. fn send_compilation_event(&self, event: Arc); + /// Returns a human-readable name for the given task. fn get_task_name(&self, task: TaskId) -> String; } From 9af8d0db693ffb8c512a02e900a0bfd2451e44c5 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 11 Feb 2026 21:14:45 +0100 Subject: [PATCH 5/6] Update mod.rs --- turbopack/crates/turbo-tasks-backend/src/backend/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index e2e55a33f107..25eabd87d921 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -1799,7 +1799,7 @@ impl TurboTasksBackendInner { } else if let Some(value) = task.get_transient_task_type() { value.to_string() } else { - panic!("Every task must have a type\n{:#?}", task); + "unknown".to_string() } } From c2df444db69a73a9e557611068c504d7250c0dc4 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 11 Feb 2026 21:15:25 +0100 Subject: [PATCH 6/6] Update mod.rs --- turbopack/crates/turbo-tasks-backend/src/backend/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 25eabd87d921..157a548eda28 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -429,6 +429,7 @@ impl TurboTasksBackendInner { }; let error = error.map_or_else( || { + // Eventual consistency will cause errors to no longer be available TurboTasksExecutionError::Panic(Arc::new(TurboTasksPanic { message: TurboTasksExecutionErrorMessage::PIISafe(Cow::Borrowed( "Error no longer available",