1mod cell_data;
2mod counter_map;
3mod eviction;
4mod operation;
5mod snapshot_coordinator;
6mod storage;
7pub mod storage_schema;
8
9use std::{
10 borrow::Cow,
11 fmt::{self, Write},
12 future::Future,
13 hash::BuildHasherDefault,
14 mem::take,
15 pin::Pin,
16 sync::{
17 Arc, LazyLock,
18 atomic::{AtomicBool, Ordering},
19 },
20 time::SystemTime,
21};
22
23use anyhow::{Context, Result, bail};
24use auto_hash_map::{AutoMap, AutoSet};
25use indexmap::IndexSet;
26use parking_lot::Mutex;
27use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
28use smallvec::{SmallVec, smallvec};
29use tokio::time::{Duration, Instant};
30use tracing::{Span, field::display, trace_span};
31use turbo_bincode::{TurboBincodeBuffer, new_turbo_bincode_decoder, new_turbo_bincode_encoder};
32use turbo_tasks::{
33 CellId, DynTaskInputsStorage, RawVc, RawVcUnpacked, ReadCellOptions, ReadCellTracking,
34 ReadConsistency, ReadOutputOptions, ReadTracking, SharedReference, TRANSIENT_TASK_BIT,
35 TaskExecutionReason, TaskId, TaskPersistence, TaskPriority, TraitTypeId, TurboTasks,
36 TurboTasksCallApi, TurboTasksPanic, ValueTypeId,
37 backend::{
38 Backend, CachedTaskType, CachedTaskTypeArc, CellContent, CellHash, TaskExecutionSpec,
39 TransientTaskType, TurboTaskContextError, TurboTaskLocalContextError, TurboTasksError,
40 TurboTasksExecutionError, TurboTasksExecutionErrorMessage, TypedCellContent,
41 VerificationMode,
42 },
43 event::{Event, EventDescription, EventListener},
44 macro_helpers::NativeFunction,
45 message_queue::{TimingEvent, TraceEvent},
46 registry::get_value_type,
47 scope::scope_and_block,
48 task_statistics::TaskStatisticsApi,
49 trace::TraceRawVcs,
50 util::{IdFactoryWithReuse, good_chunk_size, into_chunks},
51};
52#[cfg(feature = "task_dirty_cause")]
53use turbo_tasks::{FunctionId, TaskDirtyCause};
54use turbo_tasks_malloc::TurboMalloc;
55
56use self::eviction::EvictionControl;
57pub use self::{
58 eviction::EvictionMode,
59 operation::AnyOperation,
60 storage::{EvictionCounts, SpecificTaskDataCategory, TaskDataCategory},
61};
62use crate::{
63 backend::{
64 operation::{
65 AggregationUpdateJob, AggregationUpdateQueue, ChildExecuteContext,
66 CleanupOldEdgesOperation, ConnectChildOperation, ExecuteContext, ExecuteContextImpl,
67 LeafDistanceUpdateQueue, Operation, OutdatedEdge, TaskGuard, TaskType, TaskTypeRef,
68 connect_children, get_aggregation_number, get_uppers, make_task_dirty_internal,
69 prepare_new_children,
70 },
71 snapshot_coordinator::{OperationGuard, SnapshotCoordinator},
72 storage::Storage,
73 storage_schema::{TaskStorage, TaskStorageAccessors},
74 },
75 backing_storage::{SnapshotItem, compute_task_type_hash},
76 data::{
77 ActivenessState, CellRef, CollectibleRef, CollectiblesRef, Dirtyness, InProgressCellState,
78 InProgressState, InProgressStateInner, OutputValue, TransientTask,
79 },
80 error::TaskError,
81 kv_backing_storage::TurboBackingStorage,
82 utils::{
83 dash_map_raw_entry::{RawEntry, get_shard, raw_entry_in_shard, raw_get_in_shard},
84 shard_amount::compute_shard_amount,
85 stopwatch::Stopwatch,
86 },
87};
88
89const DEPENDENT_TASKS_DIRTY_PARALLELIZATION_THRESHOLD: usize = 10000;
93
94fn compute_stale_priority(task: &impl TaskGuard) -> TaskPriority {
101 TaskPriority::invalidation(
102 task.get_leaf_distance()
103 .copied()
104 .unwrap_or_default()
105 .distance,
106 )
107 .in_parent(task.is_dirty().unwrap_or(TaskPriority::leaf()))
108}
109
110pub enum StorageMode {
111 ReadOnly,
113 ReadWrite,
116 ReadWriteOnShutdown,
119}
120
121pub struct BackendOptions {
122 pub dependency_tracking: bool,
127
128 pub active_tracking: bool,
134
135 pub storage_mode: Option<StorageMode>,
137
138 pub num_workers: Option<usize>,
141
142 pub small_preallocation: bool,
144
145 pub eviction_mode: EvictionMode,
149}
150
151impl Default for BackendOptions {
152 fn default() -> Self {
153 Self {
154 dependency_tracking: true,
155 active_tracking: true,
156 storage_mode: Some(StorageMode::ReadWrite),
157 num_workers: None,
158 small_preallocation: false,
159 eviction_mode: EvictionMode::Off,
160 }
161 }
162}
163
164pub enum TurboTasksBackendJob {
165 Snapshot,
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
170enum SnapshotReason {
171 Test,
172 Stop,
173 InitialSnapshotTimeout,
174 RegularSnapshotInterval,
175 IdleTimeout,
176}
177
178impl SnapshotReason {
179 fn as_str(self) -> &'static str {
180 match self {
181 SnapshotReason::Test => "test",
182 SnapshotReason::Stop => "stop",
183 SnapshotReason::InitialSnapshotTimeout => "initial snapshot timeout",
184 SnapshotReason::RegularSnapshotInterval => "regular snapshot interval",
185 SnapshotReason::IdleTimeout => "idle timeout",
186 }
187 }
188
189 fn drain_entries(self) -> bool {
193 matches!(self, SnapshotReason::Stop)
194 }
195}
196
197pub struct TurboTasksBackend {
198 options: BackendOptions,
199
200 start_time: Instant,
201
202 persisted_task_id_factory: IdFactoryWithReuse<TaskId>,
203 transient_task_id_factory: IdFactoryWithReuse<TaskId>,
204
205 storage: Storage,
206
207 snapshot_coord: SnapshotCoordinator,
210 snapshot_in_progress: Mutex<()>,
215
216 stopping: AtomicBool,
217 stopping_event: Event,
218 idle_start_event: Event,
219 idle_end_event: Event,
220 #[cfg(feature = "verify_aggregation_graph")]
221 is_idle: AtomicBool,
222
223 task_statistics: TaskStatisticsApi,
224
225 backing_storage: TurboBackingStorage,
226
227 #[cfg(feature = "verify_aggregation_graph")]
228 root_tasks: Mutex<FxHashSet<TaskId>>,
229}
230
231impl TurboTasksBackend {
232 pub fn invalidate_storage(&self, reason_code: &str) -> Result<()> {
238 self.backing_storage.invalidate(reason_code)
239 }
240
241 pub fn new(mut options: BackendOptions, backing_storage: TurboBackingStorage) -> Self {
242 let shard_amount = compute_shard_amount(options.num_workers, options.small_preallocation);
243 if !options.dependency_tracking {
244 options.active_tracking = false;
245 }
246 let small_preallocation = options.small_preallocation;
247 let next_task_id = backing_storage
248 .next_free_task_id()
249 .expect("Failed to get task id");
250 Self {
251 options,
252 start_time: Instant::now(),
253 persisted_task_id_factory: IdFactoryWithReuse::new(
254 next_task_id,
255 TaskId::try_from(TRANSIENT_TASK_BIT - 1).unwrap(),
256 ),
257 transient_task_id_factory: IdFactoryWithReuse::new(
258 TaskId::try_from(TRANSIENT_TASK_BIT).unwrap(),
259 TaskId::MAX,
260 ),
261 storage: Storage::new(shard_amount, small_preallocation),
262 snapshot_coord: SnapshotCoordinator::new(),
263 snapshot_in_progress: Mutex::new(()),
264 stopping: AtomicBool::new(false),
265 stopping_event: Event::new(|| || "TurboTasksBackend::stopping_event".to_string()),
266 idle_start_event: Event::new(|| || "TurboTasksBackend::idle_start_event".to_string()),
267 idle_end_event: Event::new(|| || "TurboTasksBackend::idle_end_event".to_string()),
268 #[cfg(feature = "verify_aggregation_graph")]
269 is_idle: AtomicBool::new(false),
270 task_statistics: TaskStatisticsApi::default(),
271 backing_storage,
272 #[cfg(feature = "verify_aggregation_graph")]
273 root_tasks: Default::default(),
274 }
275 }
276
277 fn execute_context<'a>(
278 &'a self,
279 turbo_tasks: &'a TurboTasks<TurboTasksBackend>,
280 ) -> impl ExecuteContext<'a> {
281 ExecuteContextImpl::new(self, turbo_tasks)
282 }
283
284 fn operation_suspend_point(&self, suspend: impl FnOnce() -> AnyOperation) {
285 if self.should_persist() {
286 self.snapshot_coord.suspend_point(suspend);
287 }
288 }
289
290 pub(crate) fn start_operation(&self) -> OperationGuard<'_, AnyOperation> {
291 if !self.should_persist() {
292 return OperationGuard::noop();
293 }
294 self.snapshot_coord.begin_operation()
295 }
296
297 fn should_persist(&self) -> bool {
298 matches!(
299 self.options.storage_mode,
300 Some(StorageMode::ReadWrite) | Some(StorageMode::ReadWriteOnShutdown)
301 )
302 }
303
304 #[doc(hidden)]
311 pub fn snapshot_and_evict_for_testing(
312 &self,
313 turbo_tasks: &TurboTasks<TurboTasksBackend>,
314 ) -> (bool, EvictionCounts) {
315 assert!(
316 self.should_persist(),
317 "snapshot_and_evict requires persistence"
318 );
319 let snapshot_result = self.snapshot_and_persist(None, SnapshotReason::Test, turbo_tasks);
320 let had_new_data = match snapshot_result {
321 Ok((_, new_data)) => new_data,
322 Err(_) => {
323 return (false, EvictionCounts::default());
327 }
328 };
329 let counts = self.storage.evict_after_snapshot(None);
330 (had_new_data, counts)
331 }
332
333 fn should_restore(&self) -> bool {
334 self.options.storage_mode.is_some()
335 }
336
337 fn should_track_dependencies(&self) -> bool {
338 self.options.dependency_tracking
339 }
340
341 fn should_track_activeness(&self) -> bool {
342 self.options.active_tracking
343 }
344
345 fn track_cache_hit_by_fn(&self, native_fn: &'static NativeFunction) {
346 self.task_statistics
347 .map(|stats| stats.increment_cache_hit(native_fn));
348 }
349
350 fn track_cache_miss_by_fn(&self, native_fn: &'static NativeFunction) {
351 self.task_statistics
352 .map(|stats| stats.increment_cache_miss(native_fn));
353 }
354
355 fn task_error_to_turbo_tasks_execution_error(
360 &self,
361 error: &TaskError,
362 ctx: &mut impl ExecuteContext<'_>,
363 ) -> TurboTasksExecutionError {
364 match error {
365 TaskError::Panic(panic) => TurboTasksExecutionError::Panic(panic.clone()),
366 TaskError::Error(item) => TurboTasksExecutionError::Error(Arc::new(TurboTasksError {
367 message: item.message.clone(),
368 source: item
369 .source
370 .as_ref()
371 .map(|e| self.task_error_to_turbo_tasks_execution_error(e, ctx)),
372 })),
373 TaskError::LocalTaskContext(local_task_context) => {
374 TurboTasksExecutionError::LocalTaskContext(Arc::new(TurboTaskLocalContextError {
375 name: local_task_context.name.clone(),
376 source: local_task_context
377 .source
378 .as_ref()
379 .map(|e| self.task_error_to_turbo_tasks_execution_error(e, ctx)),
380 }))
381 }
382 TaskError::TaskChain(chain) => {
383 let task_id = chain.last().unwrap();
384 let error = {
385 let task = ctx.task(*task_id, TaskDataCategory::Meta);
386 if let Some(OutputValue::Error(error)) = task.get_output() {
387 Some(error.clone())
388 } else {
389 None
390 }
391 };
392 let error = error.map_or_else(
393 || {
394 TurboTasksExecutionError::Panic(Arc::new(TurboTasksPanic {
396 message: TurboTasksExecutionErrorMessage::PIISafe(Cow::Borrowed(
397 "Error no longer available",
398 )),
399 location: None,
400 }))
401 },
402 |e| self.task_error_to_turbo_tasks_execution_error(&e, ctx),
403 );
404 let mut current_error = error;
405 for &task_id in chain.iter().rev() {
406 current_error =
407 TurboTasksExecutionError::TaskContext(Arc::new(TurboTaskContextError {
408 task_id,
409 source: Some(current_error),
410 turbo_tasks: ctx.turbo_tasks(),
411 }));
412 }
413 current_error
414 }
415 }
416 }
417}
418
419struct TaskExecutionCompletePrepareResult {
421 pub new_children: FxHashSet<TaskId>,
422 pub is_now_immutable: bool,
423 #[cfg(feature = "verify_determinism")]
424 pub no_output_set: bool,
425 #[cfg(feature = "task_dirty_cause")]
426 pub function_id: Option<FunctionId>,
427 pub new_output: Option<OutputValue>,
428 pub output_dependent_tasks: SmallVec<[TaskId; 4]>,
429 pub is_recomputation: bool,
430 pub is_session_dependent: bool,
431}
432
433fn lock_task_and_optional_reader<'e, C: ExecuteContext<'e>>(
434 ctx: &mut C,
435 task_id: TaskId,
436 reader_id: Option<TaskId>,
437) -> (C::TaskGuardImpl, Option<C::TaskGuardImpl>) {
438 let Some(reader_id) = reader_id else {
439 return (ctx.task(task_id, TaskDataCategory::All), None);
440 };
441
442 let task = ctx.task(task_id, TaskDataCategory::All);
446 if task.immutable() && !cfg!(feature = "verify_immutable") {
447 (task, None)
448 } else {
449 drop(task);
450
451 let (task, reader) = ctx.task_pair(task_id, reader_id, TaskDataCategory::All);
455 if task.immutable() && !cfg!(feature = "verify_immutable") {
459 drop(reader);
460 (task, None)
461 } else {
462 (task, Some(reader))
463 }
464 }
465}
466
467impl TurboTasksBackend {
469 fn try_read_task_output(
470 &self,
471 task_id: TaskId,
472 reader: Option<TaskId>,
473 options: ReadOutputOptions,
474 turbo_tasks: &TurboTasks<TurboTasksBackend>,
475 ) -> Result<Result<RawVc, EventListener>> {
476 self.assert_not_persistent_calling_transient(reader, task_id, None);
477
478 let mut ctx = self.execute_context(turbo_tasks);
479 let need_reader_task = reader.and_then(|reader_id| {
480 (self.should_track_dependencies()
481 && !matches!(options.tracking, ReadTracking::Untracked)
482 && reader_id != task_id)
483 .then_some(reader_id)
484 });
485 let (mut task, mut reader_task) =
486 lock_task_and_optional_reader(&mut ctx, task_id, need_reader_task);
487
488 fn listen_to_done_event(
489 reader_description: Option<EventDescription>,
490 tracking: ReadTracking,
491 done_event: &Event,
492 ) -> EventListener {
493 done_event.listen_with_note(move || {
494 move || {
495 if let Some(reader_description) = reader_description.as_ref() {
496 format!(
497 "try_read_task_output from {} ({})",
498 reader_description, tracking
499 )
500 } else {
501 format!("try_read_task_output ({})", tracking)
502 }
503 }
504 })
505 }
506
507 fn check_in_progress(
508 task: &impl TaskGuard,
509 reader_description: Option<EventDescription>,
510 tracking: ReadTracking,
511 ) -> Option<std::result::Result<std::result::Result<RawVc, EventListener>, anyhow::Error>>
512 {
513 match task.get_in_progress() {
514 Some(InProgressState::Scheduled { done_event, .. }) => Some(Ok(Err(
515 listen_to_done_event(reader_description, tracking, done_event),
516 ))),
517 Some(InProgressState::InProgress(box InProgressStateInner {
518 done_event, ..
519 })) => Some(Ok(Err(listen_to_done_event(
520 reader_description,
521 tracking,
522 done_event,
523 )))),
524 Some(InProgressState::Canceled) => Some(Err(anyhow::anyhow!(
525 "{} was canceled",
526 task.get_task_description()
527 ))),
528 None => None,
529 }
530 }
531
532 if matches!(options.consistency, ReadConsistency::Strong) {
533 if task
534 .get_persistent_task_type()
535 .is_some_and(|t| !t.native_fn.is_root)
536 {
537 drop(task);
538 drop(reader_task);
539 panic!(
540 "Strongly consistent read of non-root task {} (reader: {}). The `root` \
541 attribute is missing on the task.",
542 self.debug_get_task_description(task_id),
543 reader.map_or_else(
544 || "unknown".to_string(),
545 |r| self.debug_get_task_description(r)
546 )
547 );
548 }
549
550 let is_dirty = task.is_dirty();
551
552 let has_dirty_containers = task.has_dirty_containers();
554 if has_dirty_containers || is_dirty.is_some() {
555 let activeness = task.get_activeness_mut();
556 let mut task_ids_to_schedule: Vec<_> = Vec::new();
557 let activeness = if let Some(activeness) = activeness {
559 activeness.set_active_until_clean();
563 activeness
564 } else {
565 if ctx.should_track_activeness() {
569 task_ids_to_schedule = task.dirty_containers().collect();
571 task_ids_to_schedule.push(task_id);
572 }
573 let activeness =
574 task.get_activeness_mut_or_insert_with(|| ActivenessState::new(task_id));
575 activeness.set_active_until_clean();
576 activeness
577 };
578 let listener = activeness.all_clean_event.listen_with_note(move || {
579 let tt = turbo_tasks.pin();
582 move || {
583 let mut ctx = tt.backend().execute_context(&tt);
584 let mut visited = FxHashSet::default();
585 fn indent(s: &str) -> String {
586 s.split_inclusive('\n')
587 .flat_map(|line: &str| [" ", line].into_iter())
588 .collect::<String>()
589 }
590 fn get_info(
591 ctx: &mut impl ExecuteContext<'_>,
592 task_id: TaskId,
593 parent_and_count: Option<(TaskId, i32)>,
594 visited: &mut FxHashSet<TaskId>,
595 ) -> String {
596 let task = ctx.task(task_id, TaskDataCategory::All);
597 let is_dirty = task.is_dirty();
598 let in_progress =
599 task.get_in_progress()
600 .map_or("not in progress", |p| match p {
601 InProgressState::InProgress(_) => "in progress",
602 InProgressState::Scheduled { .. } => "scheduled",
603 InProgressState::Canceled => "canceled",
604 });
605 let activeness = task.get_activeness().map_or_else(
606 || "not active".to_string(),
607 |activeness| format!("{activeness:?}"),
608 );
609 let aggregation_number = get_aggregation_number(&task);
610 let missing_upper = if let Some((parent_task_id, _)) = parent_and_count
611 {
612 let uppers = get_uppers(&task);
613 !uppers.contains(&parent_task_id)
614 } else {
615 false
616 };
617
618 let has_dirty_containers = task.has_dirty_containers();
620
621 let task_description = task.get_task_description();
622 let is_dirty_label = if let Some(parent_priority) = is_dirty {
623 format!(", dirty({parent_priority})")
624 } else {
625 String::new()
626 };
627 let has_dirty_containers_label = if has_dirty_containers {
628 ", dirty containers"
629 } else {
630 ""
631 };
632 let count = if let Some((_, count)) = parent_and_count {
633 format!(" {count}")
634 } else {
635 String::new()
636 };
637 let mut info = format!(
638 "{task_id} {task_description}{count} (aggr={aggregation_number}, \
639 {in_progress}, \
640 {activeness}{is_dirty_label}{has_dirty_containers_label})",
641 );
642 let children: Vec<_> = task.dirty_containers_with_count().collect();
643 drop(task);
644
645 if missing_upper {
646 info.push_str("\n ERROR: missing upper connection");
647 }
648
649 if has_dirty_containers || !children.is_empty() {
650 writeln!(info, "\n dirty tasks:").unwrap();
651
652 for (child_task_id, count) in children {
653 let task_description = ctx
654 .task(child_task_id, TaskDataCategory::Data)
655 .get_task_description();
656 if visited.insert(child_task_id) {
657 let child_info = get_info(
658 ctx,
659 child_task_id,
660 Some((task_id, count)),
661 visited,
662 );
663 info.push_str(&indent(&child_info));
664 if !info.ends_with('\n') {
665 info.push('\n');
666 }
667 } else {
668 writeln!(
669 info,
670 " {child_task_id} {task_description} {count} \
671 (already visited)"
672 )
673 .unwrap();
674 }
675 }
676 }
677 info
678 }
679 let info = get_info(&mut ctx, task_id, None, &mut visited);
680 format!(
681 "try_read_task_output (strongly consistent) from {reader:?}\n{info}"
682 )
683 }
684 });
685 drop(reader_task);
686 drop(task);
687 if !task_ids_to_schedule.is_empty() {
688 let mut queue = AggregationUpdateQueue::new();
689 queue.extend_find_and_schedule_dirty(task_ids_to_schedule);
690 queue.execute(&mut ctx);
691 }
692
693 return Ok(Err(listener));
694 }
695 }
696
697 let reader_description = reader_task
698 .as_ref()
699 .map(|r| EventDescription::new(|| r.get_task_desc_fn()))
700 .or_else(|| {
701 need_reader_task.map(|reader_id| {
702 EventDescription::new(move || move || format!("{reader_id:?}"))
703 })
704 });
705 if let Some(value) = check_in_progress(&task, reader_description.clone(), options.tracking)
706 {
707 return value;
708 }
709
710 if let Some(output) = task.get_output() {
711 let result = match output {
712 OutputValue::Cell(cell) => Ok(Ok(RawVc::task_cell(cell.task, cell.cell))),
713 OutputValue::Output(task) => Ok(Ok(RawVc::task_output(*task))),
714 OutputValue::Error(error) => Err(error.clone()),
715 };
716 if let Some(mut reader_task) = reader_task.take()
717 && options.tracking.should_track(result.is_err())
718 {
719 #[cfg(feature = "trace_task_output_dependencies")]
720 let _span = tracing::trace_span!(
721 "add output dependency",
722 task = %task_id,
723 dependent_task = ?reader
724 )
725 .entered();
726 let mut queue = LeafDistanceUpdateQueue::new();
727 let reader = reader.unwrap();
728 if task.add_output_dependent(reader) {
729 let leaf_distance = task.get_leaf_distance().copied().unwrap_or_default();
731 let reader_leaf_distance =
732 reader_task.get_leaf_distance().copied().unwrap_or_default();
733 if reader_leaf_distance.distance <= leaf_distance.distance {
734 queue.push(
735 reader,
736 leaf_distance.distance,
737 leaf_distance.max_distance_in_buffer,
738 );
739 }
740 }
741
742 drop(task);
743
744 if !reader_task.remove_outdated_output_dependencies(&task_id) {
750 let _ = reader_task.add_output_dependencies(task_id);
751 }
752 drop(reader_task);
753
754 queue.execute(&mut ctx);
755 } else {
756 drop(task);
757 }
758
759 return result.map_err(|error| {
760 self.task_error_to_turbo_tasks_execution_error(&error, &mut ctx)
761 .with_task_context(task_id, turbo_tasks.pin())
762 .into()
763 });
764 }
765 drop(reader_task);
766
767 let note = EventDescription::new(|| {
768 move || {
769 if let Some(reader) = reader_description.as_ref() {
770 format!("try_read_task_output (recompute) from {reader}",)
771 } else {
772 "try_read_task_output (recompute, untracked)".to_string()
773 }
774 }
775 });
776
777 let (in_progress_state, listener) = InProgressState::new_scheduled_with_listener(
779 TaskExecutionReason::OutputNotAvailable,
780 EventDescription::new(|| task.get_task_desc_fn()),
781 note,
782 );
783
784 let old = task.set_in_progress(in_progress_state);
787 debug_assert!(old.is_none(), "InProgress already exists");
788 ctx.schedule_task(task, TaskPriority::Recomputation);
789
790 Ok(Err(listener))
791 }
792
793 fn try_read_task_cell(
794 &self,
795 task_id: TaskId,
796 reader: Option<TaskId>,
797 cell: CellId,
798 options: ReadCellOptions,
799 turbo_tasks: &TurboTasks<TurboTasksBackend>,
800 ) -> Result<Result<TypedCellContent, EventListener>> {
801 self.assert_not_persistent_calling_transient(reader, task_id, Some(cell));
802
803 fn add_cell_dependency(
804 task_id: TaskId,
805 mut task: impl TaskGuard,
806 reader: Option<TaskId>,
807 reader_task: Option<impl TaskGuard>,
808 cell: CellId,
809 key: Option<u64>,
810 ) {
811 if let Some(mut reader_task) = reader_task {
812 let reader = reader.unwrap();
813 let reverse = CellRef { task: reader, cell };
814 if let Some(k) = key {
815 let _ = task.add_cell_dependents_hashed((reverse, k));
816 } else {
817 let _ = task.add_cell_dependents(reverse);
818 }
819 drop(task);
820
821 let target = CellRef {
827 task: task_id,
828 cell,
829 };
830 if let Some(k) = key {
831 if !reader_task.remove_outdated_cell_dependencies_hashed(&(target, k)) {
832 let _ = reader_task.add_cell_dependencies_hashed((target, k));
833 }
834 } else if !reader_task.remove_outdated_cell_dependencies(&target) {
835 let _ = reader_task.add_cell_dependencies(target);
836 }
837 drop(reader_task);
838 }
839 }
840
841 let ReadCellOptions {
842 tracking,
843 final_read_hint,
844 } = options;
845
846 let mut ctx = self.execute_context(turbo_tasks);
847 let need_reader_task = reader.and_then(|reader_id| {
848 (self.should_track_dependencies()
849 && !matches!(tracking, ReadCellTracking::Untracked)
850 && reader_id != task_id)
851 .then_some(reader_id)
852 });
853 let (mut task, reader_task) =
854 lock_task_and_optional_reader(&mut ctx, task_id, need_reader_task);
855
856 let content = if final_read_hint {
857 task.remove_cell_data(&cell, &get_value_type(cell.type_id()).persistence)
858 } else {
859 task.get_cell_data(&cell).cloned()
860 };
861 if let Some(content) = content {
862 if tracking.should_track(false) {
863 add_cell_dependency(task_id, task, reader, reader_task, cell, tracking.key());
864 }
865 return Ok(Ok(TypedCellContent(
866 cell.type_id(),
867 CellContent(Some(content)),
868 )));
869 }
870
871 let in_progress = task.get_in_progress();
872 if matches!(
873 in_progress,
874 Some(InProgressState::InProgress(..) | InProgressState::Scheduled { .. })
875 ) {
876 return Ok(Err(self
877 .listen_to_cell(&mut task, task_id, need_reader_task, &reader_task, cell)
878 .0));
879 }
880 let is_cancelled = matches!(in_progress, Some(InProgressState::Canceled));
881
882 let max_id = task.get_cell_type_max_index(&cell.type_id()).copied();
884 let Some(max_id) = max_id else {
885 let task_desc = task.get_task_description();
886 if tracking.should_track(true) {
887 add_cell_dependency(task_id, task, reader, reader_task, cell, tracking.key());
888 }
889 bail!(
890 "Cell {cell:?} no longer exists in task {task_desc} (no cell of this type exists)",
891 );
892 };
893 if cell.index() >= max_id {
894 let task_desc = task.get_task_description();
895 if tracking.should_track(true) {
896 add_cell_dependency(task_id, task, reader, reader_task, cell, tracking.key());
897 }
898 bail!("Cell {cell:?} no longer exists in task {task_desc} (index out of bounds)");
899 }
900
901 if is_cancelled {
907 bail!("{} was canceled", task.get_task_description());
908 }
909
910 let (listener, new_listener) =
912 self.listen_to_cell(&mut task, task_id, need_reader_task, &reader_task, cell);
913 drop(reader_task);
914 if !new_listener {
915 return Ok(Err(listener));
916 }
917
918 let _span = tracing::trace_span!(
919 "recomputation",
920 cell_type = get_value_type(cell.type_id()).ty.global_name,
921 cell_index = cell.index()
922 )
923 .entered();
924
925 let _ = task.add_scheduled(
926 TaskExecutionReason::CellNotAvailable,
927 EventDescription::new(|| task.get_task_desc_fn()),
928 );
929 ctx.schedule_task(task, TaskPriority::Recomputation);
930
931 Ok(Err(listener))
932 }
933
934 fn listen_to_cell(
935 &self,
936 task: &mut impl TaskGuard,
937 task_id: TaskId,
938 reader: Option<TaskId>,
939 reader_task: &Option<impl TaskGuard>,
940 cell: CellId,
941 ) -> (EventListener, bool) {
942 let note = || {
943 let reader_desc = reader_task.as_ref().map(|r| r.get_task_desc_fn());
944 move || {
945 if let Some(reader_desc) = reader_desc.as_ref() {
946 format!("try_read_task_cell (in progress) from {}", (reader_desc)())
947 } else if let Some(reader_id) = reader {
948 format!("try_read_task_cell (in progress) from {reader_id:?}")
949 } else {
950 "try_read_task_cell (in progress, untracked)".to_string()
951 }
952 }
953 };
954 if let Some(in_progress) = task.get_in_progress_cells(&cell) {
955 let listener = in_progress.event.listen_with_note(note);
957 return (listener, false);
958 }
959 let in_progress = InProgressCellState::new(task_id, cell);
960 let listener = in_progress.event.listen_with_note(note);
961 let old = task.insert_in_progress_cells(cell, in_progress);
962 debug_assert!(old.is_none(), "InProgressCell already exists");
963 (listener, true)
964 }
965
966 fn snapshot_and_persist(
967 &self,
968 parent_span: Option<tracing::Id>,
969 reason: SnapshotReason,
970 turbo_tasks: &TurboTasks<TurboTasksBackend>,
971 ) -> Result<(Instant, bool), anyhow::Error> {
972 let snapshot_span =
973 tracing::trace_span!(parent: parent_span.clone(), "snapshot", reason = reason.as_str())
974 .entered();
975 let _snapshot_in_progress = self.snapshot_in_progress.lock();
979 let start = Instant::now();
980 let wall_start = SystemTime::now();
984 debug_assert!(self.should_persist());
985
986 let mut snapshot_phase = {
987 let _span = tracing::info_span!("blocking").entered();
988 self.snapshot_coord.begin_snapshot()
989 };
990 let (snapshot_guard, has_modifications) = self.storage.start_snapshot();
993
994 let suspended_operations = snapshot_phase.take_suspended_operations();
995
996 let snapshot_time = Instant::now();
997 drop(snapshot_phase);
998
999 if !has_modifications {
1000 drop(snapshot_guard);
1003 return Ok((start, false));
1004 }
1005
1006 #[cfg(feature = "print_cache_item_size")]
1007 #[derive(Default)]
1008 struct TaskCacheStats {
1009 data: usize,
1010 #[cfg(feature = "print_cache_item_size_with_compressed")]
1011 data_compressed: usize,
1012 data_count: usize,
1013 meta: usize,
1014 #[cfg(feature = "print_cache_item_size_with_compressed")]
1015 meta_compressed: usize,
1016 meta_count: usize,
1017 upper_count: usize,
1018 collectibles_count: usize,
1019 aggregated_collectibles_count: usize,
1020 children_count: usize,
1021 followers_count: usize,
1022 collectibles_dependents_count: usize,
1023 aggregated_dirty_containers_count: usize,
1024 output_size: usize,
1025 }
1026 #[cfg(feature = "print_cache_item_size")]
1029 struct FormatSizes {
1030 size: usize,
1031 #[cfg(feature = "print_cache_item_size_with_compressed")]
1032 compressed_size: usize,
1033 }
1034 #[cfg(feature = "print_cache_item_size")]
1035 impl std::fmt::Display for FormatSizes {
1036 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1037 use turbo_tasks::util::FormatBytes;
1038 #[cfg(feature = "print_cache_item_size_with_compressed")]
1039 {
1040 write!(
1041 f,
1042 "{} ({} compressed)",
1043 FormatBytes(self.size),
1044 FormatBytes(self.compressed_size)
1045 )
1046 }
1047 #[cfg(not(feature = "print_cache_item_size_with_compressed"))]
1048 {
1049 write!(f, "{}", FormatBytes(self.size))
1050 }
1051 }
1052 }
1053 #[cfg(feature = "print_cache_item_size")]
1054 impl TaskCacheStats {
1055 #[cfg(feature = "print_cache_item_size_with_compressed")]
1056 fn compressed_size(data: &[u8]) -> Result<usize> {
1057 Ok(lzzzz::lz4::Compressor::new()?.next_to_vec(
1058 data,
1059 &mut Vec::new(),
1060 lzzzz::lz4::ACC_LEVEL_DEFAULT,
1061 )?)
1062 }
1063
1064 fn add_data(&mut self, data: &[u8]) {
1065 self.data += data.len();
1066 #[cfg(feature = "print_cache_item_size_with_compressed")]
1067 {
1068 self.data_compressed += Self::compressed_size(data).unwrap_or(0);
1069 }
1070 self.data_count += 1;
1071 }
1072
1073 fn add_meta(&mut self, data: &[u8]) {
1074 self.meta += data.len();
1075 #[cfg(feature = "print_cache_item_size_with_compressed")]
1076 {
1077 self.meta_compressed += Self::compressed_size(data).unwrap_or(0);
1078 }
1079 self.meta_count += 1;
1080 }
1081
1082 fn add_counts(&mut self, storage: &TaskStorage) {
1083 let counts = storage.meta_counts();
1084 self.upper_count += counts.upper;
1085 self.collectibles_count += counts.collectibles;
1086 self.aggregated_collectibles_count += counts.aggregated_collectibles;
1087 self.children_count += counts.children;
1088 self.followers_count += counts.followers;
1089 self.collectibles_dependents_count += counts.collectibles_dependents;
1090 self.aggregated_dirty_containers_count += counts.aggregated_dirty_containers;
1091 if let Some(output) = storage.get_output() {
1092 use turbo_bincode::turbo_bincode_encode;
1093
1094 self.output_size += turbo_bincode_encode(&output)
1095 .map(|data| data.len())
1096 .unwrap_or(0);
1097 }
1098 }
1099
1100 fn task_name(storage: &TaskStorage) -> String {
1102 storage
1103 .get_persistent_task_type()
1104 .map(|t| t.to_string())
1105 .unwrap_or_else(|| "<unknown>".to_string())
1106 }
1107
1108 fn sort_key(&self) -> usize {
1111 #[cfg(feature = "print_cache_item_size_with_compressed")]
1112 {
1113 self.data_compressed + self.meta_compressed
1114 }
1115 #[cfg(not(feature = "print_cache_item_size_with_compressed"))]
1116 {
1117 self.data + self.meta
1118 }
1119 }
1120
1121 fn format_total(&self) -> FormatSizes {
1122 FormatSizes {
1123 size: self.data + self.meta,
1124 #[cfg(feature = "print_cache_item_size_with_compressed")]
1125 compressed_size: self.data_compressed + self.meta_compressed,
1126 }
1127 }
1128
1129 fn format_data(&self) -> FormatSizes {
1130 FormatSizes {
1131 size: self.data,
1132 #[cfg(feature = "print_cache_item_size_with_compressed")]
1133 compressed_size: self.data_compressed,
1134 }
1135 }
1136
1137 fn format_avg_data(&self) -> FormatSizes {
1138 FormatSizes {
1139 size: self.data.checked_div(self.data_count).unwrap_or(0),
1140 #[cfg(feature = "print_cache_item_size_with_compressed")]
1141 compressed_size: self
1142 .data_compressed
1143 .checked_div(self.data_count)
1144 .unwrap_or(0),
1145 }
1146 }
1147
1148 fn format_meta(&self) -> FormatSizes {
1149 FormatSizes {
1150 size: self.meta,
1151 #[cfg(feature = "print_cache_item_size_with_compressed")]
1152 compressed_size: self.meta_compressed,
1153 }
1154 }
1155
1156 fn format_avg_meta(&self) -> FormatSizes {
1157 FormatSizes {
1158 size: self.meta.checked_div(self.meta_count).unwrap_or(0),
1159 #[cfg(feature = "print_cache_item_size_with_compressed")]
1160 compressed_size: self
1161 .meta_compressed
1162 .checked_div(self.meta_count)
1163 .unwrap_or(0),
1164 }
1165 }
1166 }
1167 #[cfg(feature = "print_cache_item_size")]
1168 let task_cache_stats: Mutex<FxHashMap<_, TaskCacheStats>> =
1169 Mutex::new(FxHashMap::default());
1170
1171 let process = |task_id: TaskId, inner: &TaskStorage, buffer: &mut TurboBincodeBuffer| {
1178 let encode_category = |task_id: TaskId,
1179 data: &TaskStorage,
1180 category: SpecificTaskDataCategory,
1181 buffer: &mut TurboBincodeBuffer|
1182 -> Option<TurboBincodeBuffer> {
1183 match encode_task_data(task_id, data, category, buffer) {
1184 Ok(encoded) => {
1185 #[cfg(feature = "print_cache_item_size")]
1186 {
1187 let mut stats = task_cache_stats.lock();
1188 let entry = stats.entry(TaskCacheStats::task_name(inner)).or_default();
1189 match category {
1190 SpecificTaskDataCategory::Meta => entry.add_meta(&encoded),
1191 SpecificTaskDataCategory::Data => entry.add_data(&encoded),
1192 }
1193 }
1194 Some(encoded)
1195 }
1196 Err(err) => {
1197 panic!(
1198 "Serializing task {} failed ({:?}): {:?}",
1199 self.debug_get_task_description(task_id),
1200 category,
1201 err
1202 );
1203 }
1204 }
1205 };
1206 if task_id.is_transient() {
1207 unreachable!("transient task_ids should never be enqueued to be persisted");
1208 }
1209
1210 let encode_meta = inner.flags.meta_modified();
1211 let encode_data = inner.flags.data_modified();
1212
1213 #[cfg(feature = "print_cache_item_size")]
1214 if encode_data || encode_meta {
1215 task_cache_stats
1216 .lock()
1217 .entry(TaskCacheStats::task_name(inner))
1218 .or_default()
1219 .add_counts(inner);
1220 }
1221
1222 let meta = if encode_meta {
1223 encode_category(task_id, inner, SpecificTaskDataCategory::Meta, buffer)
1224 } else {
1225 None
1226 };
1227
1228 let data = if encode_data {
1229 encode_category(task_id, inner, SpecificTaskDataCategory::Data, buffer)
1230 } else {
1231 None
1232 };
1233 let task_type_hash = if inner.flags.new_task() {
1234 let task_type = inner.get_persistent_task_type().expect(
1235 "It is not possible for a new_task to not have a persistent_task_type. Task \
1236 creation for persistent tasks uses a single ExecutionContextImpl for \
1237 creating the task (which sets new_task) and connect_child (which sets \
1238 persistent_task_type) and take_snapshot waits for all operations to complete \
1239 or suspend before we start snapshotting. So task creation will always set \
1240 the task_type.",
1241 );
1242 Some(compute_task_type_hash(task_type))
1243 } else {
1244 None
1245 };
1246
1247 SnapshotItem {
1248 task_id,
1249 meta,
1250 data,
1251 task_type_hash,
1252 }
1253 };
1254
1255 let task_snapshots =
1256 self.storage
1257 .take_snapshot(snapshot_guard, &process, reason.drain_entries());
1258
1259 drop(snapshot_span);
1260 let snapshot_duration = start.elapsed();
1261 let task_count = task_snapshots.len();
1262
1263 if task_snapshots.is_empty() {
1264 std::hint::cold_path();
1267 return Ok((snapshot_time, false));
1268 }
1269
1270 let persist_start = Instant::now();
1271 let span = tracing::info_span!(
1272 parent: parent_span,
1273 "persist",
1274 reason = reason.as_str(),
1275 snapshot_meta = tracing::field::Empty,
1276 )
1277 .entered();
1278 let snapshot_meta = self
1282 .backing_storage
1283 .save_snapshot(suspended_operations, task_snapshots)?;
1284 span.record("snapshot_meta", display(snapshot_meta));
1285
1286 #[cfg(feature = "print_cache_item_size")]
1287 {
1288 let mut task_cache_stats = task_cache_stats
1289 .into_inner()
1290 .into_iter()
1291 .collect::<Vec<_>>();
1292 if !task_cache_stats.is_empty() {
1293 use turbo_tasks::util::FormatBytes;
1294
1295 use crate::utils::markdown_table::print_markdown_table;
1296
1297 task_cache_stats.sort_unstable_by(|(key_a, stats_a), (key_b, stats_b)| {
1298 (stats_b.sort_key(), key_b).cmp(&(stats_a.sort_key(), key_a))
1299 });
1300
1301 println!(
1302 "Task cache stats: {}",
1303 FormatSizes {
1304 size: task_cache_stats
1305 .iter()
1306 .map(|(_, s)| s.data + s.meta)
1307 .sum::<usize>(),
1308 #[cfg(feature = "print_cache_item_size_with_compressed")]
1309 compressed_size: task_cache_stats
1310 .iter()
1311 .map(|(_, s)| s.data_compressed + s.meta_compressed)
1312 .sum::<usize>()
1313 },
1314 );
1315
1316 print_markdown_table(
1317 [
1318 "Task",
1319 " Total Size",
1320 " Data Size",
1321 " Data Count x Avg",
1322 " Data Count x Avg",
1323 " Meta Size",
1324 " Meta Count x Avg",
1325 " Meta Count x Avg",
1326 " Uppers",
1327 " Coll",
1328 " Agg Coll",
1329 " Children",
1330 " Followers",
1331 " Coll Deps",
1332 " Agg Dirty",
1333 " Output Size",
1334 ],
1335 task_cache_stats.iter(),
1336 |(task_desc, stats)| {
1337 [
1338 task_desc.to_string(),
1339 format!(" {}", stats.format_total()),
1340 format!(" {}", stats.format_data()),
1341 format!(" {} x", stats.data_count),
1342 format!("{}", stats.format_avg_data()),
1343 format!(" {}", stats.format_meta()),
1344 format!(" {} x", stats.meta_count),
1345 format!("{}", stats.format_avg_meta()),
1346 format!(" {}", stats.upper_count),
1347 format!(" {}", stats.collectibles_count),
1348 format!(" {}", stats.aggregated_collectibles_count),
1349 format!(" {}", stats.children_count),
1350 format!(" {}", stats.followers_count),
1351 format!(" {}", stats.collectibles_dependents_count),
1352 format!(" {}", stats.aggregated_dirty_containers_count),
1353 format!(" {}", FormatBytes(stats.output_size)),
1354 ]
1355 },
1356 );
1357 }
1358 }
1359
1360 let elapsed = start.elapsed();
1361 let persist_duration = persist_start.elapsed();
1362 if elapsed > Duration::from_secs(10) {
1364 turbo_tasks.send_compilation_event(Arc::new(TimingEvent::new(
1365 "Finished writing to filesystem cache".to_string(),
1366 elapsed,
1367 )));
1368 }
1369
1370 let wall_start_ms = wall_start
1371 .duration_since(SystemTime::UNIX_EPOCH)
1372 .unwrap_or_default()
1373 .as_secs_f64()
1375 * 1000.0;
1376 let wall_end_ms = wall_start_ms + elapsed.as_secs_f64() * 1000.0;
1377 turbo_tasks.send_compilation_event(Arc::new(TraceEvent::new(
1378 "turbopack-persistence",
1379 wall_start_ms,
1380 wall_end_ms,
1381 serde_json::json!([
1382 ["reason", reason.as_str()],
1383 [
1384 "snapshot_duration_ms",
1385 snapshot_duration.as_secs_f64() * 1000.0,
1386 ],
1387 [
1388 "persist_duration_ms",
1389 persist_duration.as_secs_f64() * 1000.0,
1390 ],
1391 ["task_count", task_count],
1392 ["bytes_written", snapshot_meta.bytes_written,],
1393 ["bytes_deleted", snapshot_meta.bytes_deleted,]
1394 ]),
1395 )));
1396
1397 Ok((snapshot_time, true))
1398 }
1399
1400 fn startup(&self, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
1401 if self.should_restore() {
1402 let uncompleted_operations = self
1406 .backing_storage
1407 .uncompleted_operations()
1408 .expect("Failed to get uncompleted operations");
1409 if !uncompleted_operations.is_empty() {
1410 let mut ctx = self.execute_context(turbo_tasks);
1411 for op in uncompleted_operations {
1412 op.execute(&mut ctx);
1413 }
1414 }
1415 }
1416
1417 if matches!(self.options.storage_mode, Some(StorageMode::ReadWrite)) {
1420 let _span = trace_span!("persisting background job").entered();
1422 let _span = tracing::info_span!("thread").entered();
1423 turbo_tasks.schedule_backend_background_job(TurboTasksBackendJob::Snapshot);
1424 }
1425 }
1426
1427 fn stopping(&self) {
1428 self.stopping.store(true, Ordering::Release);
1429 self.stopping_event.notify(usize::MAX);
1430 }
1431
1432 #[allow(unused_variables)]
1433 fn stop(&self, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
1434 #[cfg(feature = "verify_aggregation_graph")]
1435 {
1436 self.is_idle.store(false, Ordering::Release);
1437 self.verify_aggregation_graph(turbo_tasks, false);
1438 }
1439 self.storage.drop_task_cache();
1441 if self.should_persist() {
1442 if let Err(err) =
1446 self.snapshot_and_persist(Span::current().into(), SnapshotReason::Stop, turbo_tasks)
1447 {
1448 eprintln!("Persisting failed during shutdown: {err:?}");
1449 }
1450 }
1451 self.storage.drop_contents();
1452 if let Err(err) = self.backing_storage.shutdown() {
1453 println!("Shutting down failed: {err}");
1454 }
1455 }
1456
1457 #[allow(unused_variables)]
1458 fn idle_start(&self, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
1459 self.idle_start_event.notify(usize::MAX);
1460
1461 #[cfg(feature = "verify_aggregation_graph")]
1462 {
1463 use tokio::select;
1464
1465 self.is_idle.store(true, Ordering::Release);
1466 let turbo_tasks = turbo_tasks.pin();
1470 tokio::task::spawn(async move {
1471 let backend = &turbo_tasks.backend();
1472 select! {
1473 _ = tokio::time::sleep(Duration::from_secs(5)) => {
1474 }
1476 _ = backend.idle_end_event.listen() => {
1477 return;
1478 }
1479 }
1480 if !backend.is_idle.load(Ordering::Relaxed) {
1481 return;
1482 }
1483 backend.verify_aggregation_graph(&turbo_tasks, true);
1484 });
1485 }
1486 }
1487
1488 fn idle_end(&self) {
1489 #[cfg(feature = "verify_aggregation_graph")]
1490 self.is_idle.store(false, Ordering::Release);
1491 self.idle_end_event.notify(usize::MAX);
1492 }
1493
1494 fn get_or_create_task(
1495 &self,
1496 native_fn: &'static NativeFunction,
1497 this: Option<RawVc>,
1498 arg: &mut dyn DynTaskInputsStorage,
1499 parent_task: Option<TaskId>,
1500 persistence: TaskPersistence,
1501 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1502 ) -> TaskId {
1503 let transient = matches!(persistence, TaskPersistence::Transient);
1504
1505 if transient
1506 && let Some(parent_task) = parent_task
1507 && !parent_task.is_transient()
1508 {
1509 let task_type = CachedTaskType {
1510 native_fn,
1511 this,
1512 arg: arg.take_box(),
1513 };
1514 self.panic_persistent_calling_transient(
1515 self.debug_get_task_description(parent_task),
1516 Some(&task_type),
1517 None,
1518 );
1519 }
1520
1521 let is_root = native_fn.is_root;
1522
1523 let arg_ref = arg.as_ref();
1525 let hash = CachedTaskType::hash_from_components(
1526 self.storage.task_cache.hasher(),
1527 native_fn,
1528 this,
1529 arg_ref,
1530 );
1531 let shard = get_shard(&self.storage.task_cache, hash);
1535
1536 let mut ctx = self.execute_context(turbo_tasks);
1537 if let Some(task_id) =
1541 raw_get_in_shard(shard, hash, |k| k.eq_components(native_fn, this, arg_ref))
1542 {
1543 self.track_cache_hit_by_fn(native_fn);
1544 operation::ConnectChildOperation::run(parent_task, task_id, ctx);
1545 return task_id;
1546 }
1547
1548 let task_id = if !transient
1553 && let Some((task_id, stored_type)) = ctx.task_by_type(native_fn, this, arg_ref)
1554 {
1555 self.track_cache_hit_by_fn(native_fn);
1556 match raw_entry_in_shard(shard, self.storage.task_cache.hasher(), hash, |k| {
1559 k.eq_components(native_fn, this, arg_ref)
1560 }) {
1561 RawEntry::Occupied(_) => {}
1562 RawEntry::Vacant(e) => {
1563 e.insert(stored_type, task_id);
1564 }
1565 };
1566 task_id
1567 } else {
1568 match raw_entry_in_shard(shard, self.storage.task_cache.hasher(), hash, |k| {
1569 k.eq_components(native_fn, this, arg_ref)
1570 }) {
1571 RawEntry::Occupied(e) => {
1572 let task_id = *e.get();
1575 drop(e);
1576 self.track_cache_hit_by_fn(native_fn);
1577 task_id
1578 }
1579 RawEntry::Vacant(e) => {
1580 let task_type = CachedTaskTypeArc::new(CachedTaskType {
1584 native_fn,
1585 this,
1586 arg: arg.take_box(),
1587 });
1588 let task_id = if transient {
1589 self.transient_task_id_factory.get()
1590 } else {
1591 self.persisted_task_id_factory.get()
1592 };
1593 self.storage
1597 .initialize_new_task(task_id, Some(task_type.clone()));
1598 e.insert(task_type, task_id);
1600 self.track_cache_miss_by_fn(native_fn);
1601 if is_root {
1605 AggregationUpdateQueue::run(
1606 AggregationUpdateJob::UpdateAggregationNumber {
1607 task_id,
1608 base_aggregation_number: u32::MAX,
1609 distance: None,
1610 },
1611 &mut ctx,
1612 );
1613 } else if native_fn.is_session_dependent && self.should_track_dependencies() {
1614 const SESSION_DEPENDENT_AGGREGATION_NUMBER: u32 = u32::MAX >> 2;
1615 AggregationUpdateQueue::run(
1616 AggregationUpdateJob::UpdateAggregationNumber {
1617 task_id,
1618 base_aggregation_number: SESSION_DEPENDENT_AGGREGATION_NUMBER,
1619 distance: None,
1620 },
1621 &mut ctx,
1622 );
1623 };
1624
1625 task_id
1626 }
1627 }
1628 };
1629
1630 operation::ConnectChildOperation::run(parent_task, task_id, ctx);
1631
1632 task_id
1633 }
1634
1635 fn debug_trace_transient_task(
1638 &self,
1639 task_type: &CachedTaskType,
1640 cell_id: Option<CellId>,
1641 ) -> DebugTraceTransientTask {
1642 fn inner_id(
1645 backend: &TurboTasksBackend,
1646 task_id: TaskId,
1647 cell_type_id: Option<ValueTypeId>,
1648 visited_set: &mut FxHashSet<TaskId>,
1649 ) -> DebugTraceTransientTask {
1650 if let Some(task_type) = backend.debug_get_cached_task_type(task_id) {
1651 if visited_set.contains(&task_id) {
1652 let task_name = task_type.get_name();
1653 DebugTraceTransientTask::Collapsed {
1654 task_name,
1655 cell_type_id,
1656 }
1657 } else {
1658 inner_cached(backend, &task_type, cell_type_id, visited_set)
1659 }
1660 } else {
1661 DebugTraceTransientTask::Uncached { cell_type_id }
1662 }
1663 }
1664 fn inner_cached(
1665 backend: &TurboTasksBackend,
1666 task_type: &CachedTaskType,
1667 cell_type_id: Option<ValueTypeId>,
1668 visited_set: &mut FxHashSet<TaskId>,
1669 ) -> DebugTraceTransientTask {
1670 let task_name = task_type.get_name();
1671
1672 let cause_self = task_type.this.and_then(|cause_self_raw_vc| {
1673 let Some(task_id) = cause_self_raw_vc.try_get_task_id() else {
1674 return None;
1678 };
1679 if task_id.is_transient() {
1680 Some(Box::new(inner_id(
1681 backend,
1682 task_id,
1683 cause_self_raw_vc.try_get_type_id(),
1684 visited_set,
1685 )))
1686 } else {
1687 None
1688 }
1689 });
1690 let cause_args = task_type
1691 .arg
1692 .get_raw_vcs()
1693 .into_iter()
1694 .filter_map(|raw_vc| {
1695 let Some(task_id) = raw_vc.try_get_task_id() else {
1696 return None;
1698 };
1699 if !task_id.is_transient() {
1700 return None;
1701 }
1702 Some((task_id, raw_vc.try_get_type_id()))
1703 })
1704 .collect::<IndexSet<_>>() .into_iter()
1706 .map(|(task_id, cell_type_id)| {
1707 inner_id(backend, task_id, cell_type_id, visited_set)
1708 })
1709 .collect();
1710
1711 DebugTraceTransientTask::Cached {
1712 task_name,
1713 cell_type_id,
1714 cause_self,
1715 cause_args,
1716 }
1717 }
1718 inner_cached(
1719 self,
1720 task_type,
1721 cell_id.map(|c| c.type_id()),
1722 &mut FxHashSet::default(),
1723 )
1724 }
1725
1726 fn invalidate_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
1727 if !self.should_track_dependencies() {
1728 panic!("Dependency tracking is disabled so invalidation is not allowed");
1729 }
1730 operation::InvalidateOperation::run(
1731 smallvec![task_id],
1732 #[cfg(feature = "task_dirty_cause")]
1733 TaskDirtyCause::Invalidator,
1734 self.execute_context(turbo_tasks),
1735 );
1736 }
1737
1738 fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks<TurboTasksBackend>) {
1739 if !self.should_track_dependencies() {
1740 panic!("Dependency tracking is disabled so invalidation is not allowed");
1741 }
1742 operation::InvalidateOperation::run(
1743 tasks.iter().copied().collect(),
1744 #[cfg(feature = "task_dirty_cause")]
1745 TaskDirtyCause::Unknown,
1746 self.execute_context(turbo_tasks),
1747 );
1748 }
1749
1750 fn invalidate_tasks_set(
1751 &self,
1752 tasks: &AutoSet<TaskId, BuildHasherDefault<FxHasher>, 2>,
1753 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1754 ) {
1755 if !self.should_track_dependencies() {
1756 panic!("Dependency tracking is disabled so invalidation is not allowed");
1757 }
1758 operation::InvalidateOperation::run(
1759 tasks.iter().copied().collect(),
1760 #[cfg(feature = "task_dirty_cause")]
1761 TaskDirtyCause::Unknown,
1762 self.execute_context(turbo_tasks),
1763 );
1764 }
1765
1766 fn invalidate_serialization(
1767 &self,
1768 task_id: TaskId,
1769 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1770 ) {
1771 if task_id.is_transient() {
1772 return;
1773 }
1774 let mut ctx = self.execute_context(turbo_tasks);
1775 let mut task = ctx.task(task_id, TaskDataCategory::Data);
1776 task.invalidate_serialization();
1777 }
1778
1779 fn debug_get_task_description(&self, task_id: TaskId) -> String {
1780 let task = self.storage.access_mut(task_id);
1781 if let Some(value) = task.get_persistent_task_type() {
1782 format!("{task_id:?} {}", value)
1783 } else if let Some(value) = task.get_transient_task_type() {
1784 format!("{task_id:?} {}", value)
1785 } else {
1786 format!("{task_id:?} unknown")
1787 }
1788 }
1789
1790 fn get_task_name(
1791 &self,
1792 task_id: TaskId,
1793 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1794 ) -> String {
1795 let mut ctx = self.execute_context(turbo_tasks);
1796 let task = ctx.task(task_id, TaskDataCategory::Data);
1797 if let Some(value) = task.get_persistent_task_type() {
1798 value.to_string()
1799 } else if let Some(value) = task.get_transient_task_type() {
1800 value.to_string()
1801 } else {
1802 "unknown".to_string()
1803 }
1804 }
1805
1806 fn debug_get_cached_task_type(&self, task_id: TaskId) -> Option<CachedTaskTypeArc> {
1807 let task = self.storage.access_mut(task_id);
1808 task.get_persistent_task_type().cloned()
1809 }
1810
1811 fn task_execution_canceled(
1812 &self,
1813 task_id: TaskId,
1814 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1815 ) {
1816 let mut ctx = self.execute_context(turbo_tasks);
1817 let mut task = ctx.task(task_id, TaskDataCategory::All);
1818 if let Some(in_progress) = task.take_in_progress() {
1819 match in_progress {
1820 InProgressState::Scheduled {
1821 done_event,
1822 reason: _,
1823 } => done_event.notify(usize::MAX),
1824 InProgressState::InProgress(box InProgressStateInner { done_event, .. }) => {
1825 done_event.notify(usize::MAX)
1826 }
1827 InProgressState::Canceled => {}
1828 }
1829 }
1830 let in_progress_cells = task.take_in_progress_cells();
1833 if let Some(ref cells) = in_progress_cells {
1834 for state in cells.values() {
1835 state.event.notify(usize::MAX);
1836 }
1837 }
1838
1839 let data_update = if self.should_track_dependencies() && !task_id.is_transient() {
1845 task.update_dirty_state(Some(Dirtyness::SessionDependent))
1846 } else {
1847 None
1848 };
1849
1850 let old = task.set_in_progress(InProgressState::Canceled);
1851 debug_assert!(old.is_none(), "InProgress already exists");
1852 drop(task);
1853
1854 if let Some(data_update) = data_update {
1855 AggregationUpdateQueue::run(data_update, &mut ctx);
1856 }
1857
1858 drop(in_progress_cells);
1859 }
1860
1861 fn try_start_task_execution(
1862 &self,
1863 task_id: TaskId,
1864 priority: TaskPriority,
1865 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1866 ) -> Option<TaskExecutionSpec<'_>> {
1867 let execution_reason;
1868 let task_type;
1869 #[cfg(feature = "task_dirty_cause")]
1870 let cause;
1871 {
1872 let mut ctx = self.execute_context(turbo_tasks);
1873 let mut task = ctx.task(task_id, TaskDataCategory::All);
1874 task_type = task.get_task_type().to_owned();
1875 let once_task = matches!(task_type, TaskType::Transient(ref tt) if matches!(&**tt, TransientTask::Once(_)));
1876 if let Some(tasks) = task.prefetch() {
1877 drop(task);
1878 ctx.prepare_tasks(tasks, "prefetch");
1879 task = ctx.task(task_id, TaskDataCategory::All);
1880 }
1881 let in_progress = task.take_in_progress()?;
1882 let InProgressState::Scheduled { done_event, reason } = in_progress else {
1883 let old = task.set_in_progress(in_progress);
1884 debug_assert!(old.is_none(), "InProgress already exists");
1885 return None;
1886 };
1887 execution_reason = reason;
1888 #[cfg(feature = "task_dirty_cause")]
1889 {
1890 cause = match task.get_dirty() {
1891 Some(Dirtyness::Dirty { cause, .. }) => Some(cause.clone()),
1892 _ => None,
1893 };
1894 }
1895 let old = task.set_in_progress(InProgressState::InProgress(Box::new(
1896 InProgressStateInner {
1897 stale: false,
1898 once_task,
1899 done_event,
1900 marked_as_completed: false,
1901 new_children: Default::default(),
1902 },
1903 )));
1904 debug_assert!(old.is_none(), "InProgress already exists");
1905
1906 enum Collectible {
1908 Current(CollectibleRef, i32),
1909 Outdated(CollectibleRef),
1910 }
1911 let collectibles = task
1912 .iter_collectibles()
1913 .map(|(&collectible, &value)| Collectible::Current(collectible, value))
1914 .chain(
1915 task.iter_outdated_collectibles()
1916 .map(|(collectible, _count)| Collectible::Outdated(*collectible)),
1917 )
1918 .collect::<Vec<_>>();
1919 for collectible in collectibles {
1920 match collectible {
1921 Collectible::Current(collectible, value) => {
1922 let _ = task.insert_outdated_collectible(collectible, value);
1923 }
1924 Collectible::Outdated(collectible) => {
1925 if task
1926 .collectibles()
1927 .is_none_or(|m| m.get(&collectible).is_none())
1928 {
1929 task.remove_outdated_collectibles(&collectible);
1930 }
1931 }
1932 }
1933 }
1934
1935 if self.should_track_dependencies() {
1936 let cell_dependencies = task.iter_cell_dependencies().collect();
1941 task.set_outdated_cell_dependencies(cell_dependencies);
1942 let cell_dependencies_hashed = task.iter_cell_dependencies_hashed().collect();
1943 task.set_outdated_cell_dependencies_hashed(cell_dependencies_hashed);
1944
1945 let outdated_output_dependencies = task.iter_output_dependencies().collect();
1946 task.set_outdated_output_dependencies(outdated_output_dependencies);
1947 }
1948 }
1949
1950 let (span, future) = match task_type {
1951 TaskType::Cached(task_type) => {
1952 let CachedTaskType {
1953 native_fn,
1954 this,
1955 arg,
1956 } = &*task_type;
1957 (
1958 native_fn.span(
1959 task_id.persistence(),
1960 execution_reason,
1961 priority,
1962 #[cfg(feature = "task_dirty_cause")]
1963 cause.as_ref(),
1964 ),
1965 native_fn.execute(*this, &**arg),
1966 )
1967 }
1968 TaskType::Transient(task_type) => {
1969 let span = tracing::trace_span!("turbo_tasks::root_task");
1970 let future = match &*task_type {
1971 TransientTask::Root(f) => f(),
1972 TransientTask::Once(future_mutex) => take(&mut *future_mutex.lock())?,
1973 };
1974 (span, future)
1975 }
1976 };
1977 Some(TaskExecutionSpec { future, span })
1978 }
1979
1980 fn task_execution_completed(
1984 &self,
1985 task_id: TaskId,
1986 result: Result<RawVc, TurboTasksExecutionError>,
1987 cell_counters: &AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>,
1988 #[cfg(feature = "verify_determinism")] stateful: bool,
1989 has_invalidator: bool,
1990 turbo_tasks: &TurboTasks<TurboTasksBackend>,
1991 ) -> Option<TaskPriority> {
1992 #[cfg(not(feature = "trace_task_details"))]
2007 let span = tracing::trace_span!(
2008 "task execution completed",
2009 new_children = tracing::field::Empty
2010 )
2011 .entered();
2012 #[cfg(feature = "trace_task_details")]
2013 let span = tracing::trace_span!(
2014 "task execution completed",
2015 task_id = display(task_id),
2016 result = match result.as_ref() {
2017 Ok(value) => display(either::Either::Left(value)),
2018 Err(err) => display(either::Either::Right(err)),
2019 },
2020 new_children = tracing::field::Empty,
2021 immutable = tracing::field::Empty,
2022 new_output = tracing::field::Empty,
2023 output_dependents = tracing::field::Empty,
2024 aggregation_number = tracing::field::Empty,
2025 stale = tracing::field::Empty,
2026 )
2027 .entered();
2028
2029 let is_error = result.is_err();
2030
2031 let mut ctx = self.execute_context(turbo_tasks);
2032
2033 let TaskExecutionCompletePrepareResult {
2034 new_children,
2035 is_now_immutable,
2036 #[cfg(feature = "verify_determinism")]
2037 no_output_set,
2038 new_output,
2039 #[cfg(feature = "task_dirty_cause")]
2040 function_id,
2041 output_dependent_tasks,
2042 is_recomputation,
2043 is_session_dependent,
2044 } = match self.task_execution_completed_prepare(
2045 &mut ctx,
2046 #[cfg(feature = "trace_task_details")]
2047 &span,
2048 task_id,
2049 result,
2050 cell_counters,
2051 #[cfg(feature = "verify_determinism")]
2052 stateful,
2053 has_invalidator,
2054 ) {
2055 Ok(r) => r,
2056 Err(stale_priority) => {
2057 #[cfg(feature = "trace_task_details")]
2059 span.record("stale", "prepare");
2060 return Some(stale_priority);
2061 }
2062 };
2063
2064 #[cfg(feature = "trace_task_details")]
2065 span.record("new_output", new_output.is_some());
2066 #[cfg(feature = "trace_task_details")]
2067 span.record("output_dependents", output_dependent_tasks.len());
2068
2069 if !output_dependent_tasks.is_empty() {
2074 self.task_execution_completed_invalidate_output_dependent(
2075 &mut ctx,
2076 task_id,
2077 #[cfg(feature = "task_dirty_cause")]
2078 function_id,
2079 output_dependent_tasks,
2080 );
2081 }
2082
2083 let has_new_children = !new_children.is_empty();
2084 span.record("new_children", new_children.len());
2085
2086 if has_new_children {
2087 self.task_execution_completed_unfinished_children_dirty(&mut ctx, &new_children)
2088 }
2089
2090 if has_new_children
2091 && let Some(stale_priority) =
2092 self.task_execution_completed_connect(&mut ctx, task_id, new_children)
2093 {
2094 #[cfg(feature = "trace_task_details")]
2096 span.record("stale", "connect");
2097 return Some(stale_priority);
2098 }
2099
2100 let (stale_priority, in_progress_cells) = self.task_execution_completed_finish(
2101 &mut ctx,
2102 task_id,
2103 #[cfg(feature = "verify_determinism")]
2104 no_output_set,
2105 new_output,
2106 is_now_immutable,
2107 is_session_dependent,
2108 );
2109 if let Some(stale_priority) = stale_priority {
2110 #[cfg(feature = "trace_task_details")]
2112 span.record("stale", "finish");
2113 return Some(stale_priority);
2114 }
2115
2116 let removed_data = self.task_execution_completed_cleanup(
2117 &mut ctx,
2118 task_id,
2119 cell_counters,
2120 is_error,
2121 is_recomputation,
2122 );
2123
2124 drop(removed_data);
2126 drop(in_progress_cells);
2127
2128 None
2129 }
2130
2131 fn task_execution_completed_prepare(
2132 &self,
2133 ctx: &mut impl ExecuteContext<'_>,
2134 #[cfg(feature = "trace_task_details")] span: &Span,
2135 task_id: TaskId,
2136 result: Result<RawVc, TurboTasksExecutionError>,
2137 cell_counters: &AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>,
2138 #[cfg(feature = "verify_determinism")] stateful: bool,
2139 has_invalidator: bool,
2140 ) -> Result<TaskExecutionCompletePrepareResult, TaskPriority> {
2141 let mut task = ctx.task(task_id, TaskDataCategory::All);
2142 let is_recomputation = task.is_dirty().is_none();
2143 let is_session_dependent = self.should_track_dependencies()
2146 && matches!(task.get_task_type(), TaskTypeRef::Cached(tt) if tt.native_fn.is_session_dependent);
2147 let Some(in_progress) = task.get_in_progress_mut() else {
2148 panic!("Task execution completed, but task is not in progress: {task:#?}");
2149 };
2150 if matches!(in_progress, InProgressState::Canceled) {
2151 return Ok(TaskExecutionCompletePrepareResult {
2152 new_children: Default::default(),
2153 is_now_immutable: false,
2154 #[cfg(feature = "verify_determinism")]
2155 no_output_set: false,
2156 #[cfg(feature = "task_dirty_cause")]
2157 function_id: None,
2158 new_output: None,
2159 output_dependent_tasks: Default::default(),
2160 is_recomputation,
2161 is_session_dependent,
2162 });
2163 }
2164 let &mut InProgressState::InProgress(box InProgressStateInner {
2165 stale,
2166 ref mut new_children,
2167 once_task: is_once_task,
2168 ..
2169 }) = in_progress
2170 else {
2171 panic!("Task execution completed, but task is not in progress: {task:#?}");
2172 };
2173
2174 #[cfg(not(feature = "no_fast_stale"))]
2176 if stale && !is_once_task {
2177 let stale_priority = compute_stale_priority(&task);
2178 let Some(InProgressState::InProgress(box InProgressStateInner {
2179 done_event,
2180 mut new_children,
2181 ..
2182 })) = task.take_in_progress()
2183 else {
2184 unreachable!();
2185 };
2186 let old = task.set_in_progress(InProgressState::Scheduled {
2187 done_event,
2188 reason: TaskExecutionReason::Stale,
2189 });
2190 debug_assert!(old.is_none(), "InProgress already exists");
2191 for task in task.iter_children() {
2194 new_children.remove(&task);
2195 }
2196 drop(task);
2197
2198 AggregationUpdateQueue::run(
2201 AggregationUpdateJob::DecreaseActiveCounts {
2202 task_ids: new_children.into_iter().collect(),
2203 },
2204 ctx,
2205 );
2206 return Err(stale_priority);
2207 }
2208
2209 let mut new_children = take(new_children);
2211
2212 #[cfg(feature = "task_dirty_cause")]
2214 let function_id = match task.get_task_type() {
2215 TaskTypeRef::Cached(task_type) => {
2216 Some(turbo_tasks::registry::get_function_id(task_type.native_fn))
2217 }
2218 TaskTypeRef::Transient(_) => None,
2219 };
2220
2221 #[cfg(feature = "verify_determinism")]
2223 if stateful {
2224 task.set_stateful(true);
2225 }
2226
2227 if has_invalidator {
2229 task.set_invalidator(true);
2230 }
2231
2232 if result.is_ok() || is_recomputation {
2242 let old_counters: FxHashMap<_, _> = task
2243 .iter_cell_type_max_index()
2244 .map(|(&k, &v)| (k, v))
2245 .collect();
2246 let mut counters_to_remove = old_counters.clone();
2247
2248 for (&cell_type, &max_index) in cell_counters.iter() {
2249 if let Some(old_max_index) = counters_to_remove.remove(&cell_type) {
2250 if old_max_index != max_index {
2251 task.insert_cell_type_max_index(cell_type, max_index);
2252 }
2253 } else {
2254 task.insert_cell_type_max_index(cell_type, max_index);
2255 }
2256 }
2257 for (cell_type, _) in counters_to_remove {
2258 task.remove_cell_type_max_index(&cell_type);
2259 }
2260 }
2261
2262 let mut queue = AggregationUpdateQueue::new();
2263
2264 let mut old_edges = Vec::new();
2265
2266 let has_children = !new_children.is_empty();
2267 let is_immutable = task.immutable();
2268 let task_dependencies_for_immutable =
2269 if !is_immutable
2271 && !is_session_dependent
2273 && !task.invalidator()
2275 && task.is_collectibles_dependencies_empty()
2277 {
2278 Some(
2279 task.iter_output_dependencies()
2281 .chain(task.iter_cell_dependencies().map(|r| r.task))
2282 .chain(task.iter_cell_dependencies_hashed().map(|(r, _)| r.task))
2283 .collect::<FxHashSet<_>>(),
2284 )
2285 } else {
2286 None
2287 };
2288
2289 if has_children {
2290 let _aggregation_number =
2292 prepare_new_children(task_id, &mut task, &new_children, &mut queue);
2293
2294 #[cfg(feature = "trace_task_details")]
2295 span.record("aggregation_number", _aggregation_number);
2296
2297 old_edges.extend(
2299 task.iter_children()
2300 .filter(|task| !new_children.remove(task))
2301 .map(OutdatedEdge::Child),
2302 );
2303 } else {
2304 old_edges.extend(task.iter_children().map(OutdatedEdge::Child));
2305 }
2306
2307 old_edges.extend(
2308 task.iter_outdated_collectibles()
2309 .map(|(&collectible, &count)| OutdatedEdge::Collectible(collectible, count)),
2310 );
2311
2312 if self.should_track_dependencies() {
2313 old_edges.extend(
2320 task.iter_outdated_cell_dependencies()
2321 .map(OutdatedEdge::CellDependency),
2322 );
2323 old_edges.extend(
2324 task.iter_outdated_cell_dependencies_hashed()
2325 .map(|(r, k)| OutdatedEdge::HashedCellDependency(r, k)),
2326 );
2327 old_edges.extend(
2328 task.iter_outdated_output_dependencies()
2329 .map(OutdatedEdge::OutputDependency),
2330 );
2331 }
2332
2333 let current_output = task.get_output();
2335 #[cfg(feature = "verify_determinism")]
2336 let no_output_set = current_output.is_none();
2337 let new_output = match result.map(RawVc::unpack) {
2338 Ok(RawVcUnpacked::TaskOutput(output_task_id)) => {
2339 if let Some(OutputValue::Output(current_task_id)) = current_output
2340 && *current_task_id == output_task_id
2341 {
2342 None
2343 } else {
2344 Some(OutputValue::Output(output_task_id))
2345 }
2346 }
2347 Ok(RawVcUnpacked::TaskCell(output_task_id, cell)) => {
2348 if let Some(OutputValue::Cell(CellRef {
2349 task: current_task_id,
2350 cell: current_cell,
2351 })) = current_output
2352 && *current_task_id == output_task_id
2353 && *current_cell == cell
2354 {
2355 None
2356 } else {
2357 Some(OutputValue::Cell(CellRef {
2358 task: output_task_id,
2359 cell,
2360 }))
2361 }
2362 }
2363 Ok(RawVcUnpacked::LocalOutput(..)) => {
2364 panic!("Non-local tasks must not return a local Vc");
2365 }
2366 Err(err) => {
2367 if let Some(OutputValue::Error(old_error)) = current_output
2368 && **old_error == err
2369 {
2370 None
2371 } else {
2372 Some(OutputValue::Error(Arc::new((&err).into())))
2373 }
2374 }
2375 };
2376 let mut output_dependent_tasks = SmallVec::<[_; 4]>::new();
2377 if new_output.is_some() && ctx.should_track_dependencies() {
2379 output_dependent_tasks = task.iter_output_dependent().collect();
2380 }
2381
2382 drop(task);
2383
2384 let mut is_now_immutable = false;
2386 if let Some(dependencies) = task_dependencies_for_immutable
2387 && dependencies
2388 .iter()
2389 .all(|&task_id| ctx.task(task_id, TaskDataCategory::Data).immutable())
2390 {
2391 is_now_immutable = true;
2392 }
2393 #[cfg(feature = "trace_task_details")]
2394 span.record("immutable", is_immutable || is_now_immutable);
2395
2396 if !queue.is_empty() || !old_edges.is_empty() {
2397 #[cfg(any(
2398 feature = "trace_task_completion",
2399 feature = "trace_aggregation_update_stats"
2400 ))]
2401 let _span =
2402 tracing::trace_span!("remove old edges and prepare new children", stats = Empty)
2403 .entered();
2404 #[cfg(feature = "trace_aggregation_update_stats")]
2408 {
2409 let stats = CleanupOldEdgesOperation::run(task_id, old_edges, queue, ctx);
2410 _span.record("stats", tracing::field::debug(stats));
2411 }
2412 #[cfg(not(feature = "trace_aggregation_update_stats"))]
2413 CleanupOldEdgesOperation::run(task_id, old_edges, queue, ctx);
2414 }
2415
2416 Ok(TaskExecutionCompletePrepareResult {
2417 new_children,
2418 is_now_immutable,
2419 #[cfg(feature = "verify_determinism")]
2420 no_output_set,
2421 #[cfg(feature = "task_dirty_cause")]
2422 function_id,
2423 new_output,
2424 output_dependent_tasks,
2425 is_recomputation,
2426 is_session_dependent,
2427 })
2428 }
2429
2430 fn task_execution_completed_invalidate_output_dependent(
2431 &self,
2432 ctx: &mut impl ExecuteContext<'_>,
2433 task_id: TaskId,
2434 #[cfg(feature = "task_dirty_cause")] function_id: Option<FunctionId>,
2435 output_dependent_tasks: SmallVec<[TaskId; 4]>,
2436 ) {
2437 debug_assert!(!output_dependent_tasks.is_empty());
2438
2439 #[cfg(feature = "task_dirty_cause")]
2440 let cause = match function_id {
2441 Some(function) => TaskDirtyCause::OutputChange { function },
2442 None => TaskDirtyCause::RootOutputChange,
2443 };
2444
2445 if output_dependent_tasks.len() > 1 {
2446 ctx.prepare_tasks(
2447 output_dependent_tasks
2448 .iter()
2449 .map(|&id| (id, TaskDataCategory::All)),
2450 "invalidate output dependents",
2451 );
2452 }
2453
2454 fn process_output_dependents(
2455 ctx: &mut impl ExecuteContext<'_>,
2456 task_id: TaskId,
2457 #[cfg(feature = "task_dirty_cause")] cause: &TaskDirtyCause,
2458 dependent_task_id: TaskId,
2459 queue: &mut AggregationUpdateQueue,
2460 ) {
2461 #[cfg(feature = "trace_task_output_dependencies")]
2462 let span = tracing::trace_span!(
2463 "invalidate output dependency",
2464 task = %task_id,
2465 dependent_task = %dependent_task_id,
2466 result = tracing::field::Empty,
2467 )
2468 .entered();
2469 let mut make_stale = true;
2470 let dependent = ctx.task(dependent_task_id, TaskDataCategory::All);
2471 let transient_task_type = dependent.get_transient_task_type();
2472 if transient_task_type.is_some_and(|tt| matches!(&**tt, TransientTask::Once(_))) {
2473 #[cfg(feature = "trace_task_output_dependencies")]
2475 span.record("result", "once task");
2476 return;
2477 }
2478 if dependent.outdated_output_dependencies_contains(&task_id) {
2479 #[cfg(feature = "trace_task_output_dependencies")]
2480 span.record("result", "outdated dependency");
2481 make_stale = false;
2486 } else if !dependent.output_dependencies_contains(&task_id) {
2487 #[cfg(feature = "trace_task_output_dependencies")]
2490 span.record("result", "no backward dependency");
2491 return;
2492 }
2493 make_task_dirty_internal(
2494 dependent,
2495 dependent_task_id,
2496 make_stale,
2497 #[cfg(feature = "task_dirty_cause")]
2498 cause.clone(),
2499 queue,
2500 ctx,
2501 );
2502 #[cfg(feature = "trace_task_output_dependencies")]
2503 span.record("result", "marked dirty");
2504 }
2505
2506 if output_dependent_tasks.len() > DEPENDENT_TASKS_DIRTY_PARALLELIZATION_THRESHOLD {
2507 let chunk_size = good_chunk_size(output_dependent_tasks.len());
2508 let chunks = into_chunks(output_dependent_tasks.to_vec(), chunk_size);
2509 let _ = scope_and_block(chunks.len(), |scope| {
2510 for chunk in chunks {
2511 let child_ctx = ctx.child_context();
2512 #[cfg(feature = "task_dirty_cause")]
2513 let cause = &cause;
2514 scope.spawn(move || {
2515 let mut ctx = child_ctx.create();
2516 let mut queue = AggregationUpdateQueue::new();
2517 for dependent_task_id in chunk {
2518 process_output_dependents(
2519 &mut ctx,
2520 task_id,
2521 #[cfg(feature = "task_dirty_cause")]
2522 cause,
2523 dependent_task_id,
2524 &mut queue,
2525 )
2526 }
2527 queue.execute(&mut ctx);
2528 });
2529 }
2530 });
2531 } else {
2532 let mut queue = AggregationUpdateQueue::new();
2533 for dependent_task_id in output_dependent_tasks {
2534 process_output_dependents(
2535 ctx,
2536 task_id,
2537 #[cfg(feature = "task_dirty_cause")]
2538 &cause,
2539 dependent_task_id,
2540 &mut queue,
2541 );
2542 }
2543 queue.execute(ctx);
2544 }
2545 }
2546
2547 fn task_execution_completed_unfinished_children_dirty(
2548 &self,
2549 ctx: &mut impl ExecuteContext<'_>,
2550 new_children: &FxHashSet<TaskId>,
2551 ) {
2552 debug_assert!(!new_children.is_empty());
2553
2554 let mut queue = AggregationUpdateQueue::new();
2555 ctx.for_each_task_all(
2556 new_children.iter().copied(),
2557 "unfinished children dirty",
2558 |child_task, ctx| {
2559 if !child_task.has_output() {
2560 let child_id = child_task.id();
2561 make_task_dirty_internal(
2562 child_task,
2563 child_id,
2564 false,
2565 #[cfg(feature = "task_dirty_cause")]
2566 TaskDirtyCause::InitialDirty,
2567 &mut queue,
2568 ctx,
2569 );
2570 }
2571 },
2572 );
2573
2574 queue.execute(ctx);
2575 }
2576
2577 fn task_execution_completed_connect(
2578 &self,
2579 ctx: &mut impl ExecuteContext<'_>,
2580 task_id: TaskId,
2581 new_children: FxHashSet<TaskId>,
2582 ) -> Option<TaskPriority> {
2583 debug_assert!(!new_children.is_empty());
2584
2585 let mut task = ctx.task(task_id, TaskDataCategory::All);
2586 let Some(in_progress) = task.get_in_progress() else {
2587 panic!("Task execution completed, but task is not in progress: {task:#?}");
2588 };
2589 if matches!(in_progress, InProgressState::Canceled) {
2590 return None;
2592 }
2593 let InProgressState::InProgress(box InProgressStateInner {
2594 #[cfg(not(feature = "no_fast_stale"))]
2595 stale,
2596 once_task: is_once_task,
2597 ..
2598 }) = in_progress
2599 else {
2600 panic!("Task execution completed, but task is not in progress: {task:#?}");
2601 };
2602
2603 #[cfg(not(feature = "no_fast_stale"))]
2605 if *stale && !is_once_task {
2606 let stale_priority = compute_stale_priority(&task);
2607 let Some(InProgressState::InProgress(box InProgressStateInner { done_event, .. })) =
2608 task.take_in_progress()
2609 else {
2610 unreachable!();
2611 };
2612 let old = task.set_in_progress(InProgressState::Scheduled {
2613 done_event,
2614 reason: TaskExecutionReason::Stale,
2615 });
2616 debug_assert!(old.is_none(), "InProgress already exists");
2617 drop(task);
2618
2619 AggregationUpdateQueue::run(
2622 AggregationUpdateJob::DecreaseActiveCounts {
2623 task_ids: new_children.into_iter().collect(),
2624 },
2625 ctx,
2626 );
2627 return Some(stale_priority);
2628 }
2629
2630 let has_active_count = ctx.should_track_activeness()
2631 && task
2632 .get_activeness()
2633 .is_some_and(|activeness| activeness.active_counter > 0);
2634 connect_children(
2635 ctx,
2636 task_id,
2637 task,
2638 new_children,
2639 has_active_count,
2640 ctx.should_track_activeness(),
2641 );
2642
2643 None
2644 }
2645
2646 #[allow(clippy::type_complexity)]
2647 fn task_execution_completed_finish(
2648 &self,
2649 ctx: &mut impl ExecuteContext<'_>,
2650 task_id: TaskId,
2651 #[cfg(feature = "verify_determinism")] no_output_set: bool,
2652 new_output: Option<OutputValue>,
2653 is_now_immutable: bool,
2654 is_session_dependent: bool,
2655 ) -> (
2656 Option<TaskPriority>,
2657 Option<
2658 auto_hash_map::AutoMap<CellId, InProgressCellState, BuildHasherDefault<FxHasher>, 1>,
2659 >,
2660 ) {
2661 let mut task = ctx.task(task_id, TaskDataCategory::All);
2662 let Some(in_progress) = task.take_in_progress() else {
2663 panic!("Task execution completed, but task is not in progress: {task:#?}");
2664 };
2665 if matches!(in_progress, InProgressState::Canceled) {
2666 return (None, None);
2668 }
2669 let InProgressState::InProgress(box InProgressStateInner {
2670 done_event,
2671 once_task: is_once_task,
2672 stale,
2673 marked_as_completed: _,
2674 new_children,
2675 }) = in_progress
2676 else {
2677 panic!("Task execution completed, but task is not in progress: {task:#?}");
2678 };
2679 debug_assert!(new_children.is_empty());
2680
2681 if stale && !is_once_task {
2683 let stale_priority = compute_stale_priority(&task);
2684 let old = task.set_in_progress(InProgressState::Scheduled {
2685 done_event,
2686 reason: TaskExecutionReason::Stale,
2687 });
2688 debug_assert!(old.is_none(), "InProgress already exists");
2689 return (Some(stale_priority), None);
2690 }
2691
2692 let mut old_content = None;
2694 if let Some(value) = new_output {
2695 old_content = task.set_output(value);
2696 }
2697
2698 if is_now_immutable {
2701 task.set_immutable(true);
2702 }
2703
2704 let in_progress_cells = task.take_in_progress_cells();
2706 if let Some(ref cells) = in_progress_cells {
2707 for state in cells.values() {
2708 state.event.notify(usize::MAX);
2709 }
2710 }
2711
2712 let new_dirtyness = if is_session_dependent {
2714 Some(Dirtyness::SessionDependent)
2715 } else {
2716 None
2717 };
2718 #[cfg(feature = "verify_determinism")]
2719 let dirty_changed = task.get_dirty().cloned() != new_dirtyness;
2720 let data_update = task.update_dirty_state(new_dirtyness);
2721
2722 #[cfg(feature = "verify_determinism")]
2726 let stale_priority: Option<TaskPriority> =
2727 ((dirty_changed || no_output_set) && !task_id.is_transient() && !is_once_task)
2728 .then(TaskPriority::leaf);
2729 #[cfg(not(feature = "verify_determinism"))]
2730 let stale_priority: Option<TaskPriority> = None;
2731 if stale_priority.is_some() {
2732 let old = task.set_in_progress(InProgressState::Scheduled {
2733 done_event,
2734 reason: TaskExecutionReason::Stale,
2735 });
2736 debug_assert!(old.is_none(), "InProgress already exists");
2737 drop(task);
2738 } else {
2739 drop(task);
2740
2741 done_event.notify(usize::MAX);
2743 }
2744
2745 drop(old_content);
2746
2747 if let Some(data_update) = data_update {
2748 AggregationUpdateQueue::run(data_update, ctx);
2749 }
2750
2751 (stale_priority, in_progress_cells)
2753 }
2754
2755 fn task_execution_completed_cleanup(
2756 &self,
2757 ctx: &mut impl ExecuteContext<'_>,
2758 task_id: TaskId,
2759 cell_counters: &AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>,
2760 is_error: bool,
2761 is_recomputation: bool,
2762 ) -> Vec<SharedReference> {
2763 let mut task = ctx.task(task_id, TaskDataCategory::All);
2764 let mut removed_cell_data = Vec::new();
2765 if !is_error || is_recomputation {
2771 let to_remove: Vec<_> = task
2777 .iter_cell_data()
2778 .filter_map(|(cell, _)| {
2779 cell_counters
2780 .get(&cell.type_id())
2781 .is_none_or(|start_index| cell.index() >= *start_index)
2782 .then_some(*cell)
2783 })
2784 .collect();
2785 removed_cell_data.reserve_exact(to_remove.len());
2786 for cell in to_remove {
2787 if let Some(data) =
2788 task.remove_cell_data(&cell, &get_value_type(cell.type_id()).persistence)
2789 {
2790 removed_cell_data.push(data);
2791 }
2792 }
2793 let to_remove_hash: Vec<_> = task
2795 .iter_cell_data_hash()
2796 .filter_map(|(cell, _)| {
2797 cell_counters
2798 .get(&cell.type_id())
2799 .is_none_or(|start_index| cell.index() >= *start_index)
2800 .then_some(*cell)
2801 })
2802 .collect();
2803 for cell in to_remove_hash {
2804 task.remove_cell_data_hash(&cell);
2805 }
2806 }
2807
2808 task.cleanup_after_execution();
2812
2813 drop(task);
2814
2815 removed_cell_data
2817 }
2818
2819 fn log_unrecoverable_persist_error() {
2822 eprintln!(
2823 "Persisting is disabled for this session due to an unrecoverable error. Stopping the \
2824 background persisting process."
2825 );
2826 }
2827
2828 fn run_backend_job<'a>(
2829 &'a self,
2830 job: TurboTasksBackendJob,
2831 turbo_tasks: &'a TurboTasks<TurboTasksBackend>,
2832 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
2833 Box::pin(async move {
2834 match job {
2835 TurboTasksBackendJob::Snapshot => {
2836 debug_assert!(self.should_persist());
2837
2838 static IDLE_TIMEOUT: LazyLock<Duration> = LazyLock::new(|| {
2841 std::env::var("TURBO_ENGINE_SNAPSHOT_IDLE_TIMEOUT_MILLIS")
2842 .ok()
2843 .and_then(|v| v.parse::<u64>().ok())
2844 .map(Duration::from_millis)
2845 .unwrap_or(Duration::from_secs(2))
2846 });
2847
2848 static MIN_SNAPSHOT_ACTIVE_TIME: LazyLock<Duration> = LazyLock::new(|| {
2856 std::env::var("TURBO_ENGINE_SNAPSHOT_MIN_ACTIVE_TIME_MILLIS")
2857 .ok()
2858 .and_then(|v| v.parse::<u64>().ok())
2859 .map(Duration::from_millis)
2860 .unwrap_or(Duration::from_secs(1))
2861 });
2862
2863 let mut last_snapshot = self.start_time;
2864 let mut idle_start_listener = self.idle_start_event.listen();
2865 let mut idle_end_listener = self.idle_end_event.listen();
2866 let mut fresh_idle = true;
2869 let mut is_first = true;
2870 let mut eviction_control = EvictionControl::new(self.options.eviction_mode);
2873 let mut active_time = Stopwatch::new();
2878 'outer: loop {
2879 const FIRST_SNAPSHOT_WAIT: Duration = Duration::from_secs(300);
2880 const SNAPSHOT_INTERVAL: Duration = Duration::from_secs(120);
2881 let idle_timeout = *IDLE_TIMEOUT;
2882 let (time, mut reason) = if is_first {
2883 (FIRST_SNAPSHOT_WAIT, SnapshotReason::InitialSnapshotTimeout)
2884 } else {
2885 (SNAPSHOT_INTERVAL, SnapshotReason::RegularSnapshotInterval)
2886 };
2887
2888 if !turbo_tasks.is_idle() {
2891 active_time.start();
2892 }
2893
2894 let until = last_snapshot + time;
2895 if until > Instant::now() {
2896 let mut stop_listener = self.stopping_event.listen();
2897 if self.stopping.load(Ordering::Acquire) {
2898 return;
2899 }
2900 let mut idle_time = if turbo_tasks.is_idle() && fresh_idle {
2901 Instant::now() + idle_timeout
2902 } else {
2903 far_future()
2904 };
2905 loop {
2906 tokio::select! {
2907 _ = &mut stop_listener => {
2908 return;
2909 },
2910 _ = &mut idle_start_listener => {
2911 active_time.stop();
2913 idle_time = Instant::now() + idle_timeout;
2914 idle_start_listener = self.idle_start_event.listen()
2915 },
2916 _ = &mut idle_end_listener => {
2917 active_time.start();
2919 idle_time = far_future();
2920 idle_end_listener = self.idle_end_event.listen()
2921 },
2922 _ = tokio::time::sleep_until(until) => {
2923 break;
2924 },
2925 _ = tokio::time::sleep_until(idle_time) => {
2926 if turbo_tasks.is_idle() {
2927 reason = SnapshotReason::IdleTimeout;
2928 break;
2929 }
2930 },
2931 }
2932 }
2933 }
2934
2935 if active_time.elapsed() < *MIN_SNAPSHOT_ACTIVE_TIME {
2942 fresh_idle = false;
2949 is_first = false;
2950 last_snapshot = Instant::now();
2951 continue 'outer;
2952 }
2953
2954 let background_span =
2958 tracing::info_span!(parent: None, "background snapshot");
2959 match self.snapshot_and_persist(background_span.id(), reason, turbo_tasks) {
2960 Err(err) => {
2961 eprintln!("Persisting failed: {err:?}");
2964 Self::log_unrecoverable_persist_error();
2965 return;
2966 }
2967 Ok((snapshot_start, new_data)) => {
2968 fresh_idle = new_data;
2970 is_first = false;
2971 last_snapshot = snapshot_start;
2972 active_time.reset();
2980
2981 macro_rules! check_idle_ended {
2985 () => {{
2986 tokio::select! {
2987 biased;
2988 _ = &mut idle_end_listener => {
2989 idle_end_listener = self.idle_end_event.listen();
2990 true
2991 },
2992 _ = std::future::ready(()) => false,
2993 }
2994 }};
2995 }
2996 let ran_eviction = if eviction_control.should_evict(new_data) {
3010 self.storage.evict_after_snapshot(background_span.id());
3015 eviction_control.record_eviction();
3017 true
3018 } else {
3019 false
3020 };
3021
3022 let mut ran_compaction = false;
3029 const MAX_IDLE_COMPACTION_PASSES: usize = 10;
3030 for _ in 0..MAX_IDLE_COMPACTION_PASSES {
3031 if check_idle_ended!() {
3032 continue 'outer;
3033 }
3034 let compact_span = tracing::info_span!(
3038 parent: background_span.id(),
3039 "compact database",
3040 stats = tracing::field::Empty,
3041 )
3042 .entered();
3043 match self.backing_storage.compact() {
3044 Ok(Some(stats)) => {
3045 compact_span.record("stats", display(stats));
3046 ran_compaction = true;
3047 }
3048 Ok(None) => break,
3049 Err(err) => {
3050 eprintln!("Compaction failed: {err:?}");
3051 if self.backing_storage.has_unrecoverable_write_error()
3052 {
3053 Self::log_unrecoverable_persist_error();
3054 return;
3055 }
3056 break;
3057 }
3058 }
3059 }
3060 if !check_idle_ended!()
3065 && (new_data || ran_compaction || ran_eviction)
3066 {
3067 TurboMalloc::collect(true);
3068 }
3069 }
3070 }
3071 }
3072 }
3073 }
3074 })
3075 }
3076
3077 fn try_read_own_task_cell(
3078 &self,
3079 task_id: TaskId,
3080 cell: CellId,
3081 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3082 ) -> Result<TypedCellContent> {
3083 let mut ctx = self.execute_context(turbo_tasks);
3084 let task = ctx.task(task_id, TaskDataCategory::Data);
3085 if let Some(content) = task.get_cell_data(&cell).cloned() {
3086 Ok(CellContent(Some(content)).into_typed(cell.type_id()))
3087 } else {
3088 Ok(CellContent(None).into_typed(cell.type_id()))
3089 }
3090 }
3091
3092 fn read_task_collectibles(
3093 &self,
3094 task_id: TaskId,
3095 collectible_type: TraitTypeId,
3096 reader_id: Option<TaskId>,
3097 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3098 ) -> AutoMap<RawVc, i32, BuildHasherDefault<FxHasher>, 1> {
3099 let mut ctx = self.execute_context(turbo_tasks);
3100 let mut collectibles = AutoMap::default();
3101 {
3102 let mut task = ctx.task(task_id, TaskDataCategory::All);
3103 if task
3104 .get_persistent_task_type()
3105 .is_some_and(|t| !t.native_fn.is_root)
3106 {
3107 drop(task);
3108 panic!(
3109 "Reading collectibles of non-root task {} (reader: {}). The `root` attribute \
3110 is missing on the task.",
3111 self.debug_get_task_description(task_id),
3112 reader_id.map_or_else(
3113 || "unknown".to_string(),
3114 |r| self.debug_get_task_description(r)
3115 )
3116 );
3117 }
3118 for (collectible, count) in task.iter_aggregated_collectibles() {
3119 if *count > 0 && collectible.collectible_type == collectible_type {
3120 *collectibles
3121 .entry(RawVc::task_cell(
3122 collectible.cell.task,
3123 collectible.cell.cell,
3124 ))
3125 .or_insert(0) += 1;
3126 }
3127 }
3128 for (&collectible, &count) in task.iter_collectibles() {
3129 if collectible.collectible_type == collectible_type {
3130 *collectibles
3131 .entry(RawVc::task_cell(
3132 collectible.cell.task,
3133 collectible.cell.cell,
3134 ))
3135 .or_insert(0) += count;
3136 }
3137 }
3138 if let Some(reader_id) = reader_id {
3139 let _ = task.add_collectibles_dependents((collectible_type, reader_id));
3140 }
3141 }
3142 if let Some(reader_id) = reader_id {
3143 let mut reader = ctx.task(reader_id, TaskDataCategory::Data);
3144 let target = CollectiblesRef {
3145 task: task_id,
3146 collectible_type,
3147 };
3148 if !reader.remove_outdated_collectibles_dependencies(&target) {
3149 let _ = reader.add_collectibles_dependencies(target);
3150 }
3151 }
3152 collectibles
3153 }
3154
3155 fn emit_collectible(
3156 &self,
3157 collectible_type: TraitTypeId,
3158 collectible: RawVc,
3159 task_id: TaskId,
3160 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3161 ) {
3162 self.assert_valid_collectible(task_id, collectible);
3163
3164 let Some((collectible_task, cell)) = collectible.as_task_cell() else {
3165 panic!("Collectibles need to be resolved");
3166 };
3167 let cell = CellRef {
3168 task: collectible_task,
3169 cell,
3170 };
3171 operation::UpdateCollectibleOperation::run(
3172 task_id,
3173 CollectibleRef {
3174 collectible_type,
3175 cell,
3176 },
3177 1,
3178 self.execute_context(turbo_tasks),
3179 );
3180 }
3181
3182 fn unemit_collectible(
3183 &self,
3184 collectible_type: TraitTypeId,
3185 collectible: RawVc,
3186 count: u32,
3187 task_id: TaskId,
3188 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3189 ) {
3190 self.assert_valid_collectible(task_id, collectible);
3191
3192 let Some((collectible_task, cell)) = collectible.as_task_cell() else {
3193 panic!("Collectibles need to be resolved");
3194 };
3195 let cell = CellRef {
3196 task: collectible_task,
3197 cell,
3198 };
3199 operation::UpdateCollectibleOperation::run(
3200 task_id,
3201 CollectibleRef {
3202 collectible_type,
3203 cell,
3204 },
3205 -(i32::try_from(count).unwrap()),
3206 self.execute_context(turbo_tasks),
3207 );
3208 }
3209
3210 fn update_task_cell(
3211 &self,
3212 task_id: TaskId,
3213 cell: CellId,
3214 content: CellContent,
3215 updated_key_hashes: Option<SmallVec<[u64; 2]>>,
3216 content_hash: Option<CellHash>,
3217 verification_mode: VerificationMode,
3218 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3219 ) {
3220 operation::UpdateCellOperation::run(
3221 task_id,
3222 cell,
3223 content,
3224 updated_key_hashes,
3225 content_hash,
3226 verification_mode,
3227 self.execute_context(turbo_tasks),
3228 );
3229 }
3230
3231 fn mark_own_task_as_finished(&self, task: TaskId, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
3232 let mut ctx = self.execute_context(turbo_tasks);
3233 let mut task = ctx.task(task, TaskDataCategory::Data);
3234 if let Some(InProgressState::InProgress(box InProgressStateInner {
3235 marked_as_completed,
3236 ..
3237 })) = task.get_in_progress_mut()
3238 {
3239 *marked_as_completed = true;
3240 }
3245 }
3246
3247 fn connect_task(
3248 &self,
3249 task: TaskId,
3250 parent_task: Option<TaskId>,
3251 turbo_tasks: &TurboTasks<TurboTasksBackend>,
3252 ) {
3253 self.assert_not_persistent_calling_transient(parent_task, task, None);
3254 ConnectChildOperation::run(parent_task, task, self.execute_context(turbo_tasks));
3255 }
3256
3257 fn create_transient_task(&self, task_type: TransientTaskType) -> TaskId {
3258 let task_id = self.transient_task_id_factory.get();
3259 {
3260 let mut task = self.storage.access_mut(task_id);
3261 task.init_transient_task(task_id, task_type, self.should_track_activeness());
3262 }
3263 #[cfg(feature = "verify_aggregation_graph")]
3264 self.root_tasks.lock().insert(task_id);
3265 task_id
3266 }
3267
3268 fn dispose_root_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks<TurboTasksBackend>) {
3269 #[cfg(feature = "verify_aggregation_graph")]
3270 self.root_tasks.lock().remove(&task_id);
3271
3272 let mut ctx = self.execute_context(turbo_tasks);
3273 let mut task = ctx.task(task_id, TaskDataCategory::All);
3274 let is_dirty = task.is_dirty();
3275 let has_dirty_containers = task.has_dirty_containers();
3276 if is_dirty.is_some() || has_dirty_containers {
3277 if let Some(activeness_state) = task.get_activeness_mut() {
3278 activeness_state.unset_root_type();
3280 activeness_state.set_active_until_clean();
3281 };
3282 } else if let Some(activeness_state) = task.take_activeness() {
3283 activeness_state.all_clean_event.notify(usize::MAX);
3286 }
3287 }
3288
3289 #[cfg(feature = "verify_aggregation_graph")]
3290 fn verify_aggregation_graph(&self, turbo_tasks: &TurboTasks<TurboTasksBackend>, idle: bool) {
3291 if env::var("TURBO_ENGINE_VERIFY_GRAPH").ok().as_deref() == Some("0") {
3292 return;
3293 }
3294 use std::{collections::VecDeque, env, io::stdout};
3295
3296 use crate::backend::operation::{get_uppers, is_aggregating_node};
3297
3298 let mut ctx = self.execute_context(turbo_tasks);
3299 let root_tasks = self.root_tasks.lock().clone();
3300
3301 for task_id in root_tasks.into_iter() {
3302 let mut queue = VecDeque::new();
3303 let mut visited = FxHashSet::default();
3304 let mut aggregated_nodes = FxHashSet::default();
3305 let mut collectibles = FxHashMap::default();
3306 let root_task_id = task_id;
3307 visited.insert(task_id);
3308 aggregated_nodes.insert(task_id);
3309 queue.push_back(task_id);
3310 let mut counter = 0;
3311 while let Some(task_id) = queue.pop_front() {
3312 counter += 1;
3313 if counter % 100000 == 0 {
3314 println!(
3315 "queue={}, visited={}, aggregated_nodes={}",
3316 queue.len(),
3317 visited.len(),
3318 aggregated_nodes.len()
3319 );
3320 }
3321 let task = ctx.task(task_id, TaskDataCategory::All);
3322 if idle && !self.is_idle.load(Ordering::Relaxed) {
3323 return;
3324 }
3325
3326 let uppers = get_uppers(&task);
3327 if task_id != root_task_id
3328 && !uppers.iter().any(|upper| aggregated_nodes.contains(upper))
3329 {
3330 panic!(
3331 "Task {} {} doesn't report to any root but is reachable from one (uppers: \
3332 {:?})",
3333 task_id,
3334 task.get_task_description(),
3335 uppers
3336 );
3337 }
3338
3339 for (collectible, _) in task.iter_aggregated_collectibles() {
3340 collectibles
3341 .entry(*collectible)
3342 .or_insert_with(|| (false, Vec::new()))
3343 .1
3344 .push(task_id);
3345 }
3346
3347 for (&collectible, &value) in task.iter_collectibles() {
3348 if value > 0 {
3349 if let Some((flag, _)) = collectibles.get_mut(&collectible) {
3350 *flag = true
3351 } else {
3352 panic!(
3353 "Task {} has a collectible {:?} that is not in any upper task",
3354 task_id, collectible
3355 );
3356 }
3357 }
3358 }
3359
3360 let is_dirty = task.has_dirty();
3361 let has_dirty_container = task.has_dirty_containers();
3362 let should_be_in_upper = is_dirty || has_dirty_container;
3363
3364 let aggregation_number = get_aggregation_number(&task);
3365 if is_aggregating_node(aggregation_number) {
3366 aggregated_nodes.insert(task_id);
3367 }
3368 for child_id in task.iter_children() {
3375 if visited.insert(child_id) {
3377 queue.push_back(child_id);
3378 }
3379 }
3380 drop(task);
3381
3382 if should_be_in_upper {
3383 for upper_id in uppers {
3384 let upper = ctx.task(upper_id, TaskDataCategory::All);
3385 let in_upper = upper
3386 .get_aggregated_dirty_containers(&task_id)
3387 .is_some_and(|&dirty| dirty > 0);
3388 if !in_upper {
3389 let containers: Vec<_> = upper
3390 .iter_aggregated_dirty_containers()
3391 .map(|(&k, &v)| (k, v))
3392 .collect();
3393 let upper_task_desc = upper.get_task_description();
3394 drop(upper);
3395 panic!(
3396 "Task {} ({}) is dirty, but is not listed in the upper task {} \
3397 ({})\nThese dirty containers are present:\n{:#?}",
3398 task_id,
3399 ctx.task(task_id, TaskDataCategory::Data)
3400 .get_task_description(),
3401 upper_id,
3402 upper_task_desc,
3403 containers,
3404 );
3405 }
3406 }
3407 }
3408 }
3409
3410 for (collectible, (flag, task_ids)) in collectibles {
3411 if !flag {
3412 use std::io::Write;
3413 let mut stdout = stdout().lock();
3414 writeln!(
3415 stdout,
3416 "{:?} that is not emitted in any child task but in these aggregated \
3417 tasks: {:#?}",
3418 collectible,
3419 task_ids
3420 .iter()
3421 .map(|t| format!(
3422 "{t} {}",
3423 ctx.task(*t, TaskDataCategory::Data).get_task_description()
3424 ))
3425 .collect::<Vec<_>>()
3426 )
3427 .unwrap();
3428
3429 let task_id = collectible.cell.task;
3430 let mut queue = {
3431 let task = ctx.task(task_id, TaskDataCategory::All);
3432 get_uppers(&task)
3433 };
3434 let mut visited = FxHashSet::default();
3435 for &upper_id in queue.iter() {
3436 visited.insert(upper_id);
3437 writeln!(stdout, "{task_id:?} -> {upper_id:?}").unwrap();
3438 }
3439 while let Some(task_id) = queue.pop() {
3440 let task = ctx.task(task_id, TaskDataCategory::All);
3441 let desc = task.get_task_description();
3442 let aggregated_collectible = task
3443 .get_aggregated_collectibles(&collectible)
3444 .copied()
3445 .unwrap_or_default();
3446 let uppers = get_uppers(&task);
3447 drop(task);
3448 writeln!(
3449 stdout,
3450 "upper {task_id} {desc} collectible={aggregated_collectible}"
3451 )
3452 .unwrap();
3453 if task_ids.contains(&task_id) {
3454 writeln!(
3455 stdout,
3456 "Task has an upper connection to an aggregated task that doesn't \
3457 reference it. Upper connection is invalid!"
3458 )
3459 .unwrap();
3460 }
3461 for upper_id in uppers {
3462 writeln!(stdout, "{task_id:?} -> {upper_id:?}").unwrap();
3463 if !visited.contains(&upper_id) {
3464 queue.push(upper_id);
3465 }
3466 }
3467 }
3468 panic!("See stdout for more details");
3469 }
3470 }
3471 }
3472 }
3473
3474 fn assert_not_persistent_calling_transient(
3475 &self,
3476 parent_id: Option<TaskId>,
3477 child_id: TaskId,
3478 cell_id: Option<CellId>,
3479 ) {
3480 if let Some(parent_id) = parent_id
3481 && !parent_id.is_transient()
3482 && child_id.is_transient()
3483 {
3484 self.panic_persistent_calling_transient(
3485 self.debug_get_task_description(parent_id),
3486 self.debug_get_cached_task_type(child_id).as_deref(),
3487 cell_id,
3488 );
3489 }
3490 }
3491
3492 fn panic_persistent_calling_transient(
3493 &self,
3494 parent: String,
3495 child: Option<&CachedTaskType>,
3496 cell_id: Option<CellId>,
3497 ) -> ! {
3498 let transient_reason = if let Some(child) = child {
3499 Cow::Owned(format!(
3500 " The callee is transient because it depends on:\n{}",
3501 self.debug_trace_transient_task(child, cell_id),
3502 ))
3503 } else {
3504 Cow::Borrowed("")
3505 };
3506 panic!(
3507 "Persistent task {} is not allowed to call, read, or connect to transient tasks {}.{}",
3508 parent,
3509 child.map_or("unknown", |t| t.get_name()),
3510 transient_reason,
3511 );
3512 }
3513
3514 fn assert_valid_collectible(&self, task_id: TaskId, collectible: RawVc) {
3515 let Some((col_task_id, col_cell_id)) = collectible.as_task_cell() else {
3517 let task_info = if let Some(col_task_ty) = collectible
3519 .try_get_task_id()
3520 .map(|t| self.debug_get_task_description(t))
3521 {
3522 Cow::Owned(format!(" (return type of {col_task_ty})"))
3523 } else {
3524 Cow::Borrowed("")
3525 };
3526 panic!("Collectible{task_info} must be a ResolvedVc")
3527 };
3528 if col_task_id.is_transient() && !task_id.is_transient() {
3529 let transient_reason =
3530 if let Some(col_task_ty) = self.debug_get_cached_task_type(col_task_id) {
3531 Cow::Owned(format!(
3532 ". The collectible is transient because it depends on:\n{}",
3533 self.debug_trace_transient_task(&col_task_ty, Some(col_cell_id)),
3534 ))
3535 } else {
3536 Cow::Borrowed("")
3537 };
3538 panic!(
3540 "Collectible is transient, transient collectibles cannot be emitted from \
3541 persistent tasks{transient_reason}",
3542 )
3543 }
3544 }
3545}
3546
3547impl Backend for TurboTasksBackend {
3548 fn startup(&self, turbo_tasks: &TurboTasks<Self>) {
3549 self.startup(turbo_tasks);
3550 }
3551
3552 fn stopping(&self, _turbo_tasks: &TurboTasks<Self>) {
3553 self.stopping();
3554 }
3555
3556 fn stop(&self, turbo_tasks: &TurboTasks<Self>) {
3557 self.stop(turbo_tasks);
3558 }
3559
3560 fn idle_start(&self, turbo_tasks: &TurboTasks<Self>) {
3561 self.idle_start(turbo_tasks);
3562 }
3563
3564 fn idle_end(&self, _turbo_tasks: &TurboTasks<Self>) {
3565 self.idle_end();
3566 }
3567
3568 fn get_or_create_task(
3569 &self,
3570 native_fn: &'static NativeFunction,
3571 this: Option<RawVc>,
3572 arg: &mut dyn DynTaskInputsStorage,
3573 parent_task: Option<TaskId>,
3574 persistence: TaskPersistence,
3575 turbo_tasks: &TurboTasks<Self>,
3576 ) -> TaskId {
3577 self.get_or_create_task(native_fn, this, arg, parent_task, persistence, turbo_tasks)
3578 }
3579
3580 fn invalidate_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks<Self>) {
3581 self.invalidate_task(task_id, turbo_tasks);
3582 }
3583
3584 fn invalidate_tasks(&self, tasks: &[TaskId], turbo_tasks: &TurboTasks<Self>) {
3585 self.invalidate_tasks(tasks, turbo_tasks);
3586 }
3587
3588 fn invalidate_tasks_set(
3589 &self,
3590 tasks: &AutoSet<TaskId, BuildHasherDefault<FxHasher>, 2>,
3591 turbo_tasks: &TurboTasks<Self>,
3592 ) {
3593 self.invalidate_tasks_set(tasks, turbo_tasks);
3594 }
3595
3596 fn invalidate_serialization(&self, task_id: TaskId, turbo_tasks: &TurboTasks<Self>) {
3597 self.invalidate_serialization(task_id, turbo_tasks);
3598 }
3599
3600 fn task_execution_canceled(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>) {
3601 self.task_execution_canceled(task, turbo_tasks)
3602 }
3603
3604 fn try_start_task_execution(
3605 &self,
3606 task_id: TaskId,
3607 priority: TaskPriority,
3608 turbo_tasks: &TurboTasks<Self>,
3609 ) -> Option<TaskExecutionSpec<'_>> {
3610 self.try_start_task_execution(task_id, priority, turbo_tasks)
3611 }
3612
3613 fn task_execution_completed(
3614 &self,
3615 task_id: TaskId,
3616 result: Result<RawVc, TurboTasksExecutionError>,
3617 cell_counters: &AutoMap<ValueTypeId, u32, BuildHasherDefault<FxHasher>, 8>,
3618 #[cfg(feature = "verify_determinism")] stateful: bool,
3619 has_invalidator: bool,
3620 turbo_tasks: &TurboTasks<Self>,
3621 ) -> Option<TaskPriority> {
3622 self.task_execution_completed(
3623 task_id,
3624 result,
3625 cell_counters,
3626 #[cfg(feature = "verify_determinism")]
3627 stateful,
3628 has_invalidator,
3629 turbo_tasks,
3630 )
3631 }
3632
3633 type BackendJob = TurboTasksBackendJob;
3634
3635 fn run_backend_job<'a>(
3636 &'a self,
3637 job: Self::BackendJob,
3638 turbo_tasks: &'a TurboTasks<Self>,
3639 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
3640 self.run_backend_job(job, turbo_tasks)
3641 }
3642
3643 fn try_read_task_output(
3644 &self,
3645 task_id: TaskId,
3646 reader: Option<TaskId>,
3647 options: ReadOutputOptions,
3648 turbo_tasks: &TurboTasks<Self>,
3649 ) -> Result<Result<RawVc, EventListener>> {
3650 self.try_read_task_output(task_id, reader, options, turbo_tasks)
3651 }
3652
3653 fn try_read_task_cell(
3654 &self,
3655 task_id: TaskId,
3656 cell: CellId,
3657 reader: Option<TaskId>,
3658 options: ReadCellOptions,
3659 turbo_tasks: &TurboTasks<Self>,
3660 ) -> Result<Result<TypedCellContent, EventListener>> {
3661 self.try_read_task_cell(task_id, reader, cell, options, turbo_tasks)
3662 }
3663
3664 fn try_read_own_task_cell(
3665 &self,
3666 task_id: TaskId,
3667 cell: CellId,
3668 turbo_tasks: &TurboTasks<Self>,
3669 ) -> Result<TypedCellContent> {
3670 self.try_read_own_task_cell(task_id, cell, turbo_tasks)
3671 }
3672
3673 fn read_task_collectibles(
3674 &self,
3675 task_id: TaskId,
3676 collectible_type: TraitTypeId,
3677 reader: Option<TaskId>,
3678 turbo_tasks: &TurboTasks<Self>,
3679 ) -> AutoMap<RawVc, i32, BuildHasherDefault<FxHasher>, 1> {
3680 self.read_task_collectibles(task_id, collectible_type, reader, turbo_tasks)
3681 }
3682
3683 fn emit_collectible(
3684 &self,
3685 collectible_type: TraitTypeId,
3686 collectible: RawVc,
3687 task_id: TaskId,
3688 turbo_tasks: &TurboTasks<Self>,
3689 ) {
3690 self.emit_collectible(collectible_type, collectible, task_id, turbo_tasks)
3691 }
3692
3693 fn unemit_collectible(
3694 &self,
3695 collectible_type: TraitTypeId,
3696 collectible: RawVc,
3697 count: u32,
3698 task_id: TaskId,
3699 turbo_tasks: &TurboTasks<Self>,
3700 ) {
3701 self.unemit_collectible(collectible_type, collectible, count, task_id, turbo_tasks)
3702 }
3703
3704 fn update_task_cell(
3705 &self,
3706 task_id: TaskId,
3707 cell: CellId,
3708 content: CellContent,
3709 updated_key_hashes: Option<SmallVec<[u64; 2]>>,
3710 content_hash: Option<CellHash>,
3711 verification_mode: VerificationMode,
3712 turbo_tasks: &TurboTasks<Self>,
3713 ) {
3714 self.update_task_cell(
3715 task_id,
3716 cell,
3717 content,
3718 updated_key_hashes,
3719 content_hash,
3720 verification_mode,
3721 turbo_tasks,
3722 );
3723 }
3724
3725 fn mark_own_task_as_finished(&self, task_id: TaskId, turbo_tasks: &TurboTasks<Self>) {
3726 self.mark_own_task_as_finished(task_id, turbo_tasks);
3727 }
3728
3729 fn connect_task(
3730 &self,
3731 task: TaskId,
3732 parent_task: Option<TaskId>,
3733 turbo_tasks: &TurboTasks<Self>,
3734 ) {
3735 self.connect_task(task, parent_task, turbo_tasks);
3736 }
3737
3738 fn create_transient_task(
3739 &self,
3740 task_type: TransientTaskType,
3741 _turbo_tasks: &TurboTasks<Self>,
3742 ) -> TaskId {
3743 self.create_transient_task(task_type)
3744 }
3745
3746 fn dispose_root_task(&self, task_id: TaskId, turbo_tasks: &TurboTasks<Self>) {
3747 self.dispose_root_task(task_id, turbo_tasks);
3748 }
3749
3750 fn task_statistics(&self) -> &TaskStatisticsApi {
3751 &self.task_statistics
3752 }
3753
3754 fn is_tracking_dependencies(&self) -> bool {
3755 self.options.dependency_tracking
3756 }
3757
3758 fn get_task_name(&self, task: TaskId, turbo_tasks: &TurboTasks<Self>) -> String {
3759 self.get_task_name(task, turbo_tasks)
3760 }
3761}
3762
3763enum DebugTraceTransientTask {
3764 Cached {
3765 task_name: &'static str,
3766 cell_type_id: Option<ValueTypeId>,
3767 cause_self: Option<Box<DebugTraceTransientTask>>,
3768 cause_args: Vec<DebugTraceTransientTask>,
3769 },
3770 Collapsed {
3772 task_name: &'static str,
3773 cell_type_id: Option<ValueTypeId>,
3774 },
3775 Uncached {
3776 cell_type_id: Option<ValueTypeId>,
3777 },
3778}
3779
3780impl DebugTraceTransientTask {
3781 fn fmt_indented(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
3782 let indent = " ".repeat(level);
3783 f.write_str(&indent)?;
3784
3785 fn fmt_cell_type_id(
3786 f: &mut fmt::Formatter<'_>,
3787 cell_type_id: Option<ValueTypeId>,
3788 ) -> fmt::Result {
3789 if let Some(ty) = cell_type_id {
3790 write!(
3791 f,
3792 " (read cell of type {})",
3793 get_value_type(ty).ty.global_name
3794 )
3795 } else {
3796 Ok(())
3797 }
3798 }
3799
3800 match self {
3802 Self::Cached {
3803 task_name,
3804 cell_type_id,
3805 ..
3806 }
3807 | Self::Collapsed {
3808 task_name,
3809 cell_type_id,
3810 ..
3811 } => {
3812 f.write_str(task_name)?;
3813 fmt_cell_type_id(f, *cell_type_id)?;
3814 if matches!(self, Self::Collapsed { .. }) {
3815 f.write_str(" (collapsed)")?;
3816 }
3817 }
3818 Self::Uncached { cell_type_id } => {
3819 f.write_str("unknown transient task")?;
3820 fmt_cell_type_id(f, *cell_type_id)?;
3821 }
3822 }
3823 f.write_char('\n')?;
3824
3825 if let Self::Cached {
3827 cause_self,
3828 cause_args,
3829 ..
3830 } = self
3831 {
3832 if let Some(c) = cause_self {
3833 writeln!(f, "{indent} self:")?;
3834 c.fmt_indented(f, level + 1)?;
3835 }
3836 if !cause_args.is_empty() {
3837 writeln!(f, "{indent} args:")?;
3838 for c in cause_args {
3839 c.fmt_indented(f, level + 1)?;
3840 }
3841 }
3842 }
3843 Ok(())
3844 }
3845}
3846
3847impl fmt::Display for DebugTraceTransientTask {
3848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3849 self.fmt_indented(f, 0)
3850 }
3851}
3852
3853fn far_future() -> Instant {
3855 Instant::now() + Duration::from_secs(86400 * 365 * 30)
3860}
3861
3862fn encode_task_data(
3874 task: TaskId,
3875 data: &TaskStorage,
3876 category: SpecificTaskDataCategory,
3877 scratch_buffer: &mut TurboBincodeBuffer,
3878) -> Result<TurboBincodeBuffer> {
3879 scratch_buffer.clear();
3880 let mut encoder = new_turbo_bincode_encoder(scratch_buffer);
3881 data.encode(category, &mut encoder)?;
3882
3883 if cfg!(feature = "verify_serialization") {
3884 TaskStorage::new()
3885 .decode(
3886 category,
3887 &mut new_turbo_bincode_decoder(&scratch_buffer[..]),
3888 )
3889 .with_context(|| {
3890 format!(
3891 "expected to be able to decode serialized data for '{category:?}' information \
3892 for {task}"
3893 )
3894 })?;
3895 }
3896 Ok(SmallVec::from_slice(scratch_buffer))
3897}