Skip to main content

turbo_tasks/
backend.rs

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