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