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)]
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 NonLocalValue for Invalidator {}
59unsafe impl OperationValue for Invalidator {}
60
61/// A user-facing reason why a task was invalidated. This should only be used
62/// for invalidation that were triggered by the user.
63///
64/// Reasons are deduplicated, so this need to implement [Eq] and [Hash]
65pub trait InvalidationReason: DynEq + DynHash + Display + Send + Sync + 'static {
66    fn kind(&self) -> Option<StaticOrArc<dyn InvalidationReasonKind>> {
67        None
68    }
69}
70
71/// Invalidation reason kind. This is used to merge multiple reasons of the same
72/// kind into a combined description.
73///
74/// Reason kinds are used a hash map key, so this need to implement [Eq] and
75/// [Hash]
76pub trait InvalidationReasonKind: DynEq + DynHash + Send + Sync + 'static {
77    /// Displays a description of multiple invalidation reasons of the same
78    /// kind. It is only called with two or more reasons.
79    fn fmt(
80        &self,
81        data: &FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
82        f: &mut std::fmt::Formatter<'_>,
83    ) -> std::fmt::Result;
84}
85
86impl_partial_eq_for_dyn!(dyn InvalidationReason);
87impl_eq_for_dyn!(dyn InvalidationReason);
88impl_hash_for_dyn!(dyn InvalidationReason);
89
90impl_partial_eq_for_dyn!(dyn InvalidationReasonKind);
91impl_eq_for_dyn!(dyn InvalidationReasonKind);
92impl_hash_for_dyn!(dyn InvalidationReasonKind);
93
94#[derive(PartialEq, Eq, Hash)]
95enum MapKey {
96    Untyped {
97        unique_tag: usize,
98    },
99    Typed {
100        kind: StaticOrArc<dyn InvalidationReasonKind>,
101    },
102}
103
104enum MapEntry {
105    Single {
106        reason: StaticOrArc<dyn InvalidationReason>,
107    },
108    Multiple {
109        reasons: FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
110    },
111}
112
113/// A set of [InvalidationReason]s. They are automatically deduplicated and
114/// merged by kind during insertion. It implements [Display] to get a readable
115/// representation.
116#[derive(Default)]
117pub struct InvalidationReasonSet {
118    next_unique_tag: usize,
119    // We track typed and untyped entries in the same map to keep the occurrence order of entries.
120    map: FxIndexMap<MapKey, MapEntry>,
121}
122
123impl InvalidationReasonSet {
124    pub(crate) fn insert(&mut self, reason: StaticOrArc<dyn InvalidationReason>) {
125        if let Some(kind) = reason.kind() {
126            let key = MapKey::Typed { kind };
127            match self.map.entry(key) {
128                Entry::Occupied(mut entry) => {
129                    let entry = &mut *entry.get_mut();
130                    match replace(
131                        entry,
132                        MapEntry::Multiple {
133                            reasons: FxIndexSet::default(),
134                        },
135                    ) {
136                        MapEntry::Single {
137                            reason: existing_reason,
138                        } => {
139                            if reason == existing_reason {
140                                *entry = MapEntry::Single {
141                                    reason: existing_reason,
142                                };
143                                return;
144                            }
145                            let mut reasons = FxIndexSet::default();
146                            reasons.insert(existing_reason);
147                            reasons.insert(reason);
148                            *entry = MapEntry::Multiple { reasons };
149                        }
150                        MapEntry::Multiple { mut reasons } => {
151                            reasons.insert(reason);
152                            *entry = MapEntry::Multiple { reasons };
153                        }
154                    }
155                }
156                Entry::Vacant(entry) => {
157                    entry.insert(MapEntry::Single { reason });
158                }
159            }
160        } else {
161            let key = MapKey::Untyped {
162                unique_tag: self.next_unique_tag,
163            };
164            self.next_unique_tag += 1;
165            self.map.insert(key, MapEntry::Single { reason });
166        }
167    }
168
169    pub fn is_empty(&self) -> bool {
170        self.map.is_empty()
171    }
172
173    pub fn len(&self) -> usize {
174        self.map.len()
175    }
176}
177
178impl Display for InvalidationReasonSet {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        let count = self.map.len();
181        for (i, (key, entry)) in self.map.iter().enumerate() {
182            if i > 0 {
183                write!(f, ", ")?;
184                if i == count - 1 {
185                    write!(f, "and ")?;
186                }
187            }
188            match entry {
189                MapEntry::Single { reason } => {
190                    write!(f, "{reason}")?;
191                }
192                MapEntry::Multiple { reasons } => {
193                    let MapKey::Typed { kind } = key else {
194                        unreachable!("An untyped reason can't collect more than one reason");
195                    };
196                    kind.fmt(reasons, f)?
197                }
198            }
199        }
200        Ok(())
201    }
202}