Skip to main content

turbo_tasks/
invalidation.rs

1use std::{fmt::Display, mem::replace, sync::Arc};
2
3use bincode::{Decode, Encode};
4use indexmap::map::Entry;
5use turbo_dyn_eq_hash::{
6    DynEq, DynHash, impl_eq_for_dyn, impl_hash_for_dyn, impl_partial_eq_for_dyn,
7};
8
9use crate::{
10    FxIndexMap, FxIndexSet, NonLocalValue, OperationValue, TaskId, TurboTasksApi,
11    manager::{current_task_if_available, mark_invalidator},
12    trace::TraceRawVcs,
13    util::StaticOrArc,
14};
15
16/// Get an [`Invalidator`] that can be used to invalidate the current task
17/// based on external events.
18/// Returns `None` if called outside of a task context.
19pub fn get_invalidator() -> Option<Invalidator> {
20    if let Some(task) = current_task_if_available("turbo_tasks::get_invalidator()") {
21        mark_invalidator();
22        Some(Invalidator { task })
23    } else {
24        None
25    }
26}
27
28/// A lightweight handle to invalidate a task. Only stores the task ID.
29/// The caller must provide the `TurboTasksApi` when calling invalidation methods.
30#[derive(Clone, Copy, Hash, PartialEq, Eq, Encode, Decode, Debug)]
31pub struct Invalidator {
32    task: TaskId,
33}
34
35impl Invalidator {
36    pub fn invalidate(self, turbo_tasks: &dyn TurboTasksApi) {
37        turbo_tasks.invalidate(self.task);
38    }
39
40    pub fn invalidate_with_reason<T: InvalidationReason>(
41        self,
42        turbo_tasks: &dyn TurboTasksApi,
43        reason: T,
44    ) {
45        turbo_tasks.invalidate_with_reason(
46            self.task,
47            (Arc::new(reason) as Arc<dyn InvalidationReason>).into(),
48        );
49    }
50}
51
52impl TraceRawVcs for Invalidator {
53    fn trace_raw_vcs(&self, _context: &mut crate::trace::TraceRawVcsContext) {
54        // nothing here
55    }
56}
57
58unsafe impl OperationValue for Invalidator {}
59// Safety: Invalidator only contains a TaskId (a NonZero<u32> wrapper) and does not contain any
60// local Vc references.
61unsafe impl NonLocalValue for Invalidator {}
62
63/// A user-facing reason why a task was invalidated. This should only be used
64/// for invalidation that were triggered by the user.
65///
66/// Reasons are deduplicated, so this need to implement [Eq] and [Hash]
67pub trait InvalidationReason: DynEq + DynHash + Display + Send + Sync + 'static {
68    fn kind(&self) -> Option<StaticOrArc<dyn InvalidationReasonKind>> {
69        None
70    }
71}
72
73/// Invalidation reason kind. This is used to merge multiple reasons of the same
74/// kind into a combined description.
75///
76/// Reason kinds are used a hash map key, so this need to implement [Eq] and
77/// [Hash]
78pub trait InvalidationReasonKind: DynEq + DynHash + Send + Sync + 'static {
79    /// Displays a description of multiple invalidation reasons of the same
80    /// kind. It is only called with two or more reasons.
81    fn fmt(
82        &self,
83        data: &FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
84        f: &mut std::fmt::Formatter<'_>,
85    ) -> std::fmt::Result;
86}
87
88impl_partial_eq_for_dyn!(dyn InvalidationReason);
89impl_eq_for_dyn!(dyn InvalidationReason);
90impl_hash_for_dyn!(dyn InvalidationReason);
91
92impl_partial_eq_for_dyn!(dyn InvalidationReasonKind);
93impl_eq_for_dyn!(dyn InvalidationReasonKind);
94impl_hash_for_dyn!(dyn InvalidationReasonKind);
95
96#[derive(PartialEq, Eq, Hash)]
97enum MapKey {
98    Untyped {
99        unique_tag: usize,
100    },
101    Typed {
102        kind: StaticOrArc<dyn InvalidationReasonKind>,
103    },
104}
105
106enum MapEntry {
107    Single {
108        reason: StaticOrArc<dyn InvalidationReason>,
109    },
110    Multiple {
111        reasons: FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
112    },
113}
114
115/// A set of [InvalidationReason]s. They are automatically deduplicated and
116/// merged by kind during insertion. It implements [Display] to get a readable
117/// representation.
118#[derive(Default)]
119pub struct InvalidationReasonSet {
120    next_unique_tag: usize,
121    // We track typed and untyped entries in the same map to keep the occurrence order of entries.
122    map: FxIndexMap<MapKey, MapEntry>,
123}
124
125impl InvalidationReasonSet {
126    pub(crate) fn insert(&mut self, reason: StaticOrArc<dyn InvalidationReason>) {
127        if let Some(kind) = reason.kind() {
128            let key = MapKey::Typed { kind };
129            match self.map.entry(key) {
130                Entry::Occupied(mut entry) => {
131                    let entry = &mut *entry.get_mut();
132                    match replace(
133                        entry,
134                        MapEntry::Multiple {
135                            reasons: FxIndexSet::default(),
136                        },
137                    ) {
138                        MapEntry::Single {
139                            reason: existing_reason,
140                        } => {
141                            if reason == existing_reason {
142                                *entry = MapEntry::Single {
143                                    reason: existing_reason,
144                                };
145                                return;
146                            }
147                            let mut reasons = FxIndexSet::default();
148                            reasons.insert(existing_reason);
149                            reasons.insert(reason);
150                            *entry = MapEntry::Multiple { reasons };
151                        }
152                        MapEntry::Multiple { mut reasons } => {
153                            reasons.insert(reason);
154                            *entry = MapEntry::Multiple { reasons };
155                        }
156                    }
157                }
158                Entry::Vacant(entry) => {
159                    entry.insert(MapEntry::Single { reason });
160                }
161            }
162        } else {
163            let key = MapKey::Untyped {
164                unique_tag: self.next_unique_tag,
165            };
166            self.next_unique_tag += 1;
167            self.map.insert(key, MapEntry::Single { reason });
168        }
169    }
170
171    pub fn is_empty(&self) -> bool {
172        self.map.is_empty()
173    }
174
175    pub fn len(&self) -> usize {
176        self.map.len()
177    }
178}
179
180impl Display for InvalidationReasonSet {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        let count = self.map.len();
183        for (i, (key, entry)) in self.map.iter().enumerate() {
184            if i > 0 {
185                write!(f, ", ")?;
186                if i == count - 1 {
187                    write!(f, "and ")?;
188                }
189            }
190            match entry {
191                MapEntry::Single { reason } => {
192                    write!(f, "{reason}")?;
193                }
194                MapEntry::Multiple { reasons } => {
195                    let MapKey::Typed { kind } = key else {
196                        unreachable!("An untyped reason can't collect more than one reason");
197                    };
198                    kind.fmt(reasons, f)?
199                }
200            }
201        }
202        Ok(())
203    }
204}