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