1use futures::future::{BoxFuture, FutureExt, Shared};
10use std::any::Any;
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13
14tokio::task_local! {
17 pub(crate) static MEMO_STORE: Arc<MemoStore>;
22}
23
24pub type MemoSlot = Shared<BoxFuture<'static, Arc<dyn Any + Send + Sync>>>;
32
33#[derive(Eq, PartialEq, Hash, Clone, Copy)]
39pub struct MemoKey {
40 callsite: std::any::TypeId,
41 args_hash: u64,
42}
43
44impl MemoKey {
45 pub fn new<Marker: 'static, A: std::hash::Hash>(args: &A) -> Self {
51 use std::collections::hash_map::DefaultHasher;
52 use std::hash::Hasher;
53 let mut h = DefaultHasher::new();
54 args.hash(&mut h);
55 Self {
56 callsite: std::any::TypeId::of::<Marker>(),
57 args_hash: h.finish(),
58 }
59 }
60}
61
62pub struct MemoStore {
73 entries: Mutex<HashMap<MemoKey, MemoSlot>>,
74}
75
76impl MemoStore {
77 pub fn new() -> Self {
79 Self {
80 entries: Mutex::new(HashMap::new()),
81 }
82 }
83
84 pub fn get_or_insert(
91 &self,
92 key: MemoKey,
93 make_fut: impl FnOnce() -> BoxFuture<'static, Arc<dyn Any + Send + Sync>>,
94 ) -> MemoSlot {
95 let slot = {
96 let mut map = self.entries.lock().unwrap();
97 map.entry(key)
98 .or_insert_with(|| make_fut().shared())
99 .clone()
100 }; slot
102 }
103}
104
105impl Default for MemoStore {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111pub fn current_memo_store() -> Option<Arc<MemoStore>> {
118 MEMO_STORE.try_with(|s| s.clone()).ok()
119}
120
121#[allow(dead_code)]
124pub(crate) fn memo_scope() -> Arc<MemoStore> {
125 Arc::new(MemoStore::new())
126}
127
128#[allow(dead_code)]
131pub(crate) async fn with_memo_scope<F, R>(store: Arc<MemoStore>, f: F) -> R
132where
133 F: std::future::Future<Output = R>,
134{
135 MEMO_STORE.scope(store, f).await
136}
137
138#[cfg(test)]
141mod macro_tests;
142
143#[cfg(all(test, feature = "projections"))]
146mod render_path_tests;
147
148#[cfg(test)]
151mod tests {
152 use super::*;
153 use std::sync::atomic::{AtomicUsize, Ordering};
154
155 #[tokio::test]
158 async fn hit_body_runs_once_for_same_key() {
159 let counter = Arc::new(AtomicUsize::new(0));
160 let store = Arc::new(MemoStore::new());
161
162 struct Marker;
164 let key = MemoKey::new::<Marker, _>(&42u32);
165
166 let c1 = counter.clone();
167 let slot1 = store.get_or_insert(key, move || {
168 Box::pin(async move {
169 c1.fetch_add(1, Ordering::SeqCst);
170 Arc::new(99u32) as Arc<dyn Any + Send + Sync>
171 })
172 });
173
174 let c2 = counter.clone();
175 let slot2 = store.get_or_insert(key, move || {
176 Box::pin(async move {
178 c2.fetch_add(100, Ordering::SeqCst);
179 Arc::new(0u32) as Arc<dyn Any + Send + Sync>
180 })
181 });
182
183 let a1 = slot1.await;
184 let a2 = slot2.await;
185
186 assert_eq!(*a1.downcast_ref::<u32>().unwrap(), 99);
187 assert_eq!(*a2.downcast_ref::<u32>().unwrap(), 99);
188 assert_eq!(counter.load(Ordering::SeqCst), 1);
190 }
191
192 #[tokio::test]
195 async fn miss_distinct_keys_each_run_body() {
196 let counter = Arc::new(AtomicUsize::new(0));
197 let store = Arc::new(MemoStore::new());
198
199 struct MarkerA;
200 struct MarkerB;
201 let key_a = MemoKey::new::<MarkerA, _>(&1u32);
202 let key_b = MemoKey::new::<MarkerB, _>(&1u32); let ca = counter.clone();
205 let sa = store.get_or_insert(key_a, move || {
206 Box::pin(async move {
207 ca.fetch_add(1, Ordering::SeqCst);
208 Arc::new(10u32) as Arc<dyn Any + Send + Sync>
209 })
210 });
211
212 let cb = counter.clone();
213 let sb = store.get_or_insert(key_b, move || {
214 Box::pin(async move {
215 cb.fetch_add(1, Ordering::SeqCst);
216 Arc::new(20u32) as Arc<dyn Any + Send + Sync>
217 })
218 });
219
220 let va = sa.await;
221 let vb = sb.await;
222
223 assert_eq!(*va.downcast_ref::<u32>().unwrap(), 10);
224 assert_eq!(*vb.downcast_ref::<u32>().unwrap(), 20);
225 assert_eq!(counter.load(Ordering::SeqCst), 2);
227 }
228
229 #[tokio::test]
232 async fn coalesce_concurrent_callers_run_body_once() {
233 let counter = Arc::new(AtomicUsize::new(0));
234 let store = Arc::new(MemoStore::new());
235
236 struct Marker;
237 let key = MemoKey::new::<Marker, _>(&7u32);
238
239 let c1 = counter.clone();
240 let slot1 = store.get_or_insert(key, move || {
241 Box::pin(async move {
242 c1.fetch_add(1, Ordering::SeqCst);
243 Arc::new(42u32) as Arc<dyn Any + Send + Sync>
244 })
245 });
246
247 let slot2 = store.get_or_insert(key, || {
248 Box::pin(async move { Arc::new(0u32) as Arc<dyn Any + Send + Sync> })
250 });
251
252 let (r1, r2) = tokio::join!(slot1, slot2);
254
255 assert_eq!(*r1.downcast_ref::<u32>().unwrap(), 42);
256 assert_eq!(*r2.downcast_ref::<u32>().unwrap(), 42);
257 assert_eq!(counter.load(Ordering::SeqCst), 1);
258 }
259
260 #[test]
263 fn out_of_scope_returns_none_without_panic() {
264 let result = current_memo_store();
266 assert!(result.is_none());
267 }
268
269 #[tokio::test]
272 async fn err_cached_result_returning_future() {
273 let counter = Arc::new(AtomicUsize::new(0));
274 let store = Arc::new(MemoStore::new());
275
276 struct Marker;
277 let key = MemoKey::new::<Marker, _>(&0u32);
278
279 let c1 = counter.clone();
280 let slot1 = store.get_or_insert(key, move || {
281 Box::pin(async move {
282 c1.fetch_add(1, Ordering::SeqCst);
283 let result: Result<u32, String> = Err("boom".to_string());
285 Arc::new(result) as Arc<dyn Any + Send + Sync>
286 })
287 });
288
289 let slot2 = store.get_or_insert(key, || {
290 Box::pin(async move { Arc::new(Ok::<u32, String>(0)) as Arc<dyn Any + Send + Sync> })
291 });
292
293 let a1 = slot1.await;
294 let a2 = slot2.await;
295
296 let r1 = a1.downcast_ref::<Result<u32, String>>().unwrap();
297 let r2 = a2.downcast_ref::<Result<u32, String>>().unwrap();
298
299 assert!(r1.is_err());
300 assert_eq!(r1.as_ref().unwrap_err(), "boom");
301 assert!(r2.is_err());
302 assert_eq!(r2.as_ref().unwrap_err(), "boom");
303 assert_eq!(counter.load(Ordering::SeqCst), 1);
305 }
306
307 #[tokio::test]
310 async fn dropped_store_has_no_prior_entries() {
311 struct Marker;
312 let key = MemoKey::new::<Marker, _>(&5u32);
313
314 let store1 = Arc::new(MemoStore::new());
316 {
317 let slot = store1.get_or_insert(key, || {
318 Box::pin(async move { Arc::new(123u32) as Arc<dyn Any + Send + Sync> })
319 });
320 let _ = slot.await;
321 }
322 {
324 let map = store1.entries.lock().unwrap();
325 assert!(map.contains_key(&key));
326 }
327
328 let store2 = Arc::new(MemoStore::new());
330 {
331 let map = store2.entries.lock().unwrap();
332 assert!(!map.contains_key(&key));
333 }
334 }
335
336 #[tokio::test]
339 async fn with_scope_makes_current_memo_store_return_some() {
340 let store = memo_scope();
341 let result = with_memo_scope(store, async { current_memo_store() }).await;
342 assert!(result.is_some());
343 }
344}