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