Skip to main content

turbo_tasks/
manager.rs

1use std::{
2    cell::Cell,
3    cmp::Reverse,
4    fmt::{Debug, Display},
5    future::Future,
6    hash::{BuildHasher, BuildHasherDefault},
7    mem::take,
8    ops::Deref,
9    panic::AssertUnwindSafe,
10    pin::Pin,
11    process::abort,
12    sync::{
13        Arc, Mutex, RwLock, Weak,
14        atomic::{AtomicBool, AtomicUsize, Ordering},
15    },
16    task::{Context, Poll, Waker},
17    time::{Duration, Instant},
18};
19
20use anyhow::{Result, anyhow};
21use auto_hash_map::AutoMap;
22use bincode::{Decode, Encode};
23use either::Either;
24use futures::FutureExt;
25use rustc_hash::{FxBuildHasher, FxHasher};
26use serde::{Deserialize, Serialize};
27use smallvec::SmallVec;
28use tokio::{select, sync::mpsc::Receiver, task_local};
29use tracing::{Instrument, Span, instrument};
30use turbo_tasks_hash::{DeterministicHash, hash_xxh3_hash128};
31
32use crate::{
33    CellId, Completion, InvalidationReason, InvalidationReasonSet, OutputContent, RawVc,
34    ReadCellOptions, ReadOutcome, ReadOutputOptions, ResolvedVc, SharedReference, TaskId,
35    TraitMethod, ValueTypeId, Vc, VcRead, VcValueTrait, VcValueType,
36    backend::{
37        Backend, CellContent, CellHash, TaskCollectiblesMap, TaskExecutionSpec, TransientTaskType,
38        TurboTasksExecutionError, TypedCellContent, VerificationMode,
39    },
40    capture_future::CaptureFuture,
41    dyn_task_inputs::DynTaskInputsStorage,
42    event::{Event, EventListener},
43    id::{ExecutionId, LocalTaskId, TraitTypeId},
44    keyed::KeyedEq,
45    local_task_tracker::LocalTaskTracker,
46    macro_helpers::NativeFunction,
47    message_queue::{CompilationEvent, CompilationEventQueue},
48    priority_runner::{Claimable, Executor, PriorityRunner},
49    registry,
50    serialization_invalidation::SerializationInvalidator,
51    task::local_task::{LocalTask, LocalTaskSpec, LocalTaskType},
52    task_statistics::TaskStatisticsApi,
53    trace::TraceRawVcs,
54    util::{IdFactory, StaticOrArc},
55};
56
57/// Common base trait for [`TurboTasksApi`] and [`TurboTasks`]. Provides APIs for creating tasks
58/// from function calls.
59pub trait TurboTasksCallApi: Sync + Send {
60    /// Calls a native function with arguments. Resolves arguments when needed
61    /// with a wrapper task.
62    ///
63    /// `inputs_resolved` is `TaskInput::is_resolved(&args)` computed at the macro callsite on
64    /// the concrete tuple type — when [`InputResolution::Resolved`], the fast path skips wrapper
65    /// task creation.
66    fn dynamic_call(
67        &self,
68        native_fn: &'static NativeFunction,
69        this: Option<RawVc>,
70        arg: &mut dyn DynTaskInputsStorage,
71        inputs_resolved: InputResolution,
72        persistence: TaskPersistence,
73    ) -> RawVc;
74    /// Call a native function with arguments.
75    /// All inputs must be resolved.
76    fn native_call(
77        &self,
78        native_fn: &'static NativeFunction,
79        this: Option<RawVc>,
80        arg: &mut dyn DynTaskInputsStorage,
81        persistence: TaskPersistence,
82    ) -> RawVc;
83    /// Calls a trait method with arguments. First input is the `self` object.
84    /// Uses a wrapper task to resolve.
85    ///
86    /// `inputs_resolved` is the macro-site `InputResolution` of the *exposed* tuple; when filtering
87    /// is involved, the post-filter value is computed inside the filter functor and supersedes
88    /// this argument.
89    fn trait_call(
90        &self,
91        trait_method: &'static TraitMethod,
92        this: RawVc,
93        arg: &mut dyn DynTaskInputsStorage,
94        inputs_resolved: InputResolution,
95        persistence: TaskPersistence,
96    ) -> RawVc;
97
98    fn run(
99        &self,
100        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
101    ) -> Pin<Box<dyn Future<Output = Result<(), TurboTasksExecutionError>> + Send>>;
102    fn run_once(
103        &self,
104        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
105    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>>;
106    fn run_once_with_reason(
107        &self,
108        reason: StaticOrArc<dyn InvalidationReason>,
109        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
110    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>>;
111    fn start_once_process(&self, future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>);
112
113    /// Sends a compilation event to subscribers.
114    fn send_compilation_event(&self, event: Arc<dyn CompilationEvent>);
115
116    /// Returns a human-readable name for the given task.
117    fn get_task_name(&self, task: TaskId) -> String;
118}
119
120/// A type-erased subset of [`TurboTasks`] stored inside a thread local when we're in a turbo task
121/// context. Returned by the [`turbo_tasks`] helper function.
122///
123/// This trait is needed because thread locals cannot contain an unresolved [`Backend`] type
124/// parameter.
125pub trait TurboTasksApi: TurboTasksCallApi + Sync + Send {
126    fn invalidate(&self, task: TaskId);
127    fn invalidate_with_reason(&self, task: TaskId, reason: StaticOrArc<dyn InvalidationReason>);
128
129    fn invalidate_serialization(&self, task: TaskId);
130
131    fn try_read_task_output(
132        &self,
133        task: TaskId,
134        options: ReadOutputOptions,
135    ) -> Result<ReadOutcome<RawVc>>;
136
137    fn try_read_task_cell(
138        &self,
139        task: TaskId,
140        index: CellId,
141        options: ReadCellOptions,
142    ) -> Result<ReadOutcome<TypedCellContent>>;
143
144    /// Reads a [`RawVc::LocalOutput`]. If the task has completed, returns the [`RawVc`] the local
145    /// task points to.
146    ///
147    /// The returned [`RawVc`] may also be a [`RawVc::LocalOutput`], so this may need to be called
148    /// recursively or in a loop.
149    ///
150    /// This does not accept a consistency argument, as you cannot control consistency of a read of
151    /// an operation owned by your own task. Strongly consistent reads are only allowed on
152    /// [`OperationVc`]s, which should never be local tasks.
153    ///
154    /// No dependency tracking will happen as a result of this function call, as it's a no-op for a
155    /// task to depend on itself.
156    ///
157    /// [`OperationVc`]: crate::OperationVc
158    fn try_read_local_output(
159        &self,
160        execution_id: ExecutionId,
161        local_task_id: LocalTaskId,
162    ) -> Result<Result<RawVc, EventListener>>;
163
164    fn read_task_collectibles(&self, task: TaskId, trait_id: TraitTypeId) -> TaskCollectiblesMap;
165
166    /// Executes a task that is scheduled but not started yet inline on the current thread, so that
167    /// a read doesn't have to wait for a worker to pick the task up. Returns whether the task's
168    /// execution completed.
169    ///
170    /// Used by the read paths; see `TurboTasks::try_execute_scheduled_task_inline` for the details.
171    fn try_execute_scheduled_task_inline(&self, key: ScheduleKey) -> bool;
172
173    /// Records that a read waited for a task that a worker was already executing, so it did not try
174    /// to claim it. Diagnostics only, see `TurboTasks::inline_execution_stats`.
175    #[cfg(feature = "inline_execution_stats")]
176    fn note_waited_for_in_progress_task(&self);
177
178    fn emit_collectible(&self, trait_type: TraitTypeId, collectible: RawVc);
179    fn unemit_collectible(&self, trait_type: TraitTypeId, collectible: RawVc, count: u32);
180    fn unemit_collectibles(&self, trait_type: TraitTypeId, collectibles: &TaskCollectiblesMap);
181
182    /// INVALIDATION: Be careful with this, it will not track dependencies, so
183    /// using it could break cache invalidation.
184    fn try_read_own_task_cell(
185        &self,
186        current_task: TaskId,
187        index: CellId,
188    ) -> Result<TypedCellContent>;
189
190    fn read_own_task_cell(&self, task: TaskId, index: CellId) -> Result<TypedCellContent>;
191    fn update_own_task_cell(
192        &self,
193        task: TaskId,
194        index: CellId,
195        content: CellContent,
196        updated_key_hashes: Option<SmallVec<[u64; 2]>>,
197        content_hash: Option<CellHash>,
198        verification_mode: VerificationMode,
199    );
200    fn mark_own_task_as_finished(&self, task: TaskId);
201
202    /// Pin a task against garbage collection. Delegates to
203    /// [`Backend::pin_task_for_gc`](crate::backend::Backend::pin_task_for_gc).
204    fn pin_task_for_gc(&self, task: TaskId);
205
206    /// Removes a pin added by [`pin_task_for_gc`](TurboTasksApi::pin_task_for_gc).
207    fn unpin_task_for_gc(&self, task: TaskId);
208
209    fn connect_task(&self, task: TaskId);
210
211    /// Wraps the given future in the current task.
212    ///
213    /// Beware: this method is not safe to use in production code. It is only intended for use in
214    /// tests and for debugging purposes.
215    fn spawn_detached_for_testing(&self, f: Pin<Box<dyn Future<Output = ()> + Send + 'static>>);
216
217    fn task_statistics(&self) -> &TaskStatisticsApi;
218
219    fn stop_and_wait(&self) -> Pin<Box<dyn Future<Output = ()> + Send>>;
220
221    fn subscribe_to_compilation_events(
222        &self,
223        event_types: Option<Vec<String>>,
224    ) -> Receiver<Arc<dyn CompilationEvent>>;
225
226    // Returns true if TurboTasks is configured to track dependencies.
227    fn is_tracking_dependencies(&self) -> bool;
228}
229
230/// A wrapper around a value that is unused.
231pub struct Unused<T> {
232    inner: T,
233}
234
235impl<T> Unused<T> {
236    /// Creates a new unused value.
237    ///
238    /// # Safety
239    ///
240    /// The wrapped value must not be used.
241    pub unsafe fn new_unchecked(inner: T) -> Self {
242        Self { inner }
243    }
244
245    /// Get the inner value, without consuming the `Unused` wrapper.
246    ///
247    /// # Safety
248    ///
249    /// The user need to make sure that the value stays unused.
250    pub unsafe fn get_unchecked(&self) -> &T {
251        &self.inner
252    }
253
254    /// Unwraps the value, consuming the `Unused` wrapper.
255    pub fn into(self) -> T {
256        self.inner
257    }
258}
259
260#[allow(clippy::manual_non_exhaustive)]
261pub struct UpdateInfo {
262    pub duration: Duration,
263    pub tasks: usize,
264    pub reasons: InvalidationReasonSet,
265    #[allow(dead_code)]
266    placeholder_for_future_fields: (),
267}
268
269#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encode, Decode)]
270pub enum TaskPersistence {
271    /// Tasks that may be persisted across sessions using serialization.
272    Persistent,
273
274    /// Tasks that will be persisted in memory for the life of this session, but won't persist
275    /// between sessions.
276    ///
277    /// This is used for [root tasks][TurboTasks::spawn_root_task] and tasks with an argument of
278    /// type [`TransientValue`][crate::value::TransientValue] or
279    /// [`TransientInstance`][crate::value::TransientInstance].
280    Transient,
281}
282
283impl Display for TaskPersistence {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        match self {
286            TaskPersistence::Persistent => write!(f, "persistent"),
287            TaskPersistence::Transient => write!(f, "transient"),
288        }
289    }
290}
291
292/// Whether a task call's inputs are already resolved, decided on the concrete input tuple at the
293/// call site. Travels alongside [`TaskPersistence`] through [`dynamic_call`] / [`trait_call`].
294#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
295pub enum InputResolution {
296    /// All inputs (and `this`, where applicable) are resolved — eligible for the synchronous fast
297    /// path with no async resolution task.
298    Resolved,
299    /// At least one input is unresolved and must be resolved in a local task first.
300    Unresolved,
301}
302
303impl InputResolution {
304    #[inline]
305    pub fn from_is_resolved(is_resolved: bool) -> Self {
306        if is_resolved {
307            Self::Resolved
308        } else {
309            Self::Unresolved
310        }
311    }
312
313    #[inline]
314    pub fn is_resolved(self) -> bool {
315        matches!(self, Self::Resolved)
316    }
317}
318
319#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
320pub enum ReadConsistency {
321    /// The default behavior for most APIs. Reads are faster, but may return stale values, which
322    /// may later trigger re-computation.
323    #[default]
324    Eventual,
325    /// Ensures all dependencies are fully resolved before returning the cell or output data, at
326    /// the cost of slower reads.
327    ///
328    /// Top-level code that returns data to the user should use strongly consistent reads.
329    Strong,
330}
331
332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
333pub enum ReadCellTracking {
334    /// Reads are tracked as dependencies of the current task.
335    Tracked {
336        /// The key used for the dependency
337        key: Option<u64>,
338    },
339    /// The read is only tracked when there is an error, otherwise it is untracked.
340    ///
341    /// INVALIDATION: Be careful with this, it will not track dependencies, so
342    /// using it could break cache invalidation.
343    TrackOnlyError,
344    /// The read is not tracked as a dependency of the current task.
345    ///
346    /// INVALIDATION: Be careful with this, it will not track dependencies, so
347    /// using it could break cache invalidation.
348    Untracked,
349}
350
351impl ReadCellTracking {
352    pub fn should_track(&self, is_err: bool) -> bool {
353        match self {
354            ReadCellTracking::Tracked { .. } => true,
355            ReadCellTracking::TrackOnlyError => is_err,
356            ReadCellTracking::Untracked => false,
357        }
358    }
359
360    pub fn key(&self) -> Option<u64> {
361        match self {
362            ReadCellTracking::Tracked { key } => *key,
363            ReadCellTracking::TrackOnlyError => None,
364            ReadCellTracking::Untracked => None,
365        }
366    }
367}
368
369impl Default for ReadCellTracking {
370    fn default() -> Self {
371        ReadCellTracking::Tracked { key: None }
372    }
373}
374
375impl Display for ReadCellTracking {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        match self {
378            ReadCellTracking::Tracked { key: None } => write!(f, "tracked"),
379            ReadCellTracking::Tracked { key: Some(key) } => write!(f, "tracked with key {key}"),
380            ReadCellTracking::TrackOnlyError => write!(f, "track only error"),
381            ReadCellTracking::Untracked => write!(f, "untracked"),
382        }
383    }
384}
385
386#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
387pub enum ReadTracking {
388    /// Reads are tracked as dependencies of the current task.
389    #[default]
390    Tracked,
391    /// The read is only tracked when there is an error, otherwise it is untracked.
392    ///
393    /// INVALIDATION: Be careful with this, it will not track dependencies, so
394    /// using it could break cache invalidation.
395    TrackOnlyError,
396    /// The read is not tracked as a dependency of the current task.
397    ///
398    /// INVALIDATION: Be careful with this, it will not track dependencies, so
399    /// using it could break cache invalidation.
400    Untracked,
401}
402
403impl ReadTracking {
404    pub fn should_track(&self, is_err: bool) -> bool {
405        match self {
406            ReadTracking::Tracked => true,
407            ReadTracking::TrackOnlyError => is_err,
408            ReadTracking::Untracked => false,
409        }
410    }
411}
412
413impl Display for ReadTracking {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        match self {
416            ReadTracking::Tracked => write!(f, "tracked"),
417            ReadTracking::TrackOnlyError => write!(f, "track only error"),
418            ReadTracking::Untracked => write!(f, "untracked"),
419        }
420    }
421}
422
423#[derive(Encode, Decode, Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
424pub enum TaskPriority {
425    #[default]
426    Initial,
427    Invalidation {
428        priority: Reverse<u32>,
429    },
430    Recomputation,
431}
432
433impl TaskPriority {
434    pub fn invalidation(priority: u32) -> Self {
435        Self::Invalidation {
436            priority: Reverse(priority),
437        }
438    }
439
440    pub fn initial() -> Self {
441        Self::Initial
442    }
443
444    pub fn leaf() -> Self {
445        Self::Invalidation {
446            priority: Reverse(0),
447        }
448    }
449
450    pub fn in_parent(&self, parent_priority: TaskPriority) -> Self {
451        match self {
452            TaskPriority::Initial => parent_priority,
453            TaskPriority::Invalidation { priority } => {
454                if let TaskPriority::Invalidation {
455                    priority: parent_priority,
456                } = parent_priority
457                    && priority.0 < parent_priority.0
458                {
459                    Self::Invalidation {
460                        priority: Reverse(parent_priority.0.saturating_add(1)),
461                    }
462                } else {
463                    *self
464                }
465            }
466            TaskPriority::Recomputation => TaskPriority::Recomputation,
467        }
468    }
469}
470
471impl Display for TaskPriority {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        match self {
474            TaskPriority::Initial => write!(f, "initial"),
475            TaskPriority::Invalidation { priority } => write!(f, "invalidation({})", priority.0),
476            TaskPriority::Recomputation => write!(f, "recomputation"),
477        }
478    }
479}
480
481enum ScheduledTask {
482    Task {
483        task_id: TaskId,
484        span: Span,
485    },
486    LocalTask {
487        ty: LocalTaskSpec,
488        persistence: TaskPersistence,
489        execution_id: ExecutionId,
490        local_task_id: LocalTaskId,
491        global_task_state: CurrentTaskStateHandle,
492        span: Span,
493    },
494}
495
496/// Identifies a scheduled task, so that a read which is about to wait for it can take it out of the
497/// scheduler queue and execute it inline instead (see `PriorityRunner::claim` and
498/// `execute_read_target_inline`).
499#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
500pub enum ScheduleKey {
501    /// A cached (non-local) task.
502    Task(TaskId),
503    /// A local task, which is only known within the execution that created it.
504    LocalTask(ExecutionId, LocalTaskId),
505}
506
507impl Claimable for ScheduledTask {
508    type Key = ScheduleKey;
509
510    fn claim_key(&self) -> Option<ScheduleKey> {
511        Some(match self {
512            ScheduledTask::Task { task_id, .. } => ScheduleKey::Task(*task_id),
513            ScheduledTask::LocalTask {
514                execution_id,
515                local_task_id,
516                ..
517            } => ScheduleKey::LocalTask(*execution_id, *local_task_id),
518        })
519    }
520}
521
522#[cfg(feature = "inline_execution_stats")]
523use std::sync::atomic::AtomicU64;
524
525/// Counters describing how reads and inline execution interacted, see
526/// [`TurboTasks::inline_execution_stats`]. Diagnostics only.
527#[cfg(feature = "inline_execution_stats")]
528#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
529pub struct InlineExecutionStats {
530    /// Tasks that were put into the scheduler queue.
531    pub queued: u64,
532    /// Reads that tried to take a queued task out of the queue.
533    pub claim_attempted: u64,
534    /// Claims that succeeded and whose execution finished on the reading thread.
535    pub claim_completed: u64,
536    /// Claims that succeeded but whose execution yielded, so it was handed to the runtime.
537    pub claim_yielded: u64,
538    /// Claims that found nothing to take, because a worker had already picked the task up.
539    pub claim_failed: u64,
540    /// Reads that waited without attempting a claim, because the task was already being executed.
541    pub waited_in_progress: u64,
542}
543
544/// The counters behind [`InlineExecutionStats`].
545///
546/// Without the `inline_execution_stats` feature this is zero-sized and every method is an empty
547/// `#[inline]` no-op, so the counting compiles away: the counters sit on the read-miss path, and a
548/// build that doesn't want the numbers shouldn't pay for them.
549#[derive(Default)]
550struct InlineExecutionCounters {
551    #[cfg(feature = "inline_execution_stats")]
552    claim_attempted: AtomicU64,
553    #[cfg(feature = "inline_execution_stats")]
554    claim_completed: AtomicU64,
555    #[cfg(feature = "inline_execution_stats")]
556    claim_yielded: AtomicU64,
557    #[cfg(feature = "inline_execution_stats")]
558    claim_failed: AtomicU64,
559    #[cfg(feature = "inline_execution_stats")]
560    waited_in_progress: AtomicU64,
561}
562
563impl InlineExecutionCounters {
564    /// A read found its task queued and tried to take it over.
565    #[inline]
566    fn claim_attempted(&self) {
567        #[cfg(feature = "inline_execution_stats")]
568        self.claim_attempted.fetch_add(1, Ordering::Relaxed);
569    }
570
571    /// A claim succeeded and the execution finished on the reading thread.
572    #[inline]
573    fn claim_completed(&self) {
574        #[cfg(feature = "inline_execution_stats")]
575        self.claim_completed.fetch_add(1, Ordering::Relaxed);
576    }
577
578    /// A claim succeeded but the execution yielded, so it was handed to the runtime.
579    #[inline]
580    fn claim_yielded(&self) {
581        #[cfg(feature = "inline_execution_stats")]
582        self.claim_yielded.fetch_add(1, Ordering::Relaxed);
583    }
584
585    /// A claim found nothing to take, because a worker had already picked the task up.
586    #[inline]
587    fn claim_failed(&self) {
588        #[cfg(feature = "inline_execution_stats")]
589        self.claim_failed.fetch_add(1, Ordering::Relaxed);
590    }
591
592    /// A read waited without attempting a claim, because the task was already being executed.
593    #[cfg(feature = "inline_execution_stats")]
594    #[inline]
595    fn waited_in_progress(&self) {
596        self.waited_in_progress.fetch_add(1, Ordering::Relaxed);
597    }
598}
599
600/// Whether a dump of [`InlineExecutionStats`] was requested via `TURBO_ENGINE_INLINE_STATS=1`.
601#[cfg(feature = "inline_execution_stats")]
602pub(crate) fn inline_stats_requested() -> bool {
603    static REQUESTED: std::sync::LazyLock<bool> = std::sync::LazyLock::new(|| {
604        std::env::var("TURBO_ENGINE_INLINE_STATS").is_ok_and(|value| value != "0")
605    });
606    *REQUESTED
607}
608
609/// Maximum number of task executions that may be nested inline on a single thread, to conserve
610/// stack space. (The alternative would be growing the stack on demand, the way SWC does.)
611const MAX_INLINE_EXECUTION_DEPTH: usize = 16;
612
613thread_local! {
614    /// How many task executions are currently nested inline on this thread.
615    static INLINE_EXECUTION_DEPTH: Cell<usize> = const { Cell::new(0) };
616}
617
618/// Whether the current thread may execute another task inline, see [`MAX_INLINE_EXECUTION_DEPTH`].
619fn inline_execution_allowed() -> bool {
620    INLINE_EXECUTION_DEPTH.get() < MAX_INLINE_EXECUTION_DEPTH
621}
622
623/// Counts one level of inline task execution on this thread, see [`MAX_INLINE_EXECUTION_DEPTH`].
624struct InlineExecutionDepthGuard;
625
626impl InlineExecutionDepthGuard {
627    fn enter() -> Self {
628        INLINE_EXECUTION_DEPTH.set(INLINE_EXECUTION_DEPTH.get() + 1);
629        Self
630    }
631}
632
633impl Drop for InlineExecutionDepthGuard {
634    fn drop(&mut self) {
635        INLINE_EXECUTION_DEPTH.set(INLINE_EXECUTION_DEPTH.get() - 1);
636    }
637}
638
639/// Polls `future` once inline and then spawns it if it doesn't complete so tokio drives it. Returns
640/// whether it completed.
641fn poll_once_or_spawn(future: impl Future<Output = ()> + Send + 'static) -> bool {
642    let _depth_guard = InlineExecutionDepthGuard::enter();
643    let span_slot = InlineExecutionSpanSlot::default();
644    let mut future = Box::pin(INLINE_EXECUTION_SPAN.scope(span_slot.clone(), future));
645    // A waker that never wakes anything is fine here: if this poll doesn't complete the future we
646    // spawn it, and a spawned task is always polled at least once, which is the poll that registers
647    // the real waker.
648    match future
649        .as_mut()
650        .poll(&mut Context::from_waker(Waker::noop()))
651    {
652        Poll::Ready(()) => {
653            span_slot.record("complete");
654            true
655        }
656        Poll::Pending => {
657            span_slot.record("partial");
658            tokio::task::spawn(future);
659            false
660        }
661    }
662}
663
664/// Executes the task inline if possible, returns true if it executed to completion.
665pub(crate) fn execute_read_target_inline(
666    turbo_tasks: &dyn TurboTasksApi,
667    key: ScheduleKey,
668) -> bool {
669    if !inline_execution_allowed() {
670        // Nested too deeply; a worker will pick the task up, as it always did.
671        return false;
672    }
673    turbo_tasks.try_execute_scheduled_task_inline(key)
674}
675
676pub struct TurboTasks<B: Backend + 'static> {
677    this: Weak<Self>,
678    backend: B,
679    execution_id_factory: IdFactory<ExecutionId>,
680    stopped: AtomicBool,
681    currently_scheduled_foreground_jobs: AtomicUsize,
682    currently_scheduled_background_jobs: AtomicUsize,
683    scheduled_tasks: AtomicUsize,
684    /// Diagnostics for reads and inline execution, see `TurboTasks::inline_execution_stats`.
685    /// Zero-sized without the `inline_execution_stats` feature.
686    inline_counters: InlineExecutionCounters,
687    priority_runner:
688        Arc<PriorityRunner<TurboTasks<B>, ScheduledTask, TaskPriority, TurboTasksExecutor>>,
689    start: Mutex<Option<Instant>>,
690    aggregated_update: Mutex<(Option<(Duration, usize)>, InvalidationReasonSet)>,
691    /// Event that is triggered when currently_scheduled_foreground_jobs becomes non-zero
692    event_foreground_start: Event,
693    /// Event that is triggered when all foreground jobs are done
694    /// (currently_scheduled_foreground_jobs becomes zero)
695    event_foreground_done: Event,
696    /// Event that is triggered when all background jobs are done
697    event_background_done: Event,
698    compilation_events: CompilationEventQueue,
699}
700
701/// Information about a non-local task. A non-local task can contain multiple "local" tasks, which
702/// all share the same non-local task state.
703///
704/// A non-local task is one that:
705///
706/// - Has a unique task id.
707/// - Is potentially cached.
708/// - The backend is aware of.
709struct CurrentTaskState {
710    task_id: Option<TaskId>,
711    execution_id: ExecutionId,
712    priority: TaskPriority,
713
714    /// True if the current task has state in cells (interior mutability).
715    /// Only tracked when verify_determinism feature is enabled.
716    #[cfg(feature = "verify_determinism")]
717    stateful: bool,
718
719    /// True if the current task uses an external invalidator
720    has_invalidator: bool,
721
722    /// True if we're in a top-level task (e.g. `.run_once(...)` or `.run(...)`).
723    /// Eventually consistent reads are not allowed in top-level tasks.
724    in_top_level_task: bool,
725
726    /// Tracks how many cells of each type has been allocated so far during this task execution.
727    /// When a task is re-executed, the cell count may not match the existing cell vec length.
728    ///
729    /// This is taken (and becomes `None`) during teardown of a task.
730    cell_counters: Option<AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>>,
731
732    /// Tracks execution of Local tasks (and detached test futures) created during this global
733    /// task's execution.
734    local_tasks: LocalTaskTracker,
735}
736
737impl CurrentTaskState {
738    fn new(
739        task_id: TaskId,
740        execution_id: ExecutionId,
741        priority: TaskPriority,
742        in_top_level_task: bool,
743    ) -> Self {
744        Self {
745            task_id: Some(task_id),
746            execution_id,
747            priority,
748            #[cfg(feature = "verify_determinism")]
749            stateful: false,
750            has_invalidator: false,
751            in_top_level_task,
752            cell_counters: Some(AutoMap::default()),
753            local_tasks: LocalTaskTracker::new(),
754        }
755    }
756
757    fn new_temporary(
758        execution_id: ExecutionId,
759        priority: TaskPriority,
760        in_top_level_task: bool,
761    ) -> Self {
762        Self {
763            task_id: None,
764            execution_id,
765            priority,
766            #[cfg(feature = "verify_determinism")]
767            stateful: false,
768            has_invalidator: false,
769            in_top_level_task,
770            cell_counters: None,
771            local_tasks: LocalTaskTracker::new(),
772        }
773    }
774
775    fn assert_execution_id(&self, expected_execution_id: ExecutionId) {
776        if self.execution_id != expected_execution_id {
777            panic!(
778                "Local tasks can only be scheduled/awaited within the same execution of the \
779                 parent task that created them"
780            );
781        }
782    }
783}
784
785/// A shareable current-task state handle with the immutable task ID cached
786/// outside the lock. The rest of the state is mutated by global and local
787/// tasks, but the task ID is fixed for the lifetime of an execution.
788#[derive(Clone)]
789struct CurrentTaskStateHandle {
790    inner: Arc<CurrentTaskStateInner>,
791}
792
793struct CurrentTaskStateInner {
794    current_task_id: Option<TaskId>,
795    state: RwLock<CurrentTaskState>,
796}
797
798impl CurrentTaskStateHandle {
799    fn new(state: CurrentTaskState) -> Self {
800        Self {
801            inner: Arc::new(CurrentTaskStateInner {
802                current_task_id: state.task_id,
803                state: RwLock::new(state),
804            }),
805        }
806    }
807
808    fn current_task_id(&self) -> Option<TaskId> {
809        self.inner.current_task_id
810    }
811}
812
813impl Deref for CurrentTaskStateHandle {
814    type Target = RwLock<CurrentTaskState>;
815
816    fn deref(&self) -> &Self::Target {
817        &self.inner.state
818    }
819}
820
821// TODO implement our own thread pool and make these thread locals instead
822task_local! {
823    /// The current TurboTasks instance
824    static TURBO_TASKS: Arc<dyn TurboTasksApi>;
825
826    static CURRENT_TASK_STATE: CurrentTaskStateHandle;
827
828    /// Temporarily suppresses the eventual consistency check in top-level tasks.
829    /// This is used by strongly consistent reads to allow them to succeed in top-level tasks.
830    /// This is NOT shared across local tasks (unlike CURRENT_TASK_STATE), so it's safe
831    /// to set/unset without race conditions.
832    pub(crate) static SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK: bool;
833
834    /// Set only while a reader polls a task it claimed, so that the outcome of that poll can be
835    /// recorded on the *task's* span rather than the reader's, see [`InlineExecutionSpanSlot`].
836    static INLINE_EXECUTION_SPAN: InlineExecutionSpanSlot;
837}
838
839/// Lets a reader that executes a claimed task inline record the outcome on the span of the task it
840/// executed.
841///
842/// The reader only sees the outer execution future, whose instrumented span has already been exited
843/// by the time its `poll` returns — `Span::current()` there is the reader's own span. So the
844/// executor puts the span it is about to instrument the task body with in here, and the reader
845/// records the outcome on it afterwards.
846///
847/// Only present while a claimed task is being polled inline: a task started by a worker doesn't
848/// have this task-local set, and leaves the field unset.
849#[derive(Clone, Default)]
850struct InlineExecutionSpanSlot(Arc<Mutex<Option<Span>>>);
851
852impl InlineExecutionSpanSlot {
853    /// Called by the executor with the span it instruments the task body with.
854    fn set(span: &Span) {
855        let _ = INLINE_EXECUTION_SPAN.try_with(|slot| {
856            *slot.0.lock().unwrap() = Some(span.clone());
857        });
858    }
859
860    /// Records the outcome of the inline poll on the executed task's span, if the executor got far
861    /// enough to register one (it doesn't when the task was already claimed by someone else and the
862    /// execution turns into a no-op).
863    fn record(&self, outcome: &'static str) {
864        if let Some(span) = self.0.lock().unwrap().as_ref() {
865            span.record("inline_execution", outcome);
866        }
867    }
868}
869
870impl<B: Backend + 'static> TurboTasks<B> {
871    // TODO better lifetime management for turbo tasks
872    // consider using unsafe for the task_local turbo tasks
873    // that should be safe as long tasks can't outlife turbo task
874    // so we probably want to make sure that all tasks are joined
875    // when trying to drop turbo tasks
876    pub fn new(backend: B) -> Arc<Self> {
877        let execution_id_factory = IdFactory::new(ExecutionId::MIN, ExecutionId::MAX);
878        let this = Arc::new_cyclic(|this| Self {
879            this: this.clone(),
880            backend,
881            execution_id_factory,
882            stopped: AtomicBool::new(false),
883            currently_scheduled_foreground_jobs: AtomicUsize::new(0),
884            currently_scheduled_background_jobs: AtomicUsize::new(0),
885            scheduled_tasks: AtomicUsize::new(0),
886            inline_counters: InlineExecutionCounters::default(),
887            priority_runner: Arc::new(PriorityRunner::new(TurboTasksExecutor)),
888            start: Default::default(),
889            aggregated_update: Default::default(),
890            event_foreground_done: Event::new(|| {
891                || "TurboTasks::event_foreground_done".to_string()
892            }),
893            event_foreground_start: Event::new(|| {
894                || "TurboTasks::event_foreground_start".to_string()
895            }),
896            event_background_done: Event::new(|| {
897                || "TurboTasks::event_background_done".to_string()
898            }),
899            compilation_events: CompilationEventQueue::default(),
900        });
901        this.backend.startup(&*this);
902        this
903    }
904
905    pub fn pin(&self) -> Arc<Self> {
906        self.this.upgrade().unwrap()
907    }
908
909    /// Creates a new root task
910    pub fn spawn_root_task<T, F, Fut>(&self, functor: F) -> TaskId
911    where
912        T: ?Sized,
913        F: Fn() -> Fut + Send + Sync + Clone + 'static,
914        Fut: Future<Output = Result<Vc<T>>> + Send,
915    {
916        let id = self.backend.create_transient_task(
917            TransientTaskType::Root(Box::new(move || {
918                let functor = functor.clone();
919                Box::pin(async move {
920                    mark_top_level_task();
921                    let raw_vc = functor().await?.node;
922                    raw_vc.to_non_local().await
923                })
924            })),
925            self,
926        );
927        self.schedule(id, TaskPriority::initial());
928        id
929    }
930
931    pub fn dispose_root_task(&self, task_id: TaskId) {
932        self.backend.dispose_root_task(task_id, self);
933    }
934
935    /// Pins a task against garbage collection (a transient, session-only reference). Balanced by
936    /// [`unpin_task_for_gc`](Self::unpin_task_for_gc). Used for references that escape the tracked
937    /// task graph — e.g. a `DetachedVc` holding an `OperationVc` across the NAPI boundary.
938    pub fn pin_task_for_gc(&self, task_id: TaskId) {
939        self.backend.pin_task_for_gc(task_id, self);
940    }
941
942    /// Releases a pin added by [`pin_task_for_gc`](Self::pin_task_for_gc).
943    pub fn unpin_task_for_gc(&self, task_id: TaskId) {
944        self.backend.unpin_task_for_gc(task_id, self);
945    }
946
947    // TODO make sure that all dependencies settle before reading them
948    /// Creates a new root task, that is only executed once.
949    /// Dependencies will not invalidate the task.
950    #[track_caller]
951    fn spawn_once_task<T, Fut>(&self, future: Fut)
952    where
953        T: ?Sized,
954        Fut: Future<Output = Result<Vc<T>>> + Send + 'static,
955    {
956        let id = self.backend.create_transient_task(
957            TransientTaskType::Once(Box::pin(async move {
958                mark_top_level_task();
959                let raw_vc = future.await?.node;
960                raw_vc.to_non_local().await
961            })),
962            self,
963        );
964        self.schedule(id, TaskPriority::initial());
965    }
966
967    pub async fn run_once<T: TraceRawVcs + Send + 'static>(
968        &self,
969        future: impl Future<Output = Result<T>> + Send + 'static,
970    ) -> Result<T> {
971        let (tx, rx) = tokio::sync::oneshot::channel();
972        self.spawn_once_task(async move {
973            mark_top_level_task();
974            let result = future.await;
975            tx.send(result)
976                .map_err(|_| anyhow!("unable to send result"))?;
977            Ok(Completion::new())
978        });
979
980        rx.await?
981    }
982
983    #[tracing::instrument(level = "trace", skip_all, name = "turbo_tasks::run")]
984    pub async fn run<T: TraceRawVcs + Send + 'static>(
985        &self,
986        future: impl Future<Output = Result<T>> + Send + 'static,
987    ) -> Result<T, TurboTasksExecutionError> {
988        self.begin_foreground_job();
989        // it's okay for execution ids to overflow and wrap, they're just used for an assert
990        let execution_id = self.execution_id_factory.wrapping_get();
991        let current_task_state = CurrentTaskStateHandle::new(CurrentTaskState::new_temporary(
992            execution_id,
993            TaskPriority::initial(),
994            true, // in_top_level_task
995        ));
996
997        let result = TURBO_TASKS
998            .scope(
999                self.pin(),
1000                CURRENT_TASK_STATE.scope(current_task_state, async {
1001                    let result = CaptureFuture::new(future).await;
1002
1003                    // wait for all spawned local tasks using `local` to finish
1004                    wait_for_local_tasks().await;
1005
1006                    match result {
1007                        Ok(Ok(value)) => Ok(value),
1008                        Ok(Err(err)) => Err(err.into()),
1009                        Err(err) => Err(TurboTasksExecutionError::Panic(Arc::new(err))),
1010                    }
1011                }),
1012            )
1013            .await;
1014        self.finish_foreground_job();
1015        result
1016    }
1017
1018    pub fn start_once_process(&self, future: impl Future<Output = ()> + Send + 'static) {
1019        let this = self.pin();
1020        tokio::spawn(async move {
1021            this.pin()
1022                .run_once(async move {
1023                    this.finish_foreground_job();
1024                    future.await;
1025                    this.begin_foreground_job();
1026                    Ok(())
1027                })
1028                .await
1029                .unwrap()
1030        });
1031    }
1032
1033    pub(crate) fn native_call(
1034        &self,
1035        native_fn: &'static NativeFunction,
1036        this: Option<RawVc>,
1037        arg: &mut dyn DynTaskInputsStorage,
1038        persistence: TaskPersistence,
1039    ) -> RawVc {
1040        RawVc::task_output(self.backend.get_or_create_task(
1041            native_fn,
1042            this,
1043            arg,
1044            current_task_if_available("turbo_function calls"),
1045            persistence,
1046            self,
1047        ))
1048    }
1049
1050    pub fn dynamic_call(
1051        &self,
1052        native_fn: &'static NativeFunction,
1053        this: Option<RawVc>,
1054        arg: &mut dyn DynTaskInputsStorage,
1055        inputs_resolved: InputResolution,
1056        persistence: TaskPersistence,
1057    ) -> RawVc {
1058        if inputs_resolved.is_resolved() && this.is_none_or(|this| this.is_resolved()) {
1059            return self.native_call(native_fn, this, arg, persistence);
1060        }
1061        // Need async resolution — must move the arg to the heap now
1062        let arg = arg.take_box();
1063        let task_type = LocalTaskSpec {
1064            task_type: LocalTaskType::ResolveNative { native_fn },
1065            this,
1066            arg,
1067        };
1068        self.schedule_local_task(task_type, persistence)
1069    }
1070
1071    pub fn trait_call(
1072        &self,
1073        trait_method: &'static TraitMethod,
1074        this: RawVc,
1075        arg: &mut dyn DynTaskInputsStorage,
1076        inputs_resolved: InputResolution,
1077        persistence: TaskPersistence,
1078    ) -> RawVc {
1079        // avoid creating a wrapper task if self is already resolved
1080        // for resolved cells we already know the value type so we can lookup the
1081        // function
1082        if let Some((_, cell_id)) = this.as_task_cell() {
1083            match registry::get_value_type(cell_id.type_id()).get_trait_method(trait_method) {
1084                Some(native_fn) => {
1085                    if let Some(filter) = native_fn.arg_meta.filter_owned {
1086                        let (resolved, mut arg) = (filter)(arg);
1087                        return self.dynamic_call(
1088                            native_fn,
1089                            Some(this),
1090                            &mut arg,
1091                            resolved,
1092                            persistence,
1093                        );
1094                    } else {
1095                        return self.dynamic_call(
1096                            native_fn,
1097                            Some(this),
1098                            arg,
1099                            inputs_resolved,
1100                            persistence,
1101                        );
1102                    }
1103                }
1104                None => {
1105                    // We are destined to fail at this point, but we just retry resolution in the
1106                    // local task since we cannot report an error from here.
1107                    // TODO: A panic seems appropriate since the immediate caller is to blame
1108                }
1109            }
1110        }
1111
1112        // create a wrapper task to resolve all inputs
1113        let task_type = LocalTaskSpec {
1114            task_type: LocalTaskType::ResolveTrait { trait_method },
1115            this: Some(this),
1116            arg: arg.take_box(),
1117        };
1118
1119        self.schedule_local_task(task_type, persistence)
1120    }
1121
1122    #[track_caller]
1123    pub fn schedule(&self, task_id: TaskId, priority: TaskPriority) {
1124        self.begin_foreground_job();
1125        self.scheduled_tasks.fetch_add(1, Ordering::AcqRel);
1126
1127        let task = ScheduledTask::Task {
1128            task_id,
1129            span: Span::current(),
1130        };
1131        self.priority_runner.schedule(&self.pin(), task, priority);
1132    }
1133
1134    fn schedule_local_task(
1135        &self,
1136        ty: LocalTaskSpec,
1137        // if this is a `LocalTaskType::Resolve*`, we may spawn another task with this persistence,
1138        persistence: TaskPersistence,
1139    ) -> RawVc {
1140        let task_type = ty.task_type;
1141        let (global_task_state, execution_id, priority, local_task_id) =
1142            CURRENT_TASK_STATE.with(|gts| {
1143                let mut gts_write = gts.write().unwrap();
1144                let local_task_id = gts_write.local_tasks.create(task_type);
1145                (
1146                    gts.clone(),
1147                    gts_write.execution_id,
1148                    gts_write.priority,
1149                    local_task_id,
1150                )
1151            });
1152
1153        let task = ScheduledTask::LocalTask {
1154            ty,
1155            persistence,
1156            execution_id,
1157            local_task_id,
1158            global_task_state,
1159            span: Span::current(),
1160        };
1161        self.priority_runner.schedule(&self.pin(), task, priority);
1162
1163        RawVc::local_output(execution_id, local_task_id, persistence)
1164    }
1165
1166    /// Executes the task inline if possible, returns true if it executed to completion.
1167    fn try_execute_scheduled_task_inline(&self, key: ScheduleKey) -> bool {
1168        let this = self.pin();
1169        self.inline_counters.claim_attempted();
1170        if let Some(future) = self.priority_runner.claim(&this, &key) {
1171            let completed = poll_once_or_spawn(future);
1172            if completed {
1173                self.inline_counters.claim_completed();
1174            } else {
1175                self.inline_counters.claim_yielded();
1176            }
1177            return completed;
1178        }
1179        self.inline_counters.claim_failed();
1180        false
1181    }
1182
1183    #[cfg(feature = "inline_execution_stats")]
1184    fn note_waited_for_in_progress_task(&self) {
1185        self.inline_counters.waited_in_progress();
1186    }
1187
1188    fn begin_foreground_job(&self) {
1189        if self
1190            .currently_scheduled_foreground_jobs
1191            .fetch_add(1, Ordering::AcqRel)
1192            == 0
1193        {
1194            *self.start.lock().unwrap() = Some(Instant::now());
1195            self.event_foreground_start.notify(usize::MAX);
1196            self.backend.idle_end(self);
1197        }
1198    }
1199
1200    fn finish_foreground_job(&self) {
1201        if self
1202            .currently_scheduled_foreground_jobs
1203            .fetch_sub(1, Ordering::AcqRel)
1204            == 1
1205        {
1206            self.backend.idle_start(self);
1207            // That's not super race-condition-safe, but it's only for
1208            // statistical reasons
1209            let total = self.scheduled_tasks.load(Ordering::Acquire);
1210            self.scheduled_tasks.store(0, Ordering::Release);
1211            if let Some(start) = *self.start.lock().unwrap() {
1212                let (update, _) = &mut *self.aggregated_update.lock().unwrap();
1213                if let Some(update) = update.as_mut() {
1214                    update.0 += start.elapsed();
1215                    update.1 += total;
1216                } else {
1217                    *update = Some((start.elapsed(), total));
1218                }
1219            }
1220            self.event_foreground_done.notify(usize::MAX);
1221        }
1222    }
1223
1224    fn begin_background_job(&self) {
1225        self.currently_scheduled_background_jobs
1226            .fetch_add(1, Ordering::Relaxed);
1227    }
1228
1229    fn finish_background_job(&self) {
1230        if self
1231            .currently_scheduled_background_jobs
1232            .fetch_sub(1, Ordering::Relaxed)
1233            == 1
1234        {
1235            self.event_background_done.notify(usize::MAX);
1236        }
1237    }
1238
1239    pub fn get_in_progress_count(&self) -> usize {
1240        self.currently_scheduled_foreground_jobs
1241            .load(Ordering::Acquire)
1242    }
1243
1244    /// Counters describing how reads and inline execution interacted. Diagnostics only; a dump of
1245    /// these can be requested with `TURBO_ENGINE_INLINE_STATS=1`.
1246    #[cfg(feature = "inline_execution_stats")]
1247    #[doc(hidden)]
1248    pub fn inline_execution_stats(&self) -> InlineExecutionStats {
1249        let counters = &self.inline_counters;
1250        InlineExecutionStats {
1251            queued: self.priority_runner.total_queued(),
1252            claim_attempted: counters.claim_attempted.load(Ordering::Relaxed),
1253            claim_completed: counters.claim_completed.load(Ordering::Relaxed),
1254            claim_yielded: counters.claim_yielded.load(Ordering::Relaxed),
1255            claim_failed: counters.claim_failed.load(Ordering::Relaxed),
1256            waited_in_progress: counters.waited_in_progress.load(Ordering::Relaxed),
1257        }
1258    }
1259
1260    /// Waits for the given task to finish executing. This works by performing an untracked read,
1261    /// and discarding the value of the task output.
1262    ///
1263    /// [`ReadConsistency::Eventual`] means that this will return after the task executes, but
1264    /// before all dependencies have completely settled.
1265    ///
1266    /// [`ReadConsistency::Strong`] means that this will also wait for the task and all dependencies
1267    /// to fully settle before returning.
1268    ///
1269    /// As this function is typically called in top-level code that waits for results to be ready
1270    /// for the user to access, most callers should use [`ReadConsistency::Strong`].
1271    pub async fn wait_task_completion(
1272        &self,
1273        id: TaskId,
1274        consistency: ReadConsistency,
1275    ) -> Result<()> {
1276        read_task_output(
1277            self,
1278            id,
1279            ReadOutputOptions {
1280                // INVALIDATION: This doesn't return a value, only waits for it to be ready.
1281                tracking: ReadTracking::Untracked,
1282                consistency,
1283            },
1284        )
1285        .await?;
1286        Ok(())
1287    }
1288
1289    /// Returns [UpdateInfo] with all updates aggregated over a given duration
1290    /// (`aggregation`). Will wait until an update happens.
1291    pub async fn get_or_wait_aggregated_update_info(&self, aggregation: Duration) -> UpdateInfo {
1292        self.aggregated_update_info(aggregation, Duration::MAX)
1293            .await
1294            .unwrap()
1295    }
1296
1297    /// Returns [UpdateInfo] with all updates aggregated over a given duration
1298    /// (`aggregation`). Will only return None when the timeout is reached while
1299    /// waiting for the first update.
1300    pub async fn aggregated_update_info(
1301        &self,
1302        aggregation: Duration,
1303        timeout: Duration,
1304    ) -> Option<UpdateInfo> {
1305        let listener = self
1306            .event_foreground_done
1307            .listen_with_note(|| || "wait for update info".to_string());
1308        let wait_for_finish = {
1309            let (update, reason_set) = &mut *self.aggregated_update.lock().unwrap();
1310            if aggregation.is_zero() {
1311                if let Some((duration, tasks)) = update.take() {
1312                    return Some(UpdateInfo {
1313                        duration,
1314                        tasks,
1315                        reasons: take(reason_set),
1316                        placeholder_for_future_fields: (),
1317                    });
1318                } else {
1319                    true
1320                }
1321            } else {
1322                update.is_none()
1323            }
1324        };
1325        if wait_for_finish {
1326            if timeout == Duration::MAX {
1327                // wait for finish
1328                listener.await;
1329            } else {
1330                // wait for start, then wait for finish or timeout
1331                let start_listener = self
1332                    .event_foreground_start
1333                    .listen_with_note(|| || "wait for update info".to_string());
1334                if self
1335                    .currently_scheduled_foreground_jobs
1336                    .load(Ordering::Acquire)
1337                    == 0
1338                {
1339                    start_listener.await;
1340                } else {
1341                    drop(start_listener);
1342                }
1343                if timeout.is_zero() || tokio::time::timeout(timeout, listener).await.is_err() {
1344                    // Timeout
1345                    return None;
1346                }
1347            }
1348        }
1349        if !aggregation.is_zero() {
1350            loop {
1351                select! {
1352                    () = tokio::time::sleep(aggregation) => {
1353                        break;
1354                    }
1355                    () = self.event_foreground_done.listen_with_note(|| || "wait for update info".to_string()) => {
1356                        // Resets the sleep
1357                    }
1358                }
1359            }
1360        }
1361        let (update, reason_set) = &mut *self.aggregated_update.lock().unwrap();
1362        if let Some((duration, tasks)) = update.take() {
1363            Some(UpdateInfo {
1364                duration,
1365                tasks,
1366                reasons: take(reason_set),
1367                placeholder_for_future_fields: (),
1368            })
1369        } else {
1370            panic!("aggregated_update_info must not called concurrently")
1371        }
1372    }
1373
1374    pub async fn wait_background_done(&self) {
1375        let listener = self.event_background_done.listen();
1376        if self
1377            .currently_scheduled_background_jobs
1378            .load(Ordering::Acquire)
1379            != 0
1380        {
1381            listener.await;
1382        }
1383    }
1384
1385    pub async fn stop_and_wait(&self) {
1386        #[cfg(feature = "inline_execution_stats")]
1387        if inline_stats_requested() {
1388            // Requested with `TURBO_ENGINE_INLINE_STATS=1`; printed rather than traced so it shows
1389            // up without a tracing subscriber configured.
1390            eprintln!(
1391                "turbo-tasks inline execution stats: {:#?}",
1392                self.inline_execution_stats()
1393            );
1394        }
1395        turbo_tasks_future_scope(self.pin(), async move {
1396            self.backend.stopping(self);
1397            self.stopped.store(true, Ordering::Release);
1398            {
1399                let listener = self
1400                    .event_foreground_done
1401                    .listen_with_note(|| || "wait for stop".to_string());
1402                if self
1403                    .currently_scheduled_foreground_jobs
1404                    .load(Ordering::Acquire)
1405                    != 0
1406                {
1407                    listener.await;
1408                }
1409            }
1410            {
1411                let listener = self.event_background_done.listen();
1412                if self
1413                    .currently_scheduled_background_jobs
1414                    .load(Ordering::Acquire)
1415                    != 0
1416                {
1417                    listener.await;
1418                }
1419            }
1420            self.backend.stop(self);
1421        })
1422        .await;
1423    }
1424
1425    #[track_caller]
1426    pub(crate) fn schedule_background_job<T>(&self, func: T)
1427    where
1428        T: AsyncFnOnce(Arc<TurboTasks<B>>) -> Arc<TurboTasks<B>> + Send + 'static,
1429        T::CallOnceFuture: Send,
1430    {
1431        let mut this = self.pin();
1432        self.begin_background_job();
1433        tokio::spawn(
1434            TURBO_TASKS
1435                .scope(this.clone(), async move {
1436                    if !this.stopped.load(Ordering::Acquire) {
1437                        this = func(this).await;
1438                    }
1439                    this.finish_background_job();
1440                })
1441                .in_current_span(),
1442        );
1443    }
1444
1445    fn finish_current_task_state(&self) -> FinishedTaskState {
1446        CURRENT_TASK_STATE.with(|cell| {
1447            let current_task_state = &*cell.write().unwrap();
1448            FinishedTaskState {
1449                #[cfg(feature = "verify_determinism")]
1450                stateful: current_task_state.stateful,
1451                has_invalidator: current_task_state.has_invalidator,
1452            }
1453        })
1454    }
1455
1456    pub fn backend(&self) -> &B {
1457        &self.backend
1458    }
1459
1460    pub fn get_current_task_priority(&self) -> TaskPriority {
1461        CURRENT_TASK_STATE
1462            .try_with(|task_state| task_state.read().unwrap().priority)
1463            .unwrap_or(TaskPriority::initial())
1464    }
1465
1466    pub fn is_idle(&self) -> bool {
1467        self.currently_scheduled_foreground_jobs
1468            .load(Ordering::Acquire)
1469            == 0
1470    }
1471
1472    #[track_caller]
1473    pub fn schedule_backend_background_job(&self, job: B::BackendJob) {
1474        self.schedule_background_job(async move |this| {
1475            this.backend.run_backend_job(job, &*this).await;
1476            this
1477        })
1478    }
1479}
1480
1481struct TurboTasksExecutor;
1482
1483/// Run a future and abort the process if a panic is reported
1484///
1485/// Turbtasks catches panics from user code and propagates throught the task tree, but if it happens
1486/// as part of state management we have to abort
1487async fn abort_on_panic<F: Future>(f: F) -> F::Output {
1488    match AssertUnwindSafe(f).catch_unwind().await {
1489        Ok(r) => r,
1490        Err(_) => {
1491            eprintln!(
1492                "\nturbo-tasks: an internal panic occurred outside the per-task panic \
1493                 boundary. This is a bug in turbo-tasks/Turbopack — please report it at \
1494                 https://github.com/vercel/next.js/discussions and include the panic message \
1495                 and stack trace above.\n\nAborting."
1496            );
1497            abort();
1498        }
1499    }
1500}
1501
1502impl<B: Backend> Executor<TurboTasks<B>, ScheduledTask, TaskPriority> for TurboTasksExecutor {
1503    type Future = impl Future<Output = ()> + Send + 'static;
1504
1505    fn execute(
1506        &self,
1507        this: &Arc<TurboTasks<B>>,
1508        scheduled_task: ScheduledTask,
1509        priority: TaskPriority,
1510    ) -> Self::Future {
1511        match scheduled_task {
1512            ScheduledTask::Task { task_id, span } => {
1513                let this2 = this.clone();
1514                let this = this.clone();
1515                let future = async move {
1516                    abort_on_panic(async {
1517                        // it's okay for execution ids to overflow and wrap, they're just used
1518                        // for an assert
1519                        let execution_id = this.execution_id_factory.wrapping_get();
1520                        let current_task_state =
1521                            CurrentTaskStateHandle::new(CurrentTaskState::new(
1522                                task_id,
1523                                execution_id,
1524                                priority,
1525                                false, // in_top_level_task
1526                            ));
1527                        let single_execution_future = async {
1528                            if this.stopped.load(Ordering::Acquire) {
1529                                this.backend.task_execution_canceled(task_id, &*this);
1530                                return None;
1531                            }
1532
1533                            let TaskExecutionSpec { future, span } = this
1534                                .backend
1535                                .try_start_task_execution(task_id, priority, &*this)?;
1536
1537                            // When a reader claimed this task and is polling it inline, let it
1538                            // record the outcome on this span rather than its own.
1539                            InlineExecutionSpanSlot::set(&span);
1540
1541                            async {
1542                                let result = CaptureFuture::new(future).await;
1543
1544                                // wait for all spawned local tasks using `local` to finish
1545                                wait_for_local_tasks().await;
1546
1547                                let result = match result {
1548                                    Ok(Ok(raw_vc)) => {
1549                                        // This is safe because we waited for all local tasks to
1550                                        // complete above
1551                                        raw_vc
1552                                            .to_non_local_unchecked_sync(&*this)
1553                                            .map_err(|err| err.into())
1554                                    }
1555                                    Ok(Err(err)) => Err(err.into()),
1556                                    Err(err) => Err(TurboTasksExecutionError::Panic(Arc::new(err))),
1557                                };
1558
1559                                let finished_state = this.finish_current_task_state();
1560                                let cell_counters = CURRENT_TASK_STATE
1561                                    .with(|ts| ts.write().unwrap().cell_counters.take().unwrap());
1562                                this.backend.task_execution_completed(
1563                                    task_id,
1564                                    result,
1565                                    &cell_counters,
1566                                    #[cfg(feature = "verify_determinism")]
1567                                    finished_state.stateful,
1568                                    finished_state.has_invalidator,
1569                                    &*this,
1570                                )
1571                            }
1572                            .instrument(span)
1573                            .await
1574                        };
1575                        if let Some(stale_priority) = CURRENT_TASK_STATE
1576                            .scope(current_task_state, single_execution_future)
1577                            .await
1578                        {
1579                            // Task was stale; re-schedule at the correct invalidation priority so
1580                            // other tasks can run in the right priority order.
1581                            this.schedule(task_id, stale_priority);
1582                        }
1583                        this.finish_foreground_job();
1584                    })
1585                    .await
1586                };
1587
1588                Either::Left(TURBO_TASKS.scope(this2, future).instrument(span))
1589            }
1590            ScheduledTask::LocalTask {
1591                ty,
1592                persistence,
1593                execution_id: _,
1594                local_task_id,
1595                global_task_state,
1596                span,
1597            } => {
1598                let this2 = this.clone();
1599                let this = this.clone();
1600                let task_type = ty.task_type;
1601                let future = async move {
1602                    let span = match &ty.task_type {
1603                        LocalTaskType::ResolveNative { native_fn } => {
1604                            native_fn.resolve_span(priority)
1605                        }
1606                        LocalTaskType::ResolveTrait { trait_method } => {
1607                            trait_method.resolve_span(priority)
1608                        }
1609                    };
1610                    // See the cached-task arm: lets a reader that claimed this local task record
1611                    // the outcome of its inline poll on this span.
1612                    InlineExecutionSpanSlot::set(&span);
1613                    abort_on_panic(
1614                        async move {
1615                            let result = match ty.task_type {
1616                                LocalTaskType::ResolveNative { native_fn } => {
1617                                    LocalTaskType::run_resolve_native(
1618                                        native_fn,
1619                                        ty.this,
1620                                        &*ty.arg,
1621                                        persistence,
1622                                        this,
1623                                    )
1624                                    .await
1625                                }
1626                                LocalTaskType::ResolveTrait { trait_method } => {
1627                                    LocalTaskType::run_resolve_trait(
1628                                        trait_method,
1629                                        ty.this.unwrap(),
1630                                        &*ty.arg,
1631                                        persistence,
1632                                        this,
1633                                    )
1634                                    .await
1635                                }
1636                            };
1637
1638                            let output = match result {
1639                                Ok(raw_vc) => OutputContent::Link(raw_vc),
1640                                Err(err) => OutputContent::Error(
1641                                    TurboTasksExecutionError::from(err)
1642                                        .with_local_task_context(task_type.to_string()),
1643                                ),
1644                            };
1645
1646                            CURRENT_TASK_STATE.with(move |gts| {
1647                                gts.write()
1648                                    .unwrap()
1649                                    .local_tasks
1650                                    .complete(local_task_id, output);
1651                            });
1652                        }
1653                        .instrument(span),
1654                    )
1655                    .await
1656                };
1657                let future = CURRENT_TASK_STATE.scope(global_task_state, future);
1658
1659                Either::Right(TURBO_TASKS.scope(this2, future).instrument(span))
1660            }
1661        }
1662    }
1663}
1664
1665struct FinishedTaskState {
1666    /// True if the task has state in cells (interior mutability).
1667    /// Only tracked when verify_determinism feature is enabled.
1668    #[cfg(feature = "verify_determinism")]
1669    stateful: bool,
1670
1671    /// True if the task uses an external invalidator
1672    has_invalidator: bool,
1673}
1674
1675impl<B: Backend + 'static> TurboTasksCallApi for TurboTasks<B> {
1676    fn dynamic_call(
1677        &self,
1678        native_fn: &'static NativeFunction,
1679        this: Option<RawVc>,
1680        arg: &mut dyn DynTaskInputsStorage,
1681        inputs_resolved: InputResolution,
1682        persistence: TaskPersistence,
1683    ) -> RawVc {
1684        self.dynamic_call(native_fn, this, arg, inputs_resolved, persistence)
1685    }
1686    fn native_call(
1687        &self,
1688        native_fn: &'static NativeFunction,
1689        this: Option<RawVc>,
1690        arg: &mut dyn DynTaskInputsStorage,
1691        persistence: TaskPersistence,
1692    ) -> RawVc {
1693        self.native_call(native_fn, this, arg, persistence)
1694    }
1695    fn trait_call(
1696        &self,
1697        trait_method: &'static TraitMethod,
1698        this: RawVc,
1699        arg: &mut dyn DynTaskInputsStorage,
1700        inputs_resolved: InputResolution,
1701        persistence: TaskPersistence,
1702    ) -> RawVc {
1703        self.trait_call(trait_method, this, arg, inputs_resolved, persistence)
1704    }
1705
1706    #[track_caller]
1707    fn run(
1708        &self,
1709        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
1710    ) -> Pin<Box<dyn Future<Output = Result<(), TurboTasksExecutionError>> + Send>> {
1711        let this = self.pin();
1712        Box::pin(async move { this.run(future).await })
1713    }
1714
1715    #[track_caller]
1716    fn run_once(
1717        &self,
1718        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
1719    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
1720        let this = self.pin();
1721        Box::pin(async move { this.run_once(future).await })
1722    }
1723
1724    #[track_caller]
1725    fn run_once_with_reason(
1726        &self,
1727        reason: StaticOrArc<dyn InvalidationReason>,
1728        future: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>,
1729    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
1730        {
1731            let (_, reason_set) = &mut *self.aggregated_update.lock().unwrap();
1732            reason_set.insert(reason);
1733        }
1734        let this = self.pin();
1735        Box::pin(async move { this.run_once(future).await })
1736    }
1737
1738    #[track_caller]
1739    fn start_once_process(&self, future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
1740        self.start_once_process(future)
1741    }
1742
1743    fn send_compilation_event(&self, event: Arc<dyn CompilationEvent>) {
1744        if let Err(e) = self.compilation_events.send(event) {
1745            tracing::warn!("Failed to send compilation event: {e}");
1746        }
1747    }
1748
1749    fn get_task_name(&self, task: TaskId) -> String {
1750        self.backend.get_task_name(task, self)
1751    }
1752}
1753
1754impl<B: Backend + 'static> TurboTasksApi for TurboTasks<B> {
1755    #[instrument(level = "info", skip_all, name = "invalidate")]
1756    fn invalidate(&self, task: TaskId) {
1757        self.backend.invalidate_task(task, self);
1758    }
1759
1760    #[instrument(level = "info", skip_all, name = "invalidate", fields(name = display(&reason)))]
1761    fn invalidate_with_reason(&self, task: TaskId, reason: StaticOrArc<dyn InvalidationReason>) {
1762        {
1763            let (_, reason_set) = &mut *self.aggregated_update.lock().unwrap();
1764            reason_set.insert(reason);
1765        }
1766        self.backend.invalidate_task(task, self);
1767    }
1768
1769    fn invalidate_serialization(&self, task: TaskId) {
1770        self.backend.invalidate_serialization(task, self);
1771    }
1772
1773    #[track_caller]
1774    fn try_read_task_output(
1775        &self,
1776        task: TaskId,
1777        options: ReadOutputOptions,
1778    ) -> Result<ReadOutcome<RawVc>> {
1779        if options.consistency == ReadConsistency::Eventual {
1780            debug_assert_not_in_top_level_task("read_task_output");
1781        }
1782        self.backend.try_read_task_output(
1783            task,
1784            current_task_if_available("reading Vcs"),
1785            options,
1786            self,
1787        )
1788    }
1789
1790    #[track_caller]
1791    fn try_read_task_cell(
1792        &self,
1793        task: TaskId,
1794        index: CellId,
1795        options: ReadCellOptions,
1796    ) -> Result<ReadOutcome<TypedCellContent>> {
1797        let reader = current_task_if_available("reading Vcs");
1798        self.backend
1799            .try_read_task_cell(task, index, reader, options, self)
1800    }
1801
1802    fn try_read_own_task_cell(
1803        &self,
1804        current_task: TaskId,
1805        index: CellId,
1806    ) -> Result<TypedCellContent> {
1807        self.backend
1808            .try_read_own_task_cell(current_task, index, self)
1809    }
1810
1811    #[track_caller]
1812    fn try_read_local_output(
1813        &self,
1814        execution_id: ExecutionId,
1815        local_task_id: LocalTaskId,
1816    ) -> Result<Result<RawVc, EventListener>> {
1817        debug_assert_not_in_top_level_task("read_local_output");
1818        CURRENT_TASK_STATE.with(|gts| {
1819            let gts_read = gts.read().unwrap();
1820
1821            // Local Vcs are local to their parent task's current execution, and do not exist
1822            // outside of it. This is weakly enforced at compile time using the `NonLocalValue`
1823            // marker trait. This assertion exists to handle any potential escapes that the
1824            // compile-time checks cannot capture.
1825            gts_read.assert_execution_id(execution_id);
1826
1827            match gts_read.local_tasks.get(local_task_id) {
1828                LocalTask::Scheduled { done_event } => Ok(Err(done_event.listen())),
1829                LocalTask::Done { output } => Ok(Ok(output.as_read_result()?)),
1830            }
1831        })
1832    }
1833
1834    fn read_task_collectibles(&self, task: TaskId, trait_id: TraitTypeId) -> TaskCollectiblesMap {
1835        // TODO: Add assert_not_in_top_level_task("read_task_collectibles") check here.
1836        // Collectible reads are eventually consistent.
1837        self.backend.read_task_collectibles(
1838            task,
1839            trait_id,
1840            current_task_if_available("reading collectibles"),
1841            self,
1842        )
1843    }
1844
1845    fn try_execute_scheduled_task_inline(&self, key: ScheduleKey) -> bool {
1846        self.try_execute_scheduled_task_inline(key)
1847    }
1848
1849    #[cfg(feature = "inline_execution_stats")]
1850    fn note_waited_for_in_progress_task(&self) {
1851        self.note_waited_for_in_progress_task()
1852    }
1853
1854    fn emit_collectible(&self, trait_type: TraitTypeId, collectible: RawVc) {
1855        self.backend.emit_collectible(
1856            trait_type,
1857            collectible,
1858            current_task("emitting collectible"),
1859            self,
1860        );
1861    }
1862
1863    fn unemit_collectible(&self, trait_type: TraitTypeId, collectible: RawVc, count: u32) {
1864        self.backend.unemit_collectible(
1865            trait_type,
1866            collectible,
1867            count,
1868            current_task("emitting collectible"),
1869            self,
1870        );
1871    }
1872
1873    fn unemit_collectibles(&self, trait_type: TraitTypeId, collectibles: &TaskCollectiblesMap) {
1874        for (&collectible, &count) in collectibles {
1875            if count > 0 {
1876                self.backend.unemit_collectible(
1877                    trait_type,
1878                    collectible,
1879                    count as u32,
1880                    current_task("emitting collectible"),
1881                    self,
1882                );
1883            }
1884        }
1885    }
1886
1887    fn read_own_task_cell(&self, task: TaskId, index: CellId) -> Result<TypedCellContent> {
1888        self.try_read_own_task_cell(task, index)
1889    }
1890
1891    fn update_own_task_cell(
1892        &self,
1893        task: TaskId,
1894        index: CellId,
1895        content: CellContent,
1896        updated_key_hashes: Option<SmallVec<[u64; 2]>>,
1897        content_hash: Option<CellHash>,
1898        verification_mode: VerificationMode,
1899    ) {
1900        self.backend.update_task_cell(
1901            task,
1902            index,
1903            content,
1904            updated_key_hashes,
1905            content_hash,
1906            verification_mode,
1907            self,
1908        );
1909    }
1910
1911    fn connect_task(&self, task: TaskId) {
1912        self.backend
1913            .connect_task(task, current_task_if_available("connecting task"), self);
1914    }
1915
1916    fn mark_own_task_as_finished(&self, task: TaskId) {
1917        self.backend.mark_own_task_as_finished(task, self);
1918    }
1919
1920    fn pin_task_for_gc(&self, task: TaskId) {
1921        self.backend.pin_task_for_gc(task, self);
1922    }
1923
1924    fn unpin_task_for_gc(&self, task: TaskId) {
1925        self.backend.unpin_task_for_gc(task, self);
1926    }
1927
1928    /// Creates a future that inherits the current task id and task state. The current global task
1929    /// will wait for this future to be dropped before exiting.
1930    fn spawn_detached_for_testing(&self, fut: Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
1931        // this is similar to what happens for a local task, except that we keep the local task's
1932        // state as well.
1933        let global_task_state = CURRENT_TASK_STATE.with(|ts| ts.clone());
1934        global_task_state
1935            .write()
1936            .unwrap()
1937            .local_tasks
1938            .register_detached();
1939        let wrapped = async move {
1940            // use a drop guard for panic safety
1941            struct DropGuard;
1942            impl Drop for DropGuard {
1943                fn drop(&mut self) {
1944                    CURRENT_TASK_STATE
1945                        .with(|ts| ts.write().unwrap().local_tasks.decrement_in_flight());
1946                }
1947            }
1948            let _guard = DropGuard;
1949            fut.await;
1950        };
1951        tokio::spawn(TURBO_TASKS.scope(
1952            turbo_tasks(),
1953            CURRENT_TASK_STATE.scope(global_task_state, wrapped),
1954        ));
1955    }
1956
1957    fn task_statistics(&self) -> &TaskStatisticsApi {
1958        self.backend.task_statistics()
1959    }
1960
1961    fn stop_and_wait(&self) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
1962        let this = self.pin();
1963        Box::pin(async move {
1964            this.stop_and_wait().await;
1965        })
1966    }
1967
1968    fn subscribe_to_compilation_events(
1969        &self,
1970        event_types: Option<Vec<String>>,
1971    ) -> Receiver<Arc<dyn CompilationEvent>> {
1972        self.compilation_events.subscribe(event_types)
1973    }
1974
1975    fn is_tracking_dependencies(&self) -> bool {
1976        self.backend.is_tracking_dependencies()
1977    }
1978}
1979
1980async fn wait_for_local_tasks() {
1981    let listener =
1982        CURRENT_TASK_STATE.with(|ts| ts.read().unwrap().local_tasks.listen_for_in_flight());
1983    let Some(listener) = listener else {
1984        return;
1985    };
1986    listener.await;
1987}
1988
1989pub(crate) fn current_task_if_available(from: &str) -> Option<TaskId> {
1990    match CURRENT_TASK_STATE.try_with(|ts| ts.current_task_id()) {
1991        Ok(id) => id,
1992        Err(_) => panic!(
1993            "{from} can only be used in the context of a turbo_tasks task execution or \
1994             turbo_tasks run"
1995        ),
1996    }
1997}
1998
1999pub(crate) fn current_task(from: &str) -> TaskId {
2000    match CURRENT_TASK_STATE.try_with(|ts| ts.current_task_id()) {
2001        Ok(Some(id)) => id,
2002        Ok(None) | Err(_) => {
2003            panic!("{from} can only be used in the context of a turbo_tasks task execution")
2004        }
2005    }
2006}
2007
2008/// Panics if we're not in a top-level task (e.g. [`run_once`]). Some function calls should only
2009/// happen in a top-level task (e.g. [`Effects::apply`][crate::Effects::apply]).
2010#[track_caller]
2011pub(crate) fn debug_assert_in_top_level_task(message: &str) {
2012    if !cfg!(debug_assertions) {
2013        return;
2014    }
2015
2016    let in_top_level = CURRENT_TASK_STATE
2017        .try_with(|ts| ts.read().unwrap().in_top_level_task)
2018        .unwrap_or(true);
2019    if !in_top_level {
2020        panic!("{message}");
2021    }
2022}
2023
2024#[track_caller]
2025pub(crate) fn debug_assert_not_in_top_level_task(operation: &str) {
2026    if !cfg!(debug_assertions) {
2027        return;
2028    }
2029
2030    // HACK: We set this inside of `ReadRawVcFuture` to suppress warnings about an internal
2031    // consistency bug
2032    let suppressed = SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK
2033        .try_with(|&suppressed| suppressed)
2034        .unwrap_or(false);
2035    if suppressed {
2036        return;
2037    }
2038
2039    let in_top_level = CURRENT_TASK_STATE
2040        .try_with(|ts| ts.read().unwrap().in_top_level_task)
2041        .unwrap_or(false);
2042    if in_top_level {
2043        panic!(
2044            "Eventually consistent read ({operation}) cannot be performed from a top-level task. \
2045             Top-level tasks (e.g. code inside `.run_once(...)`) must use strongly consistent \
2046             reads to avoid leaking inconsistent return values."
2047        );
2048    }
2049}
2050
2051pub async fn run<T: Send + 'static>(
2052    tt: Arc<dyn TurboTasksApi>,
2053    future: impl Future<Output = Result<T>> + Send + 'static,
2054) -> Result<T> {
2055    let (tx, rx) = tokio::sync::oneshot::channel();
2056
2057    tt.run(Box::pin(async move {
2058        let result = future.await?;
2059        tx.send(result)
2060            .map_err(|_| anyhow!("unable to send result"))?;
2061        Ok(())
2062    }))
2063    .await?;
2064
2065    Ok(rx.await?)
2066}
2067
2068pub async fn run_once<T: Send + 'static>(
2069    tt: Arc<dyn TurboTasksApi>,
2070    future: impl Future<Output = Result<T>> + Send + 'static,
2071) -> Result<T> {
2072    let (tx, rx) = tokio::sync::oneshot::channel();
2073
2074    tt.run_once(Box::pin(async move {
2075        let result = future.await?;
2076        tx.send(result)
2077            .map_err(|_| anyhow!("unable to send result"))?;
2078        Ok(())
2079    }))
2080    .await?;
2081
2082    Ok(rx.await?)
2083}
2084
2085pub async fn run_once_with_reason<T: Send + 'static>(
2086    tt: Arc<dyn TurboTasksApi>,
2087    reason: impl InvalidationReason,
2088    future: impl Future<Output = Result<T>> + Send + 'static,
2089) -> Result<T> {
2090    let (tx, rx) = tokio::sync::oneshot::channel();
2091
2092    tt.run_once_with_reason(
2093        (Arc::new(reason) as Arc<dyn InvalidationReason>).into(),
2094        Box::pin(async move {
2095            let result = future.await?;
2096            tx.send(result)
2097                .map_err(|_| anyhow!("unable to send result"))?;
2098            Ok(())
2099        }),
2100    )
2101    .await?;
2102
2103    Ok(rx.await?)
2104}
2105
2106/// Calls [`TurboTasks::dynamic_call`] for the current turbo tasks instance.
2107pub fn dynamic_call(
2108    func: &'static NativeFunction,
2109    this: Option<RawVc>,
2110    arg: &mut dyn DynTaskInputsStorage,
2111    inputs_resolved: InputResolution,
2112    persistence: TaskPersistence,
2113) -> RawVc {
2114    with_turbo_tasks(|tt| tt.dynamic_call(func, this, arg, inputs_resolved, persistence))
2115}
2116
2117/// Calls [`TurboTasks::trait_call`] for the current turbo tasks instance.
2118pub fn trait_call(
2119    trait_method: &'static TraitMethod,
2120    this: RawVc,
2121    arg: &mut dyn DynTaskInputsStorage,
2122    inputs_resolved: InputResolution,
2123    persistence: TaskPersistence,
2124) -> RawVc {
2125    with_turbo_tasks(|tt| tt.trait_call(trait_method, this, arg, inputs_resolved, persistence))
2126}
2127
2128pub fn turbo_tasks() -> Arc<dyn TurboTasksApi> {
2129    TURBO_TASKS.with(|arc| arc.clone())
2130}
2131
2132pub fn turbo_tasks_weak() -> Weak<dyn TurboTasksApi> {
2133    TURBO_TASKS.with(Arc::downgrade)
2134}
2135
2136pub fn try_turbo_tasks() -> Option<Arc<dyn TurboTasksApi>> {
2137    TURBO_TASKS.try_with(|arc| arc.clone()).ok()
2138}
2139
2140pub fn with_turbo_tasks<T>(func: impl FnOnce(&Arc<dyn TurboTasksApi>) -> T) -> T {
2141    TURBO_TASKS.with(|arc| func(arc))
2142}
2143
2144pub fn turbo_tasks_scope<T>(tt: Arc<dyn TurboTasksApi>, f: impl FnOnce() -> T) -> T {
2145    TURBO_TASKS.sync_scope(tt, f)
2146}
2147
2148pub fn turbo_tasks_future_scope<T>(
2149    tt: Arc<dyn TurboTasksApi>,
2150    f: impl Future<Output = T>,
2151) -> impl Future<Output = T> {
2152    TURBO_TASKS.scope(tt, f)
2153}
2154
2155/// Spawns the given future within the context of the current task.
2156///
2157/// Beware: this method is not safe to use in production code. It is only
2158/// intended for use in tests and for debugging purposes.
2159pub fn spawn_detached_for_testing(f: impl Future<Output = ()> + Send + 'static) {
2160    turbo_tasks().spawn_detached_for_testing(Box::pin(f));
2161}
2162
2163/// Marks the current task as finished. This excludes it from waiting for
2164/// strongly consistency.
2165pub fn mark_finished() {
2166    with_turbo_tasks(|tt| {
2167        tt.mark_own_task_as_finished(current_task("turbo_tasks::mark_finished()"))
2168    });
2169}
2170
2171/// Returns a [`SerializationInvalidator`] that can be used to invalidate the
2172/// serialization of the current task cells.
2173///
2174/// Also marks the current task as stateful when the `verify_determinism` feature is enabled,
2175/// since State allocation implies interior mutability.
2176pub fn get_serialization_invalidator() -> SerializationInvalidator {
2177    CURRENT_TASK_STATE.with(|cell| {
2178        let CurrentTaskState {
2179            task_id,
2180            #[cfg(feature = "verify_determinism")]
2181            stateful,
2182            ..
2183        } = &mut *cell.write().unwrap();
2184        #[cfg(feature = "verify_determinism")]
2185        {
2186            *stateful = true;
2187        }
2188        let Some(task_id) = *task_id else {
2189            panic!(
2190                "get_serialization_invalidator() can only be used in the context of a turbo_tasks \
2191                 task execution"
2192            );
2193        };
2194        SerializationInvalidator::new(task_id)
2195    })
2196}
2197
2198pub fn mark_invalidator() {
2199    CURRENT_TASK_STATE.with(|cell| {
2200        let CurrentTaskState {
2201            has_invalidator, ..
2202        } = &mut *cell.write().unwrap();
2203        *has_invalidator = true;
2204    })
2205}
2206
2207/// Marks the current task as stateful. This is used to indicate that the task
2208/// has interior mutability (e.g., via [`State`][crate::State]), which means
2209/// the task may produce different outputs even with the same inputs.
2210///
2211/// Only has an effect when the `verify_determinism` feature is enabled.
2212pub fn mark_stateful() {
2213    #[cfg(feature = "verify_determinism")]
2214    {
2215        CURRENT_TASK_STATE.with(|cell| {
2216            let CurrentTaskState { stateful, .. } = &mut *cell.write().unwrap();
2217            *stateful = true;
2218        })
2219    }
2220    // No-op when verify_determinism is not enabled
2221}
2222
2223/// Marks the current task context as being in a top-level task. When in a top-level task,
2224/// eventually consistent reads will panic. It is almost always a mistake to perform an eventually
2225/// consistent read at the top-level of the application.
2226pub fn mark_top_level_task() {
2227    if cfg!(debug_assertions) {
2228        CURRENT_TASK_STATE.with(|cell| {
2229            cell.write().unwrap().in_top_level_task = true;
2230        })
2231    }
2232}
2233
2234/// Unmarks the current task context as being in a top-level task. The opposite of
2235/// [`mark_top_level_task`].
2236///
2237/// This utility can be okay in unit tests, where we're observing the internal behavior of
2238/// turbo-tasks, but otherwise, it is probably a mistake to call this function.
2239///
2240/// Calling this will allow eventually-consistent reads at the top-level, potentially exposing
2241/// incomplete computations and internal errors caused by eventual consistency that would've been
2242/// caught when the function was re-run. A strongly-consistent read re-runs parts of a task until
2243/// all of the dependencies have settled.
2244pub fn unmark_top_level_task_may_leak_eventually_consistent_state() {
2245    if cfg!(debug_assertions) {
2246        CURRENT_TASK_STATE.with(|cell| {
2247            cell.write().unwrap().in_top_level_task = false;
2248        })
2249    }
2250}
2251
2252/// Pins the current task against garbage collection for the rest of the session, keeping it (and,
2253/// via the reachability it anchors, the values it produced) alive even if it becomes disconnected
2254/// from the live task graph. Use this when a value escapes the tracked graph — e.g. a `Vc` sent out
2255/// of a `spawn_detached` future across a channel, or handed across the NAPI boundary — so no
2256/// persistent parent lists it as a child and it would otherwise be collected.
2257///
2258/// No-op outside a task context, and on backends without garbage collection.
2259pub fn prevent_gc() {
2260    if let Some(task) = current_task_if_available("prevent_gc") {
2261        with_turbo_tasks(|tt| tt.pin_task_for_gc(task));
2262    }
2263}
2264
2265pub fn emit<T: VcValueTrait + ?Sized>(collectible: ResolvedVc<T>) {
2266    with_turbo_tasks(|tt| {
2267        let raw_vc = collectible.node.node;
2268        tt.emit_collectible(T::get_trait_type_id(), raw_vc)
2269    })
2270}
2271
2272pub(crate) async fn read_task_output(
2273    this: &dyn TurboTasksApi,
2274    id: TaskId,
2275    options: ReadOutputOptions,
2276) -> Result<RawVc> {
2277    loop {
2278        match this.try_read_task_output(id, options)? {
2279            ReadOutcome::Value(result) => return Ok(result),
2280            ReadOutcome::Scheduled(listener) => {
2281                // Nobody has started it yet, so take it over instead of waiting for a worker.
2282                if execute_read_target_inline(this, ScheduleKey::Task(id)) {
2283                    continue;
2284                }
2285                listener.await
2286            }
2287            ReadOutcome::InProgress(listener) => {
2288                // A worker is on it — there is nothing to take over, so don't touch the queue.
2289                #[cfg(feature = "inline_execution_stats")]
2290                this.note_waited_for_in_progress_task();
2291                listener.await
2292            }
2293        }
2294    }
2295}
2296
2297/// A reference to a task's cell with methods that allow updating the contents
2298/// of the cell.
2299///
2300/// Mutations should not outside of the task that that owns this cell. Doing so
2301/// is a logic error, and may lead to incorrect caching behavior.
2302#[derive(Clone, Copy)]
2303pub struct CurrentCellRef {
2304    current_task: TaskId,
2305    index: CellId,
2306}
2307
2308type VcReadTarget<T> = <<T as VcValueType>::Read as VcRead<T>>::Target;
2309
2310/// What a conditional cell update returns: the new content, the key hashes that changed, and an
2311/// optional hash of the value.
2312type CellUpdate = (
2313    SharedReference,
2314    Option<SmallVec<[u64; 2]>>,
2315    Option<CellHash>,
2316);
2317
2318/// The callback [`CurrentCellRef::conditional_update_with_shared_reference`] takes. It is a `dyn`
2319/// trait object so that the function's body is compiled once rather than once per cell type.
2320type CellUpdateFn<'l> = dyn FnMut(Option<&SharedReference>) -> Option<CellUpdate> + 'l;
2321
2322impl CurrentCellRef {
2323    /// Updates the cell if the given `functor` returns a value.
2324    fn conditional_update<T>(
2325        &self,
2326        functor: impl FnOnce(Option<&T>) -> Option<(T, Option<SmallVec<[u64; 2]>>, Option<CellHash>)>,
2327    ) where
2328        T: VcValueType,
2329    {
2330        // `FnMut` cannot move out of its captures, and the callee calls this at most once, so
2331        // the `FnOnce` is handed over through an `Option`.
2332        let mut functor = Some(functor);
2333        self.conditional_update_with_shared_reference(&mut |old_shared_reference| {
2334            let functor = functor.take().expect("functor is called at most once");
2335            let old_ref = old_shared_reference.and_then(|sr| sr.0.downcast_ref::<T>());
2336            let (new_value, updated_key_hashes, content_hash) = functor(old_ref)?;
2337            Some((
2338                SharedReference::new(triomphe::Arc::new(new_value)),
2339                updated_key_hashes,
2340                content_hash,
2341            ))
2342        })
2343    }
2344
2345    /// Updates the cell if the given `functor` returns a `SharedReference`.
2346    ///
2347    /// `functor` is a `dyn` trait object rather than a generic parameter on purpose. This body is
2348    /// identical for every cell type, so making it generic monomorphized it once per
2349    /// `VcValueType` in the dependency graph — over a thousand copies of the same code. The
2350    /// indirect call it costs instead is negligible next to the cell read and update it wraps.
2351    fn conditional_update_with_shared_reference(&self, functor: &mut CellUpdateFn<'_>) {
2352        let tt = turbo_tasks();
2353        let cell_content = tt.read_own_task_cell(self.current_task, self.index).ok();
2354        let update = functor(cell_content.as_ref().and_then(|cc| cc.1.0.as_ref()));
2355        if let Some((update, updated_key_hashes, content_hash)) = update {
2356            tt.update_own_task_cell(
2357                self.current_task,
2358                self.index,
2359                CellContent(Some(update)),
2360                updated_key_hashes,
2361                content_hash,
2362                VerificationMode::EqualityCheck,
2363            )
2364        }
2365    }
2366
2367    /// Replace the current cell's content with `new_value` if the current content is not equal by
2368    /// value with the existing content.
2369    ///
2370    /// The comparison happens using the value itself, not the [`VcRead::Target`] of that value.
2371    ///
2372    /// Take this example of a custom equality implementation on a transparent wrapper type:
2373    ///
2374    /// ```
2375    /// #[turbo_tasks::value(transparent, eq = "manual")]
2376    /// #[derive(Clone)]
2377    /// struct Wrapper(Vec<u32>);
2378    ///
2379    /// impl PartialEq for Wrapper {
2380    ///     fn eq(&self, other: &Wrapper) -> bool {
2381    ///         // Example: order doesn't matter for equality
2382    ///         let (mut this, mut other) = (self.0.clone(), other.0.clone());
2383    ///         this.sort_unstable();
2384    ///         other.sort_unstable();
2385    ///         this == other
2386    ///     }
2387    /// }
2388    ///
2389    /// impl Eq for Wrapper {}
2390    /// ```
2391    ///
2392    /// Comparisons of [`Vc<Wrapper>`] used when updating the cell will use `Wrapper`'s custom
2393    /// equality implementation, rather than the one provided by the target ([`Vec<u32>`]) type.
2394    ///
2395    /// However, in most cases, the default derived implementation of [`PartialEq`] is used which
2396    /// just forwards to the inner value's [`PartialEq`].
2397    ///
2398    /// If you already have a `SharedReference`, consider calling
2399    /// [`Self::compare_and_update_with_shared_reference`] which can re-use the [`SharedReference`]
2400    /// object.
2401    pub fn compare_and_update<T>(&self, new_value: T)
2402    where
2403        T: PartialEq + VcValueType,
2404    {
2405        self.conditional_update(|old_value| {
2406            if let Some(old_value) = old_value
2407                && old_value == &new_value
2408            {
2409                return None;
2410            }
2411            Some((new_value, None, None))
2412        });
2413    }
2414
2415    /// Replace the current cell's content with `new_shared_reference` if the current content is not
2416    /// equal by value with the existing content.
2417    ///
2418    /// If you already have a `SharedReference`, this is a faster version of
2419    /// [`CurrentCellRef::compare_and_update`].
2420    ///
2421    /// The value should be stored in [`SharedReference`] using the type `T`.
2422    pub fn compare_and_update_with_shared_reference<T>(&self, new_shared_reference: SharedReference)
2423    where
2424        T: VcValueType + PartialEq,
2425    {
2426        let mut new_shared_reference = Some(new_shared_reference);
2427        self.conditional_update_with_shared_reference(&mut |old_sr| {
2428            let new_shared_reference = new_shared_reference
2429                .take()
2430                .expect("functor is called at most once");
2431            if let Some(old_sr) = old_sr {
2432                let old_value = extract_sr_value::<T>(old_sr);
2433                let new_value = extract_sr_value::<T>(&new_shared_reference);
2434                if old_value == new_value {
2435                    return None;
2436                }
2437            }
2438            Some((new_shared_reference, None, None))
2439        });
2440    }
2441
2442    /// Replace the current cell's content if the new value is different.
2443    ///
2444    /// Like [`Self::compare_and_update`], but also computes and stores a hash of the value.
2445    /// When the cell's transient data is evicted, the stored hash enables the backend to detect
2446    /// whether the value actually changed without re-comparing values—avoiding unnecessary
2447    /// downstream invalidation.
2448    ///
2449    /// Requires `T: DeterministicHash` in addition to `T: PartialEq`.
2450    pub fn hashed_compare_and_update<T>(&self, new_value: T)
2451    where
2452        T: PartialEq + DeterministicHash + VcValueType,
2453    {
2454        self.conditional_update(|old_value| {
2455            if let Some(old_value) = old_value
2456                && old_value == &new_value
2457            {
2458                return None;
2459            }
2460            let content_hash = hash_xxh3_hash128(&new_value).to_le_bytes();
2461
2462            Some((new_value, None, Some(content_hash)))
2463        });
2464    }
2465
2466    /// Replace the current cell's content if the new value (from a pre-existing
2467    /// [`SharedReference`]) is different.
2468    ///
2469    /// Like [`Self::compare_and_update_with_shared_reference`], but also passes a hash
2470    /// for hash-based change detection when transient data has been evicted.
2471    pub fn hashed_compare_and_update_with_shared_reference<T>(
2472        &self,
2473        new_shared_reference: SharedReference,
2474    ) where
2475        T: VcValueType + PartialEq + DeterministicHash,
2476    {
2477        let mut new_shared_reference = Some(new_shared_reference);
2478        self.conditional_update_with_shared_reference(&mut move |old_sr| {
2479            let new_shared_reference = new_shared_reference
2480                .take()
2481                .expect("functor is called at most once");
2482            if let Some(old_sr) = old_sr {
2483                let old_value = extract_sr_value::<T>(old_sr);
2484                let new_value = extract_sr_value::<T>(&new_shared_reference);
2485                if old_value == new_value {
2486                    return None;
2487                }
2488            }
2489            let content_hash =
2490                hash_xxh3_hash128(extract_sr_value::<T>(&new_shared_reference)).to_le_bytes();
2491            Some((new_shared_reference, None, Some(content_hash)))
2492        });
2493    }
2494
2495    /// See [`Self::compare_and_update`], but selectively update individual keys.
2496    pub fn keyed_compare_and_update<T>(&self, new_value: T)
2497    where
2498        T: PartialEq + VcValueType,
2499        VcReadTarget<T>: KeyedEq,
2500        <VcReadTarget<T> as KeyedEq>::Key: std::hash::Hash,
2501    {
2502        self.conditional_update(|old_value| {
2503            let Some(old_value) = old_value else {
2504                return Some((new_value, None, None));
2505            };
2506            let old_value = <T as VcValueType>::Read::value_to_target_ref(old_value);
2507            let new_value_ref = <T as VcValueType>::Read::value_to_target_ref(&new_value);
2508            let updated_keys = old_value.different_keys(new_value_ref);
2509            if updated_keys.is_empty() {
2510                return None;
2511            }
2512            // Duplicates are very unlikely, but ok since the backend is deduplicating them
2513            let updated_key_hashes = updated_keys
2514                .into_iter()
2515                .map(|key| FxBuildHasher.hash_one(key))
2516                .collect();
2517            Some((new_value, Some(updated_key_hashes), None))
2518        });
2519    }
2520
2521    /// See [`Self::compare_and_update_with_shared_reference`], but selectively update individual
2522    /// keys.
2523    pub fn keyed_compare_and_update_with_shared_reference<T>(
2524        &self,
2525        new_shared_reference: SharedReference,
2526    ) where
2527        T: VcValueType + PartialEq,
2528        VcReadTarget<T>: KeyedEq,
2529        <VcReadTarget<T> as KeyedEq>::Key: std::hash::Hash,
2530    {
2531        let mut new_shared_reference = Some(new_shared_reference);
2532        self.conditional_update_with_shared_reference(&mut |old_sr| {
2533            let new_shared_reference = new_shared_reference
2534                .take()
2535                .expect("functor is called at most once");
2536            let Some(old_sr) = old_sr else {
2537                return Some((new_shared_reference, None, None));
2538            };
2539            let old_value = extract_sr_value::<T>(old_sr);
2540            let old_value = <T as VcValueType>::Read::value_to_target_ref(old_value);
2541            let new_value = extract_sr_value::<T>(&new_shared_reference);
2542            let new_value = <T as VcValueType>::Read::value_to_target_ref(new_value);
2543            let updated_keys = old_value.different_keys(new_value);
2544            if updated_keys.is_empty() {
2545                return None;
2546            }
2547            // Duplicates are very unlikely, but ok since the backend is deduplicating them
2548            let updated_key_hashes = updated_keys
2549                .into_iter()
2550                .map(|key| FxBuildHasher.hash_one(key))
2551                .collect();
2552            Some((new_shared_reference, Some(updated_key_hashes), None))
2553        });
2554    }
2555
2556    /// Unconditionally updates the content of the cell.
2557    pub fn update<T>(&self, new_value: T, verification_mode: VerificationMode)
2558    where
2559        T: VcValueType,
2560    {
2561        let tt = turbo_tasks();
2562        tt.update_own_task_cell(
2563            self.current_task,
2564            self.index,
2565            CellContent(Some(SharedReference::new(triomphe::Arc::new(new_value)))),
2566            None,
2567            None,
2568            verification_mode,
2569        )
2570    }
2571
2572    /// A faster version of [`Self::update`] if you already have a
2573    /// [`SharedReference`].
2574    ///
2575    /// If the passed-in [`SharedReference`] is the same as the existing cell's
2576    /// by identity, no update is performed.
2577    ///
2578    /// The value should be stored in [`SharedReference`] using the type `T`.
2579    pub fn update_with_shared_reference(
2580        &self,
2581        shared_ref: SharedReference,
2582        verification_mode: VerificationMode,
2583    ) {
2584        let tt = turbo_tasks();
2585        let update = if matches!(verification_mode, VerificationMode::EqualityCheck) {
2586            let content = tt.read_own_task_cell(self.current_task, self.index).ok();
2587            if let Some(TypedCellContent(_, CellContent(Some(shared_ref_exp)))) = content {
2588                // pointer equality (not value equality)
2589                shared_ref_exp != shared_ref
2590            } else {
2591                true
2592            }
2593        } else {
2594            true
2595        };
2596        if update {
2597            tt.update_own_task_cell(
2598                self.current_task,
2599                self.index,
2600                CellContent(Some(shared_ref)),
2601                None,
2602                None,
2603                verification_mode,
2604            )
2605        }
2606    }
2607}
2608
2609impl From<CurrentCellRef> for RawVc {
2610    fn from(cell: CurrentCellRef) -> Self {
2611        RawVc::task_cell(cell.current_task, cell.index)
2612    }
2613}
2614
2615fn extract_sr_value<T: VcValueType>(sr: &SharedReference) -> &T {
2616    sr.0.downcast_ref::<T>()
2617        .expect("cannot update SharedReference of different type")
2618}
2619
2620pub fn find_cell_by_type<T: VcValueType>() -> CurrentCellRef {
2621    find_cell_by_id(T::get_value_type_id())
2622}
2623
2624pub fn find_cell_by_id(ty: ValueTypeId) -> CurrentCellRef {
2625    CURRENT_TASK_STATE.with(|ts| {
2626        let current_task = current_task("celling turbo_tasks values");
2627        let mut ts = ts.write().unwrap();
2628        let map = ts.cell_counters.as_mut().unwrap();
2629        let current_index = map.entry(ty).or_default();
2630        let index = *current_index;
2631        assert!(
2632            index <= CellId::MAX_CELL_INDEX,
2633            "task allocated more than {} cells of a single type",
2634            CellId::MAX_CELL_INDEX as u64 + 1,
2635        );
2636        *current_index += 1;
2637        CurrentCellRef {
2638            current_task,
2639            index: CellId::new(ty, index),
2640        }
2641    })
2642}
2643
2644pub(crate) async fn read_local_output(
2645    this: &dyn TurboTasksApi,
2646    execution_id: ExecutionId,
2647    local_task_id: LocalTaskId,
2648) -> Result<RawVc> {
2649    loop {
2650        match this.try_read_local_output(execution_id, local_task_id)? {
2651            Ok(raw_vc) => return Ok(raw_vc),
2652            Err(event_listener) => {
2653                // The local task is not done yet. If it is only scheduled, execute it right here
2654                // instead of waiting for a worker to pick it up.
2655                if execute_read_target_inline(
2656                    this,
2657                    ScheduleKey::LocalTask(execution_id, local_task_id),
2658                ) {
2659                    continue;
2660                }
2661                event_listener.await
2662            }
2663        }
2664    }
2665}
2666
2667#[cfg(test)]
2668mod tests {
2669    use super::*;
2670
2671    #[test]
2672    fn test_inline_execution_depth_guard_restores_depth() {
2673        assert_eq!(INLINE_EXECUTION_DEPTH.get(), 0);
2674        {
2675            let _outer = InlineExecutionDepthGuard::enter();
2676            {
2677                let _inner = InlineExecutionDepthGuard::enter();
2678                assert_eq!(INLINE_EXECUTION_DEPTH.get(), 2);
2679            }
2680            assert_eq!(INLINE_EXECUTION_DEPTH.get(), 1);
2681        }
2682        assert_eq!(INLINE_EXECUTION_DEPTH.get(), 0);
2683    }
2684
2685    #[test]
2686    fn test_inline_depth_cap() {
2687        assert!(inline_execution_allowed(), "nothing is nested yet");
2688        let mut guards = (0..MAX_INLINE_EXECUTION_DEPTH)
2689            .map(|_| InlineExecutionDepthGuard::enter())
2690            .collect::<Vec<_>>();
2691        assert_eq!(INLINE_EXECUTION_DEPTH.get(), MAX_INLINE_EXECUTION_DEPTH);
2692        assert!(
2693            !inline_execution_allowed(),
2694            "at the nesting cap reads wait for a worker instead of executing inline"
2695        );
2696
2697        // One level below the cap inline execution is allowed again.
2698        guards.pop();
2699        assert!(inline_execution_allowed());
2700    }
2701
2702    #[tokio::test]
2703    async fn test_poll_once_or_spawn_completed_execution() {
2704        assert!(
2705            poll_once_or_spawn(async {}),
2706            "a future that completes on the first poll is executed inline"
2707        );
2708        assert_eq!(INLINE_EXECUTION_DEPTH.get(), 0);
2709    }
2710
2711    #[tokio::test]
2712    async fn test_poll_once_or_spawn_pending_execution() {
2713        let (tx, rx) = tokio::sync::oneshot::channel();
2714        let done = Arc::new(AtomicBool::new(false));
2715        let done_in_task = done.clone();
2716        assert!(
2717            !poll_once_or_spawn(async move {
2718                // Yields on the first poll, so it cannot be executed inline.
2719                tokio::task::yield_now().await;
2720                done_in_task.store(true, Ordering::SeqCst);
2721                let _ = tx.send(());
2722            }),
2723            "a future that yields is not completed inline"
2724        );
2725        assert_eq!(INLINE_EXECUTION_DEPTH.get(), 0);
2726
2727        // ...but it was spawned, so it still runs to completion.
2728        rx.await.unwrap();
2729        assert!(done.load(Ordering::SeqCst));
2730    }
2731}