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