turbo_tasks/
invalidation.rs1use std::{fmt::Display, mem::replace, sync::Arc};
2
3use bincode::{Decode, Encode};
4use indexmap::map::Entry;
5use rustc_hash::FxHashSet;
6use turbo_dyn_eq_hash::{
7 DynEq, DynHash, impl_eq_for_dyn, impl_hash_for_dyn, impl_partial_eq_for_dyn,
8};
9
10use crate::{
11 FxIndexMap, FxIndexSet, NonLocalValue, OperationValue, TaskId, TurboTasksApi,
12 manager::{current_task_if_available, mark_invalidator},
13 util::StaticOrArc,
14};
15
16pub 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#[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 pub fn invalidate_all(
54 invalidators: impl IntoIterator<Item = Invalidator>,
55 turbo_tasks: &dyn TurboTasksApi,
56 ) {
57 let tasks = Self::unique_tasks(invalidators);
58 if !tasks.is_empty() {
59 turbo_tasks.invalidate_many(&tasks);
60 }
61 }
62
63 pub fn invalidate_all_with_reason<T: InvalidationReason>(
65 invalidators: impl IntoIterator<Item = Invalidator>,
66 turbo_tasks: &dyn TurboTasksApi,
67 reason: T,
68 ) {
69 Self::invalidate_all_with_reasons(invalidators, turbo_tasks, [reason]);
70 }
71
72 pub fn invalidate_all_with_reasons<T: InvalidationReason>(
75 invalidators: impl IntoIterator<Item = Invalidator>,
76 turbo_tasks: &dyn TurboTasksApi,
77 reasons: impl IntoIterator<Item = T>,
78 ) {
79 let tasks = Self::unique_tasks(invalidators);
80 if tasks.is_empty() {
81 return;
82 }
83 let reasons = reasons
84 .into_iter()
85 .map(|reason| (Arc::new(reason) as Arc<dyn InvalidationReason>).into())
86 .collect();
87 turbo_tasks.invalidate_many_with_reasons(&tasks, reasons);
88 }
89
90 fn unique_tasks(invalidators: impl IntoIterator<Item = Invalidator>) -> Vec<TaskId> {
92 let invalidators = invalidators.into_iter();
93 let capacity = invalidators.size_hint().0;
94 let mut seen = FxHashSet::with_capacity_and_hasher(capacity, Default::default());
95 let mut tasks = Vec::with_capacity(capacity);
96 for invalidator in invalidators {
97 if seen.insert(invalidator.task) {
98 tasks.push(invalidator.task);
99 }
100 }
101 tasks
102 }
103}
104
105unsafe impl OperationValue for Invalidator {}
106unsafe impl NonLocalValue for Invalidator {}
109
110pub trait InvalidationReason: DynEq + DynHash + Display + Send + Sync + 'static {
115 fn kind(&self) -> Option<StaticOrArc<dyn InvalidationReasonKind>> {
116 None
117 }
118}
119
120pub trait InvalidationReasonKind: DynEq + DynHash + Send + Sync + 'static {
126 fn fmt(
129 &self,
130 data: &FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
131 f: &mut std::fmt::Formatter<'_>,
132 ) -> std::fmt::Result;
133}
134
135impl_partial_eq_for_dyn!(dyn InvalidationReason);
136impl_eq_for_dyn!(dyn InvalidationReason);
137impl_hash_for_dyn!(dyn InvalidationReason);
138
139impl_partial_eq_for_dyn!(dyn InvalidationReasonKind);
140impl_eq_for_dyn!(dyn InvalidationReasonKind);
141impl_hash_for_dyn!(dyn InvalidationReasonKind);
142
143#[derive(PartialEq, Eq, Hash)]
144enum MapKey {
145 Untyped {
146 unique_tag: usize,
147 },
148 Typed {
149 kind: StaticOrArc<dyn InvalidationReasonKind>,
150 },
151}
152
153enum MapEntry {
154 Single {
155 reason: StaticOrArc<dyn InvalidationReason>,
156 },
157 Multiple {
158 reasons: FxIndexSet<StaticOrArc<dyn InvalidationReason>>,
159 },
160}
161
162#[derive(Default)]
166pub struct InvalidationReasonSet {
167 next_unique_tag: usize,
168 map: FxIndexMap<MapKey, MapEntry>,
170}
171
172impl InvalidationReasonSet {
173 pub(crate) fn insert(&mut self, reason: StaticOrArc<dyn InvalidationReason>) {
174 if let Some(kind) = reason.kind() {
175 let key = MapKey::Typed { kind };
176 match self.map.entry(key) {
177 Entry::Occupied(mut entry) => {
178 let entry = &mut *entry.get_mut();
179 match replace(
180 entry,
181 MapEntry::Multiple {
182 reasons: FxIndexSet::default(),
183 },
184 ) {
185 MapEntry::Single {
186 reason: existing_reason,
187 } => {
188 if reason == existing_reason {
189 *entry = MapEntry::Single {
190 reason: existing_reason,
191 };
192 return;
193 }
194 let mut reasons = FxIndexSet::default();
195 reasons.insert(existing_reason);
196 reasons.insert(reason);
197 *entry = MapEntry::Multiple { reasons };
198 }
199 MapEntry::Multiple { mut reasons } => {
200 reasons.insert(reason);
201 *entry = MapEntry::Multiple { reasons };
202 }
203 }
204 }
205 Entry::Vacant(entry) => {
206 entry.insert(MapEntry::Single { reason });
207 }
208 }
209 } else {
210 let key = MapKey::Untyped {
211 unique_tag: self.next_unique_tag,
212 };
213 self.next_unique_tag += 1;
214 self.map.insert(key, MapEntry::Single { reason });
215 }
216 }
217
218 pub fn is_empty(&self) -> bool {
219 self.map.is_empty()
220 }
221
222 pub fn len(&self) -> usize {
223 self.map.len()
224 }
225}
226
227impl Display for InvalidationReasonSet {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 let count = self.map.len();
230 for (i, (key, entry)) in self.map.iter().enumerate() {
231 if i > 0 {
232 write!(f, ", ")?;
233 if i == count - 1 {
234 write!(f, "and ")?;
235 }
236 }
237 match entry {
238 MapEntry::Single { reason } => {
239 write!(f, "{reason}")?;
240 }
241 MapEntry::Multiple { reasons } => {
242 let MapKey::Typed { kind } = key else {
243 unreachable!("An untyped reason can't collect more than one reason");
244 };
245 kind.fmt(reasons, f)?
246 }
247 }
248 }
249 Ok(())
250 }
251}