Skip to main content

turbo_tasks/
backend.rs

1use std::{
2    borrow::{Borrow, Cow},
3    error::Error,
4    fmt::{self, Debug, Display},
5    future::Future,
6    hash::{BuildHasher, BuildHasherDefault, Hash},
7    ops::Deref,
8    pin::Pin,
9    sync::Arc,
10};
11
12use anyhow::{Result, anyhow};
13use auto_hash_map::AutoMap;
14use bincode::{
15    Decode, Encode,
16    de::Decoder,
17    enc::Encoder,
18    error::{DecodeError, EncodeError},
19    impl_borrow_decode,
20};
21use rustc_hash::FxHasher;
22use smallvec::SmallVec;
23use tracing::Span;
24use turbo_bincode::{
25    TurboBincodeDecode, TurboBincodeDecoder, TurboBincodeEncode, TurboBincodeEncoder,
26    impl_decode_for_turbo_bincode_decode, impl_encode_for_turbo_bincode_encode, new_hash_encoder,
27};
28use turbo_rcstr::RcStr;
29use turbo_tasks_hash::DeterministicHasher;
30
31use crate::{
32    CellId, RawVc, ReadCellOptions, ReadOutcome, ReadOutputOptions, ReadRef, SharedReference,
33    TaskId, TaskIdSet, TaskPriority, TraitRef, TraitTypeId, TurboTasksCallApi, TurboTasksPanic,
34    ValueTypeId, ValueTypePersistence, VcValueTrait, VcValueType,
35    dyn_task_inputs::{DynTaskInputs, DynTaskInputsStorage},
36    macro_helpers::NativeFunction,
37    manager::{TaskPersistence, TurboTasks},
38    registry,
39    task::shared_reference::TypedSharedReference,
40    task_statistics::TaskStatisticsApi,
41    turbo_tasks,
42};
43
44pub type TransientTaskRoot =
45    Box<dyn Fn() -> Pin<Box<dyn Future<Output = Result<RawVc>> + Send>> + Send + Sync>;
46
47pub enum TransientTaskType {
48    /// A root task that will track dependencies and re-execute when
49    /// dependencies change. Task will eventually settle to the correct
50    /// execution.
51    ///
52    /// Always active. Automatically scheduled.
53    Root(TransientTaskRoot),
54
55    // TODO implement these strongly consistency
56    /// A single root task execution. It won't track dependencies.
57    ///
58    /// Task will definitely include all invalidations that happened before the
59    /// start of the task. It may or may not include invalidations that
60    /// happened after that. It may see these invalidations partially
61    /// applied.
62    ///
63    /// Active until done. Automatically scheduled.
64    Once(Pin<Box<dyn Future<Output = Result<RawVc>> + Send + 'static>>),
65}
66
67impl Debug for TransientTaskType {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            Self::Root(_) => f.debug_tuple("Root").finish(),
71            Self::Once(_) => f.debug_tuple("Once").finish(),
72        }
73    }
74}
75
76/// A normal task execution containing a native (rust) function. This type is passed into the
77/// backend either to execute a function or to look up a cached result.
78#[derive(Debug, Eq)]
79pub struct CachedTaskType {
80    pub native_fn: &'static NativeFunction,
81    pub this: Option<RawVc>,
82    pub arg: Box<dyn DynTaskInputs>,
83}
84
85impl CachedTaskType {
86    /// Get the name of the function. Equivalent to the
87    /// [`Display`]/[`ToString::to_string`] implementation, but does not allocate a [`String`].
88    pub fn get_name(&self) -> &'static str {
89        self.native_fn.ty.name
90    }
91
92    /// Encodes this task type directly to a hasher, avoiding buffer allocation.
93    ///
94    /// This uses the same encoding logic as [`TurboBincodeEncode`] but writes
95    /// directly to a [`DeterministicHasher`] instead of a buffer.
96    pub fn hash_encode<H: DeterministicHasher>(&self, hasher: &mut H) {
97        Self::hash_encode_components(self.native_fn, self.this, &*self.arg, hasher);
98    }
99}
100
101impl TurboBincodeEncode for CachedTaskType {
102    fn encode(&self, encoder: &mut TurboBincodeEncoder) -> Result<(), EncodeError> {
103        Encode::encode(&registry::get_function_id(self.native_fn), encoder)?;
104
105        let (encode_arg_any, _) = self.native_fn.arg_meta.bincode;
106        Encode::encode(&self.this, encoder)?;
107        encode_arg_any(&*self.arg, encoder)?;
108
109        Ok(())
110    }
111}
112
113impl<Context> TurboBincodeDecode<Context> for CachedTaskType {
114    fn decode(decoder: &mut TurboBincodeDecoder) -> Result<Self, DecodeError> {
115        let native_fn = registry::get_native_function(Decode::decode(decoder)?);
116
117        let (_, decode_arg_any) = native_fn.arg_meta.bincode;
118        let this = Decode::decode(decoder)?;
119        let arg = decode_arg_any(decoder)?;
120
121        Ok(Self {
122            native_fn,
123            this,
124            arg,
125        })
126    }
127}
128
129impl_encode_for_turbo_bincode_encode!(CachedTaskType);
130impl_decode_for_turbo_bincode_decode!(CachedTaskType);
131impl_borrow_decode!(CachedTaskType);
132
133/// A reference-counted pointer to a [`CachedTaskType`] using `triomphe::Arc`.
134///
135/// `triomphe::Arc` saves one `usize` per allocation (no weak count) and avoids the weak-count
136/// CAS in `drop_slow` compared to `std::sync::Arc`. We never need `Weak<CachedTaskType>`, so
137/// the trade-off is favorable.
138#[derive(Clone, Debug, Hash, PartialEq, Eq)]
139pub struct CachedTaskTypeArc(pub triomphe::Arc<CachedTaskType>);
140
141impl CachedTaskTypeArc {
142    pub fn new(value: CachedTaskType) -> Self {
143        Self(triomphe::Arc::new(value))
144    }
145
146    pub fn count(&self) -> usize {
147        triomphe::Arc::count(&self.0)
148    }
149}
150
151impl AsRef<CachedTaskType> for CachedTaskTypeArc {
152    fn as_ref(&self) -> &CachedTaskType {
153        &self.0
154    }
155}
156
157impl Deref for CachedTaskTypeArc {
158    type Target = CachedTaskType;
159    #[inline]
160    fn deref(&self) -> &CachedTaskType {
161        &self.0
162    }
163}
164
165impl Borrow<CachedTaskType> for CachedTaskTypeArc {
166    #[inline]
167    fn borrow(&self) -> &CachedTaskType {
168        &self.0
169    }
170}
171
172impl Display for CachedTaskTypeArc {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        Display::fmt(&**self, f)
175    }
176}
177
178impl Encode for CachedTaskTypeArc {
179    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
180        <CachedTaskType as Encode>::encode(self, encoder)
181    }
182}
183
184impl<Context> Decode<Context> for CachedTaskTypeArc {
185    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
186        Ok(Self::new(<CachedTaskType as Decode<Context>>::decode(
187            decoder,
188        )?))
189    }
190}
191
192impl<'de, Context> bincode::BorrowDecode<'de, Context> for CachedTaskTypeArc {
193    fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = Context>>(
194        decoder: &mut D,
195    ) -> Result<Self, DecodeError> {
196        Ok(Self::new(<CachedTaskType as bincode::BorrowDecode<
197            'de,
198            Context,
199        >>::borrow_decode(decoder)?))
200    }
201}
202
203// Manual implementation is needed because of a borrow issue with `Box<dyn Trait>`:
204// https://github.com/rust-lang/rust/issues/31740
205impl PartialEq for CachedTaskType {
206    #[expect(clippy::op_ref)]
207    fn eq(&self, other: &Self) -> bool {
208        self.native_fn == other.native_fn && self.this == other.this && &self.arg == &other.arg
209    }
210}
211
212// Manual implementation because we have to have a manual `PartialEq` implementation, and clippy
213// complains if we have a derived `Hash` impl, but manual `PartialEq` impl.
214impl Hash for CachedTaskType {
215    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
216        self.native_fn.hash(state);
217        self.this.hash(state);
218        self.arg.hash(state);
219    }
220}
221
222impl Display for CachedTaskType {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        f.write_str(self.get_name())
225    }
226}
227
228impl CachedTaskType {
229    /// Compute the hash of a task type from its individual components, matching the Hash impl.
230    /// This avoids constructing a full CachedTaskType just to compute the hash.
231    pub fn hash_from_components(
232        hasher: &impl BuildHasher,
233        native_fn: &'static NativeFunction,
234        this: Option<RawVc>,
235        arg: &dyn DynTaskInputs,
236    ) -> u64 {
237        use std::hash::Hasher;
238        let mut state = hasher.build_hasher();
239        native_fn.hash(&mut state);
240        this.hash(&mut state);
241        arg.hash(&mut state);
242        state.finish()
243    }
244
245    /// Compute the deterministic hash for backing storage from components.
246    ///
247    /// This mirrors the logic in [`CachedTaskType::hash_encode`] but works with
248    /// borrowed components, avoiding the need to construct a full [`CachedTaskType`].
249    pub fn hash_encode_components<H: DeterministicHasher>(
250        native_fn: &'static NativeFunction,
251        this: Option<RawVc>,
252        arg: &dyn DynTaskInputs,
253        hasher: &mut H,
254    ) {
255        let fn_id = registry::get_function_id(native_fn);
256        {
257            let mut encoder = new_hash_encoder(hasher);
258            Encode::encode(&fn_id, &mut encoder).expect("fn_id encoding should not fail");
259            Encode::encode(&this, &mut encoder).expect("this encoding should not fail");
260        }
261        (native_fn.arg_meta.hash_encode)(arg, hasher);
262    }
263
264    /// Check equality of components against this CachedTaskType.
265    pub fn eq_components(
266        &self,
267        native_fn: &'static NativeFunction,
268        this: Option<RawVc>,
269        arg: &dyn DynTaskInputs,
270    ) -> bool {
271        std::ptr::eq(self.native_fn, native_fn) && self.this == this && &*self.arg == arg
272    }
273}
274
275pub struct TaskExecutionSpec<'a> {
276    pub future: Pin<Box<dyn Future<Output = Result<RawVc>> + Send + 'a>>,
277    pub span: Span,
278}
279
280#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
281pub struct CellContent(pub Option<SharedReference>);
282#[derive(Clone, Debug, PartialEq, Eq, Hash)]
283pub struct TypedCellContent(pub ValueTypeId, pub CellContent);
284
285impl Display for CellContent {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        match &self.0 {
288            None => write!(f, "empty"),
289            Some(content) => Display::fmt(content, f),
290        }
291    }
292}
293
294impl TypedCellContent {
295    pub fn cast<T: VcValueType>(self) -> Result<ReadRef<T>> {
296        let data = self.1.0.ok_or_else(|| anyhow!("Cell is empty"))?;
297        let data = data
298            .downcast::<T>()
299            .map_err(|_err| anyhow!("Unexpected type in cell"))?;
300        Ok(ReadRef::new_arc(data))
301    }
302
303    /// # Safety
304    ///
305    /// The caller must ensure that the TypedCellContent contains a vc
306    /// that implements T.
307    pub fn cast_trait<T>(self) -> Result<TraitRef<T>>
308    where
309        T: VcValueTrait + ?Sized,
310    {
311        let shared_reference = self
312            .1
313            .0
314            .ok_or_else(|| anyhow!("Cell is empty"))?
315            .into_typed(self.0);
316        Ok(
317            // Safety: It is a TypedSharedReference
318            TraitRef::new(shared_reference),
319        )
320    }
321
322    pub fn into_untyped(self) -> CellContent {
323        self.1
324    }
325
326    pub fn encode(&self, enc: &mut TurboBincodeEncoder) -> Result<(), EncodeError> {
327        let Self(type_id, content) = self;
328        let value_type = registry::get_value_type(*type_id);
329        type_id.encode(enc)?;
330        if let ValueTypePersistence::Persistable(encode_fn, _) = value_type.persistence {
331            if let Some(reference) = &content.0 {
332                true.encode(enc)?;
333                encode_fn(&*reference.0, enc)?;
334                Ok(())
335            } else {
336                false.encode(enc)?;
337                Ok(())
338            }
339        } else {
340            Ok(())
341        }
342    }
343
344    pub fn decode(dec: &mut TurboBincodeDecoder) -> Result<Self, DecodeError> {
345        let type_id = ValueTypeId::decode(dec)?;
346        let value_type = registry::get_value_type(type_id);
347        if let ValueTypePersistence::Persistable(_, decode_fn) = value_type.persistence {
348            let is_some = bool::decode(dec)?;
349            if is_some {
350                let reference = decode_fn(dec)?;
351                return Ok(TypedCellContent(type_id, CellContent(Some(reference))));
352            }
353        }
354        Ok(TypedCellContent(type_id, CellContent(None)))
355    }
356}
357
358impl From<TypedSharedReference> for TypedCellContent {
359    fn from(value: TypedSharedReference) -> Self {
360        TypedCellContent(value.type_id, CellContent(Some(value.reference)))
361    }
362}
363
364impl TryFrom<TypedCellContent> for TypedSharedReference {
365    type Error = TypedCellContent;
366
367    fn try_from(content: TypedCellContent) -> Result<Self, TypedCellContent> {
368        if let TypedCellContent(type_id, CellContent(Some(reference))) = content {
369            Ok(TypedSharedReference { type_id, reference })
370        } else {
371            Err(content)
372        }
373    }
374}
375
376impl CellContent {
377    pub fn into_typed(self, type_id: ValueTypeId) -> TypedCellContent {
378        TypedCellContent(type_id, self)
379    }
380}
381
382impl From<SharedReference> for CellContent {
383    fn from(value: SharedReference) -> Self {
384        CellContent(Some(value))
385    }
386}
387
388impl From<Option<SharedReference>> for CellContent {
389    fn from(value: Option<SharedReference>) -> Self {
390        CellContent(value)
391    }
392}
393
394impl TryFrom<CellContent> for SharedReference {
395    type Error = CellContent;
396
397    fn try_from(content: CellContent) -> Result<Self, CellContent> {
398        if let CellContent(Some(shared_reference)) = content {
399            Ok(shared_reference)
400        } else {
401            Err(content)
402        }
403    }
404}
405
406pub type TaskCollectiblesMap = AutoMap<RawVc, i32, BuildHasherDefault<FxHasher>, 1>;
407
408/// A 128-bit content hash stored as little-endian bytes.
409///
410/// Using a byte array rather than `u128` keeps the alignment at 1 byte, which avoids padding
411/// in structures such as `AutoMap`/`LazyField` enums that would otherwise grow to accommodate
412/// `u128`'s 16-byte alignment requirement.
413pub type CellHash = [u8; 16];
414
415// Structurally and functionally similar to Cow<&'static, str> but explicitly notes the importance
416// of non-static strings potentially containing PII (Personal Identifiable Information).
417#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
418pub enum TurboTasksExecutionErrorMessage {
419    PIISafe(#[bincode(with = "turbo_bincode::owned_cow")] Cow<'static, str>),
420    NonPIISafe(String),
421}
422
423impl Display for TurboTasksExecutionErrorMessage {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        match self {
426            TurboTasksExecutionErrorMessage::PIISafe(msg) => write!(f, "{msg}"),
427            TurboTasksExecutionErrorMessage::NonPIISafe(msg) => write!(f, "{msg}"),
428        }
429    }
430}
431
432#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
433pub struct TurboTasksError {
434    pub message: TurboTasksExecutionErrorMessage,
435    pub source: Option<TurboTasksExecutionError>,
436}
437
438/// Error context indicating that a task's execution failed. Stores a `task_id` and a reference to
439/// the `TurboTasksCallApi` so that the task name can be resolved lazily at display time (via
440/// [`TurboTasksCallApi::get_task_name`]) rather than eagerly at error creation time.
441#[derive(Clone)]
442pub struct TurboTaskContextError {
443    pub turbo_tasks: Arc<dyn TurboTasksCallApi>,
444    pub task_id: TaskId,
445    pub source: Option<TurboTasksExecutionError>,
446}
447
448impl PartialEq for TurboTaskContextError {
449    fn eq(&self, other: &Self) -> bool {
450        self.task_id == other.task_id && self.source == other.source
451    }
452}
453impl Eq for TurboTaskContextError {}
454
455impl Encode for TurboTaskContextError {
456    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
457        Encode::encode(&self.task_id, encoder)?;
458        Encode::encode(&self.source, encoder)?;
459        Ok(())
460    }
461}
462
463impl<Context> Decode<Context> for TurboTaskContextError {
464    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
465        let task_id = Decode::decode(decoder)?;
466        let source = Decode::decode(decoder)?;
467        let turbo_tasks = turbo_tasks();
468        Ok(Self {
469            turbo_tasks,
470            task_id,
471            source,
472        })
473    }
474}
475
476impl_borrow_decode!(TurboTaskContextError);
477
478impl Debug for TurboTaskContextError {
479    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480        f.debug_struct("TurboTaskContextError")
481            .field("task_id", &self.task_id)
482            .field("source", &self.source)
483            .finish()
484    }
485}
486
487/// Error context for a local task that failed. Unlike [`TurboTaskContextError`],
488/// this stores the task name directly since local tasks don't have a [`TaskId`].
489#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
490pub struct TurboTaskLocalContextError {
491    pub name: RcStr,
492    pub source: Option<TurboTasksExecutionError>,
493}
494
495#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
496pub enum TurboTasksExecutionError {
497    Panic(Arc<TurboTasksPanic>),
498    Error(Arc<TurboTasksError>),
499    TaskContext(Arc<TurboTaskContextError>),
500    LocalTaskContext(Arc<TurboTaskLocalContextError>),
501}
502
503impl TurboTasksExecutionError {
504    /// Wraps this error in a [`TaskContext`](TurboTasksExecutionError::TaskContext) layer
505    /// identifying the normal task that encountered the error.
506    pub fn with_task_context(
507        self,
508        task_id: TaskId,
509        turbo_tasks: Arc<dyn TurboTasksCallApi>,
510    ) -> Self {
511        TurboTasksExecutionError::TaskContext(Arc::new(TurboTaskContextError {
512            task_id,
513            turbo_tasks,
514            source: Some(self),
515        }))
516    }
517
518    /// Wraps this error in a [`LocalTaskContext`](TurboTasksExecutionError::LocalTaskContext) layer
519    /// identifying the local task that encountered the error.
520    pub fn with_local_task_context(self, name: String) -> Self {
521        TurboTasksExecutionError::LocalTaskContext(Arc::new(TurboTaskLocalContextError {
522            name: RcStr::from(name),
523            source: Some(self),
524        }))
525    }
526}
527
528impl Error for TurboTasksExecutionError {
529    fn source(&self) -> Option<&(dyn Error + 'static)> {
530        match self {
531            TurboTasksExecutionError::Panic(_panic) => None,
532            TurboTasksExecutionError::Error(error) => {
533                error.source.as_ref().map(|s| s as &dyn Error)
534            }
535            TurboTasksExecutionError::TaskContext(context_error) => {
536                context_error.source.as_ref().map(|s| s as &dyn Error)
537            }
538            TurboTasksExecutionError::LocalTaskContext(context_error) => {
539                context_error.source.as_ref().map(|s| s as &dyn Error)
540            }
541        }
542    }
543}
544
545impl Display for TurboTasksExecutionError {
546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547        match self {
548            TurboTasksExecutionError::Panic(panic) => write!(f, "{}", panic),
549            TurboTasksExecutionError::Error(error) => {
550                write!(f, "{}", error.message)
551            }
552            TurboTasksExecutionError::TaskContext(context_error) => {
553                let task_id = context_error.task_id;
554                let name = context_error.turbo_tasks.get_task_name(task_id);
555                if cfg!(feature = "task_id_details") {
556                    write!(f, "Execution of {name} ({}) failed", task_id)
557                } else {
558                    write!(f, "Execution of {name} failed")
559                }
560            }
561            TurboTasksExecutionError::LocalTaskContext(context_error) => {
562                write!(f, "Execution of {} failed", context_error.name)
563            }
564        }
565    }
566}
567
568impl<'l> From<&'l (dyn std::error::Error + 'static)> for TurboTasksExecutionError {
569    fn from(err: &'l (dyn std::error::Error + 'static)) -> Self {
570        if let Some(err) = err.downcast_ref::<TurboTasksExecutionError>() {
571            return err.clone();
572        }
573        let message = err.to_string();
574        let source = err.source().map(|source| source.into());
575
576        TurboTasksExecutionError::Error(Arc::new(TurboTasksError {
577            message: TurboTasksExecutionErrorMessage::NonPIISafe(message),
578            source,
579        }))
580    }
581}
582
583impl From<anyhow::Error> for TurboTasksExecutionError {
584    fn from(err: anyhow::Error) -> Self {
585        let current: &(dyn std::error::Error + 'static) = err.as_ref();
586        current.into()
587    }
588}
589
590pub enum VerificationMode {
591    EqualityCheck,
592    Skip,
593}
594
595pub trait Backend: Sized + Sync + Send {
596    #[allow(unused_variables)]
597    fn startup(&self, turbo_tasks: &TurboTasks<Self>) {}
598
599    #[allow(unused_variables)]
600    fn stop(&self, turbo_tasks: &TurboTasks<Self>) {}
601    #[allow(unused_variables)]
602    fn stopping(&self, turbo_tasks: &TurboTasks<Self>) {}
603
604    #[allow(unused_variables)]
605    fn idle_start(&self, turbo_tasks: &TurboTasks<Self>) {}
606    #[allow(unused_variables)]
607    fn idle_end(&self, turbo_tasks: &TurboTasks<Self>) {}
608
609    fn invalidate_task(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>);
610
611    fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks<Self>);
612    fn invalidate_tasks_set(&self, tasks: &TaskIdSet, turbo_tasks: &TurboTasks<Self>);
613
614    fn invalidate_serialization(&self, _task: TaskId, _turbo_tasks: &TurboTasks<Self>) {}
615
616    fn try_start_task_execution<'a>(
617        &'a self,
618        task: TaskId,
619        priority: TaskPriority,
620        turbo_tasks: &TurboTasks<Self>,
621    ) -> Option<TaskExecutionSpec<'a>>;
622
623    fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>);
624
625    /// Called when a task's execution finishes.
626    ///
627    /// Returns `Some(priority)` if the task was invalidated again while executing and must be
628    /// re-run. The caller is responsible for re-scheduling the task at the returned priority
629    /// (typically lower than the priority of the just-finished run).
630    fn task_execution_completed(
631        &self,
632        task: TaskId,
633        result: Result<RawVc, TurboTasksExecutionError>,
634        cell_counters: &AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>,
635        #[cfg(feature = "verify_determinism")] stateful: bool,
636        has_invalidator: bool,
637        turbo_tasks: &TurboTasks<Self>,
638    ) -> Option<TaskPriority>;
639
640    type BackendJob: Send + 'static;
641
642    fn run_backend_job<'a>(
643        &'a self,
644        job: Self::BackendJob,
645        turbo_tasks: &'a TurboTasks<Self>,
646    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
647
648    /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so
649    /// using it could break cache invalidation.
650    fn try_read_task_output(
651        &self,
652        task: TaskId,
653        reader: Option<TaskId>,
654        options: ReadOutputOptions,
655        turbo_tasks: &TurboTasks<Self>,
656    ) -> Result<ReadOutcome<RawVc>>;
657
658    /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so
659    /// using it could break cache invalidation.
660    fn try_read_task_cell(
661        &self,
662        task: TaskId,
663        index: CellId,
664        reader: Option<TaskId>,
665        options: ReadCellOptions,
666        turbo_tasks: &TurboTasks<Self>,
667    ) -> Result<ReadOutcome<TypedCellContent>>;
668
669    /// INVALIDATION: Be careful with this, it will not track dependencies, so
670    /// using it could break cache invalidation.
671    fn try_read_own_task_cell(
672        &self,
673        current_task: TaskId,
674        index: CellId,
675        turbo_tasks: &TurboTasks<Self>,
676    ) -> Result<TypedCellContent>;
677
678    /// INVALIDATION: Be careful with this, when reader is None, it will not track dependencies, so
679    /// using it could break cache invalidation.
680    fn read_task_collectibles(
681        &self,
682        task: TaskId,
683        trait_id: TraitTypeId,
684        reader: Option<TaskId>,
685        turbo_tasks: &TurboTasks<Self>,
686    ) -> TaskCollectiblesMap;
687
688    fn emit_collectible(
689        &self,
690        trait_type: TraitTypeId,
691        collectible: RawVc,
692        task: TaskId,
693        turbo_tasks: &TurboTasks<Self>,
694    );
695
696    fn unemit_collectible(
697        &self,
698        trait_type: TraitTypeId,
699        collectible: RawVc,
700        count: u32,
701        task: TaskId,
702        turbo_tasks: &TurboTasks<Self>,
703    );
704
705    fn update_task_cell(
706        &self,
707        task: TaskId,
708        index: CellId,
709        content: CellContent,
710        updated_key_hashes: Option<SmallVec<[u64; 2]>>,
711        content_hash: Option<CellHash>,
712        verification_mode: VerificationMode,
713        turbo_tasks: &TurboTasks<Self>,
714    );
715
716    fn get_or_create_task(
717        &self,
718        native_fn: &'static NativeFunction,
719        this: Option<RawVc>,
720        arg: &mut dyn DynTaskInputsStorage,
721        parent_task: Option<TaskId>,
722        persistence: TaskPersistence,
723        turbo_tasks: &TurboTasks<Self>,
724    ) -> TaskId;
725
726    fn connect_task(
727        &self,
728        task: TaskId,
729        parent_task: Option<TaskId>,
730        turbo_tasks: &TurboTasks<Self>,
731    );
732
733    fn mark_own_task_as_finished(&self, _task: TaskId, _turbo_tasks: &TurboTasks<Self>) {
734        // Do nothing by default
735    }
736
737    /// Pin a task against garbage collection (via [`prevent_gc`](crate::prevent_gc)). A pinned task
738    /// is treated as a GC root, keeping alive references that escape the tracked task graph. The
739    /// default is a no-op for backends without GC.
740    fn pin_task_for_gc(&self, _task: TaskId, _turbo_tasks: &TurboTasks<Self>);
741
742    /// Removes a pin added by [`pin_task_for_gc`](Backend::pin_task_for_gc).
743    fn unpin_task_for_gc(&self, _task: TaskId, _turbo_tasks: &TurboTasks<Self>);
744
745    fn create_transient_task(
746        &self,
747        task_type: TransientTaskType,
748        turbo_tasks: &TurboTasks<Self>,
749    ) -> TaskId;
750
751    fn dispose_root_task(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>);
752
753    fn task_statistics(&self) -> &TaskStatisticsApi;
754
755    fn is_tracking_dependencies(&self) -> bool;
756
757    /// Returns a human-readable name for the given task. Used by error display formatting
758    /// to lazily resolve task names instead of storing them eagerly in error objects.
759    fn get_task_name(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>) -> String;
760}
761
762#[cfg(test)]
763mod cached_task_type_tests {
764    use std::{collections::hash_map::RandomState, hash::BuildHasher};
765
766    use crate::{
767        RawVc, TaskId,
768        backend::CachedTaskType,
769        dyn_task_inputs::DynTaskInputs,
770        macro_helpers::{ArgMeta, NativeFunction, into_task_fn},
771    };
772
773    // Two distinct static NativeFunctions for testing pointer-based identity.
774    //
775    // NativeFunction uses pointer-based Hash/Eq (via `turbo_registry!`), so each
776    // static gets a unique address that serves as its identity.
777    fn dummy_fn_a() {}
778    fn dummy_fn_b() {}
779
780    static FN_A: NativeFunction = NativeFunction::new(
781        "dummy_fn_a",
782        "dummy_fn_a",
783        ArgMeta::new::<(i32,)>(),
784        &into_task_fn(dummy_fn_a),
785        false,
786        false,
787    );
788
789    static FN_B: NativeFunction = NativeFunction::new(
790        "dummy_fn_b",
791        "dummy_fn_b",
792        ArgMeta::new::<(i32,)>(),
793        &into_task_fn(dummy_fn_b),
794        false,
795        false,
796    );
797
798    /// Build a `u64` hash for a `CachedTaskType` using its `Hash` impl and a `RandomState`.
799    fn hash_task(rs: &RandomState, task: &CachedTaskType) -> u64 {
800        rs.hash_one(task)
801    }
802
803    /// Build an arg `Box<dyn DynTaskInputs>` for `(i32,)`.
804    fn make_arg(value: i32) -> Box<dyn DynTaskInputs> {
805        Box::new((value,))
806    }
807
808    /// Build a `Some(RawVc::TaskOutput(..))` this value.
809    fn make_this(id: u32) -> Option<RawVc> {
810        Some(RawVc::task_output(
811            TaskId::new(id).expect("non-zero task id"),
812        ))
813    }
814
815    // -----------------------------------------------------------------------
816    // 1. hash_from_components matches Hash impl on CachedTaskType
817    // -----------------------------------------------------------------------
818
819    #[test]
820    fn hash_from_components_matches_hash_impl_no_this() {
821        let rs = RandomState::new();
822        let arg = make_arg(42);
823        let task = CachedTaskType {
824            native_fn: &FN_A,
825            this: None,
826            arg: make_arg(42),
827        };
828        let expected = hash_task(&rs, &task);
829        let actual = CachedTaskType::hash_from_components(&rs, &FN_A, None, &*arg);
830        assert_eq!(actual, expected);
831    }
832
833    #[test]
834    fn hash_from_components_matches_hash_impl_with_this() {
835        let rs = RandomState::new();
836        let this = make_this(1);
837        let arg = make_arg(99);
838        let task = CachedTaskType {
839            native_fn: &FN_A,
840            this,
841            arg: make_arg(99),
842        };
843        let expected = hash_task(&rs, &task);
844        let actual = CachedTaskType::hash_from_components(&rs, &FN_A, this, &*arg);
845        assert_eq!(actual, expected);
846    }
847
848    // -----------------------------------------------------------------------
849    // 2. eq_components returns true when all components match
850    // -----------------------------------------------------------------------
851
852    #[test]
853    fn eq_components_returns_true_when_all_match() {
854        let task = CachedTaskType {
855            native_fn: &FN_A,
856            this: None,
857            arg: make_arg(7),
858        };
859        assert!(task.eq_components(&FN_A, None, &(7i32,)));
860    }
861
862    #[test]
863    fn eq_components_returns_true_with_matching_this() {
864        let this = make_this(1);
865        let task = CachedTaskType {
866            native_fn: &FN_A,
867            this,
868            arg: make_arg(7),
869        };
870        assert!(task.eq_components(&FN_A, this, &(7i32,)));
871    }
872
873    // -----------------------------------------------------------------------
874    // 3. eq_components returns false when native_fn differs
875    // -----------------------------------------------------------------------
876
877    #[test]
878    fn eq_components_returns_false_when_native_fn_differs() {
879        let task = CachedTaskType {
880            native_fn: &FN_A,
881            this: None,
882            arg: make_arg(7),
883        };
884        // FN_B is a different static, so ptr::eq will be false
885        assert!(!task.eq_components(&FN_B, None, &(7i32,)));
886    }
887
888    // -----------------------------------------------------------------------
889    // 4. eq_components returns false when `this` differs
890    // -----------------------------------------------------------------------
891
892    #[test]
893    fn eq_components_returns_false_when_this_differs() {
894        let task = CachedTaskType {
895            native_fn: &FN_A,
896            this: None,
897            arg: make_arg(7),
898        };
899        // Task has this=None, but we check with Some(...)
900        assert!(!task.eq_components(&FN_A, make_this(1), &(7i32,)));
901    }
902
903    #[test]
904    fn eq_components_returns_false_when_this_has_different_task_id() {
905        let task = CachedTaskType {
906            native_fn: &FN_A,
907            this: make_this(1),
908            arg: make_arg(7),
909        };
910        assert!(!task.eq_components(&FN_A, make_this(2), &(7i32,)));
911    }
912
913    // -----------------------------------------------------------------------
914    // 5. eq_components returns false when arg differs
915    // -----------------------------------------------------------------------
916
917    #[test]
918    fn eq_components_returns_false_when_arg_differs() {
919        let task = CachedTaskType {
920            native_fn: &FN_A,
921            this: None,
922            arg: make_arg(1),
923        };
924        // Same function and this, but different arg value
925        assert!(!task.eq_components(&FN_A, None, &(2i32,)));
926    }
927}