1#![doc = include_str!("../README.md")]
2#![warn(clippy::pedantic)]
3#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
4
5mod cache;
6
7#[cfg(not(feature = "boxed-trait"))]
8use std::future::Future;
9use std::{
10 any::{Any, TypeId},
11 borrow::Cow,
12 collections::{HashMap, HashSet},
13 hash::Hash,
14 sync::{
15 Arc, Mutex,
16 atomic::{AtomicBool, Ordering},
17 },
18 time::Duration,
19};
20
21pub use cache::{CacheFactory, CacheStorage, HashMapCache, LruCache, NoCache};
22use fnv::FnvHashMap;
23use futures_channel::oneshot;
24use futures_timer::Delay;
25use futures_util::future::BoxFuture;
26#[cfg(feature = "tracing")]
27use tracing::{Instrument, info_span, instrument};
28#[cfg(feature = "tracing")]
29use tracinglib as tracing;
30
31#[allow(clippy::type_complexity)]
32struct ResSender<
33 K: Send + Sync + Hash + Eq + Clone + 'static,
34 V: Send + Sync + Clone + 'static,
35 T: Loader<K, V>,
36> {
37 use_cache_values: HashMap<K, V>,
38 tx: oneshot::Sender<Result<HashMap<K, V>, T::Error>>,
39}
40
41struct Requests<
42 K: Send + Sync + Hash + Eq + Clone + 'static,
43 V: Send + Sync + Clone + 'static,
44 T: Loader<K, V>,
45> {
46 keys: HashSet<K>,
47 pending: Vec<(HashSet<K>, ResSender<K, V, T>)>,
48 cache_storage: Box<dyn CacheStorage<Key = K, Value = V>>,
49 disable_cache: bool,
50}
51
52type KeysAndSender<K, V, T> = (HashSet<K>, Vec<(HashSet<K>, ResSender<K, V, T>)>);
53
54impl<
55 K: Send + Sync + Hash + Eq + Clone + 'static,
56 V: Send + Sync + Clone + 'static,
57 T: Loader<K, V>,
58> Requests<K, V, T>
59{
60 fn new<C: CacheFactory>(cache_factory: &C) -> Self {
61 Self {
62 keys: HashSet::default(),
63 pending: Vec::new(),
64 cache_storage: cache_factory.create::<K, V>(),
65 disable_cache: false,
66 }
67 }
68
69 fn take(&mut self) -> KeysAndSender<K, V, T> {
70 (
71 std::mem::take(&mut self.keys),
72 std::mem::take(&mut self.pending),
73 )
74 }
75}
76
77#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
79pub trait Loader<K: Send + Sync + Hash + Eq + Clone + 'static, V: Send + Sync + Clone + 'static>:
80 Send + Sync + 'static
81{
82 type Error: Send + Clone + 'static;
84
85 #[cfg(feature = "boxed-trait")]
87 async fn load(&self, keys: &[K]) -> Result<HashMap<K, V>, Self::Error>;
88
89 #[cfg(not(feature = "boxed-trait"))]
91 fn load(&self, keys: &[K]) -> impl Future<Output = Result<HashMap<K, V>, Self::Error>> + Send;
92}
93
94struct DataLoaderInner<T> {
95 requests: Mutex<FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>>,
96 loader: T,
97}
98
99impl<T> DataLoaderInner<T> {
100 #[cfg_attr(feature = "tracing", instrument(skip_all))]
101 async fn do_load<K, V>(&self, disable_cache: bool, (keys, senders): KeysAndSender<K, V, T>)
102 where
103 K: Send + Sync + Hash + Eq + Clone + 'static,
104 V: Send + Sync + Clone + 'static,
105 T: Loader<K, V>,
106 {
107 let tid = TypeId::of::<(K, V)>();
108 let keys = keys.into_iter().collect::<Vec<_>>();
109
110 match self.loader.load(&keys).await {
111 Ok(values) => {
112 let mut request = self.requests.lock().unwrap();
114 let typed_requests = request
115 .get_mut(&tid)
116 .unwrap()
117 .downcast_mut::<Requests<K, V, T>>()
118 .unwrap();
119 let disable_cache = typed_requests.disable_cache || disable_cache;
120 if !disable_cache {
121 for (key, value) in &values {
122 typed_requests
123 .cache_storage
124 .insert(Cow::Borrowed(key), Cow::Borrowed(value));
125 }
126 }
127
128 for (keys, sender) in senders {
130 let mut res = HashMap::new();
131 res.extend(sender.use_cache_values);
132 for key in &keys {
133 res.extend(values.get(key).map(|value| (key.clone(), value.clone())));
134 }
135 sender.tx.send(Ok(res)).ok();
136 }
137 }
138 Err(err) => {
139 for (_, sender) in senders {
140 sender.tx.send(Err(err.clone())).ok();
141 }
142 }
143 }
144 }
145}
146
147pub struct DataLoader<T, C = NoCache> {
151 inner: Arc<DataLoaderInner<T>>,
152 cache_factory: C,
153 delay: Duration,
154 max_batch_size: usize,
155 disable_cache: AtomicBool,
156 spawner: Box<dyn Fn(BoxFuture<'static, ()>) + Send + Sync>,
157}
158
159impl<T> DataLoader<T, NoCache> {
160 pub fn new<S, R>(loader: T, spawner: S) -> Self
162 where
163 S: Fn(BoxFuture<'static, ()>) -> R + Send + Sync + 'static,
164 {
165 Self {
166 inner: Arc::new(DataLoaderInner {
167 requests: Mutex::new(HashMap::default()),
168 loader,
169 }),
170 cache_factory: NoCache,
171 delay: Duration::from_millis(1),
172 max_batch_size: 1000,
173 disable_cache: false.into(),
174 spawner: Box::new(move |fut| {
175 spawner(fut);
176 }),
177 }
178 }
179}
180
181impl<T, C: CacheFactory> DataLoader<T, C> {
182 pub fn with_cache<S, R>(loader: T, spawner: S, cache_factory: C) -> Self
184 where
185 S: Fn(BoxFuture<'static, ()>) -> R + Send + Sync + 'static,
186 {
187 Self {
188 inner: Arc::new(DataLoaderInner {
189 requests: Mutex::new(HashMap::default()),
190 loader,
191 }),
192 cache_factory,
193 delay: Duration::from_millis(1),
194 max_batch_size: 1000,
195 disable_cache: false.into(),
196 spawner: Box::new(move |fut| {
197 spawner(fut);
198 }),
199 }
200 }
201
202 #[must_use]
204 pub fn delay(self, delay: Duration) -> Self {
205 Self { delay, ..self }
206 }
207
208 #[must_use]
214 pub fn max_batch_size(self, max_batch_size: usize) -> Self {
215 Self {
216 max_batch_size,
217 ..self
218 }
219 }
220
221 #[inline]
223 pub fn loader(&self) -> &T {
224 &self.inner.loader
225 }
226
227 pub fn enable_all_cache(&self, enable: bool) {
229 self.disable_cache.store(!enable, Ordering::SeqCst);
230 }
231
232 pub fn enable_cache<K, V>(&self, enable: bool)
234 where
235 K: Send + Sync + Hash + Eq + Clone + 'static,
236 V: Send + Sync + Clone + 'static,
237 T: Loader<K, V>,
238 {
239 let tid = TypeId::of::<(K, V)>();
240 let mut requests = self.inner.requests.lock().unwrap();
241 let typed_requests = requests
242 .get_mut(&tid)
243 .unwrap()
244 .downcast_mut::<Requests<K, V, T>>()
245 .unwrap();
246 typed_requests.disable_cache = !enable;
247 }
248
249 #[cfg_attr(feature = "tracing", instrument(skip_all))]
251 pub async fn load_one<K, V>(&self, key: K) -> Result<Option<V>, T::Error>
252 where
253 K: Send + Sync + Hash + Eq + Clone + 'static,
254 V: Send + Sync + Clone + 'static,
255 T: Loader<K, V>,
256 {
257 let mut values = self.load_many(std::iter::once(key.clone())).await?;
258 Ok(values.remove(&key))
259 }
260
261 #[cfg_attr(feature = "tracing", instrument(skip_all))]
263 pub async fn load_many<K, V, I>(&self, keys: I) -> Result<HashMap<K, V>, T::Error>
264 where
265 K: Send + Sync + Hash + Eq + Clone + 'static,
266 I: IntoIterator<Item = K>,
267 V: Send + Sync + Clone + 'static,
268 T: Loader<K, V>,
269 {
270 enum Action<
271 K: Send + Sync + Hash + Eq + Clone + 'static,
272 V: Send + Sync + Clone + 'static,
273 T: Loader<K, V>,
274 > {
275 ImmediateLoad(KeysAndSender<K, V, T>),
276 StartFetch,
277 Delay,
278 }
279
280 let tid = TypeId::of::<(K, V)>();
281
282 let (action, rx) = {
283 let mut requests = self.inner.requests.lock().unwrap();
284 let typed_requests = requests
285 .entry(tid)
286 .or_insert_with(|| Box::new(Requests::<K, V, T>::new(&self.cache_factory)))
287 .downcast_mut::<Requests<K, V, T>>()
288 .unwrap();
289 let prev_count = typed_requests.keys.len();
290 let mut keys_set = HashSet::new();
291 let mut use_cache_values = HashMap::new();
292
293 if typed_requests.disable_cache || self.disable_cache.load(Ordering::SeqCst) {
294 keys_set = keys.into_iter().collect();
295 } else {
296 for key in keys {
297 if let Some(value) = typed_requests.cache_storage.get(&key) {
298 use_cache_values.insert(key.clone(), value.clone());
300 } else {
301 keys_set.insert(key);
302 }
303 }
304 }
305
306 if !use_cache_values.is_empty() && keys_set.is_empty() {
307 return Ok(use_cache_values);
308 } else if use_cache_values.is_empty() && keys_set.is_empty() {
309 return Ok(HashMap::default());
310 }
311
312 typed_requests.keys.extend(keys_set.clone());
313 let (tx, rx) = oneshot::channel();
314 typed_requests.pending.push((
315 keys_set,
316 ResSender {
317 use_cache_values,
318 tx,
319 },
320 ));
321
322 if typed_requests.keys.len() >= self.max_batch_size {
323 (Action::ImmediateLoad(typed_requests.take()), rx)
324 } else {
325 (
326 if !typed_requests.keys.is_empty() && prev_count == 0 {
327 Action::StartFetch
328 } else {
329 Action::Delay
330 },
331 rx,
332 )
333 }
334 };
335
336 match action {
337 Action::ImmediateLoad(keys) => {
338 let inner = self.inner.clone();
339 let disable_cache = self.disable_cache.load(Ordering::SeqCst);
340 let task = async move { inner.do_load(disable_cache, keys).await };
341 #[cfg(feature = "tracing")]
342 let task = task
343 .instrument(info_span!("immediate_load"))
344 .in_current_span();
345
346 (self.spawner)(Box::pin(task));
347 }
348 Action::StartFetch => {
349 let inner = self.inner.clone();
350 let disable_cache = self.disable_cache.load(Ordering::SeqCst);
351 let delay = self.delay;
352
353 let task = async move {
354 Delay::new(delay).await;
355
356 let keys = {
357 let mut request = inner.requests.lock().unwrap();
358 let typed_requests = request
359 .get_mut(&tid)
360 .unwrap()
361 .downcast_mut::<Requests<K, V, T>>()
362 .unwrap();
363 typed_requests.take()
364 };
365
366 if !keys.0.is_empty() {
367 inner.do_load(disable_cache, keys).await;
368 }
369 };
370 #[cfg(feature = "tracing")]
371 let task = task.instrument(info_span!("start_fetch")).in_current_span();
372 (self.spawner)(Box::pin(task));
373 }
374 Action::Delay => {}
375 }
376
377 rx.await.unwrap()
378 }
379
380 #[cfg_attr(feature = "tracing", instrument(skip_all))]
385 #[allow(clippy::unused_async)]
386 pub async fn feed_many<K, V, I>(&self, values: I)
387 where
388 K: Send + Sync + Hash + Eq + Clone + 'static,
389 I: IntoIterator<Item = (K, V)>,
390 V: Send + Sync + Clone + 'static,
391 T: Loader<K, V>,
392 {
393 let tid = TypeId::of::<(K, V)>();
394 let mut requests = self.inner.requests.lock().unwrap();
395 let typed_requests = requests
396 .entry(tid)
397 .or_insert_with(|| Box::new(Requests::<K, V, T>::new(&self.cache_factory)))
398 .downcast_mut::<Requests<K, V, T>>()
399 .unwrap();
400 for (key, value) in values {
401 typed_requests
402 .cache_storage
403 .insert(Cow::Owned(key), Cow::Owned(value));
404 }
405 }
406
407 #[cfg_attr(feature = "tracing", instrument(skip_all))]
412 pub async fn feed_one<K, V>(&self, key: K, value: V)
413 where
414 K: Send + Sync + Hash + Eq + Clone + 'static,
415 V: Send + Sync + Clone + 'static,
416 T: Loader<K, V>,
417 {
418 self.feed_many(std::iter::once((key, value))).await;
419 }
420
421 #[cfg_attr(feature = "tracing", instrument(skip_all))]
426 pub fn clear<K, V>(&self)
427 where
428 K: Send + Sync + Hash + Eq + Clone + 'static,
429 V: Send + Sync + Clone + 'static,
430 T: Loader<K, V>,
431 {
432 let tid = TypeId::of::<(K, V)>();
433 let mut requests = self.inner.requests.lock().unwrap();
434 let typed_requests = requests
435 .entry(tid)
436 .or_insert_with(|| Box::new(Requests::<K, V, T>::new(&self.cache_factory)))
437 .downcast_mut::<Requests<K, V, T>>()
438 .unwrap();
439 typed_requests.cache_storage.clear();
440 }
441
442 pub fn get_cached_values<K, V>(&self) -> HashMap<K, V>
444 where
445 K: Send + Sync + Hash + Eq + Clone + 'static,
446 V: Send + Sync + Clone + 'static,
447 T: Loader<K, V>,
448 {
449 let tid = TypeId::of::<(K, V)>();
450 let requests = self.inner.requests.lock().unwrap();
451 match requests.get(&tid) {
452 None => HashMap::new(),
453 Some(requests) => {
454 let typed_requests = requests.downcast_ref::<Requests<K, V, T>>().unwrap();
455 typed_requests
456 .cache_storage
457 .iter()
458 .map(|(k, v)| (k.clone(), v.clone()))
459 .collect()
460 }
461 }
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use fnv::FnvBuildHasher;
468 use tokio::join;
469
470 use super::*;
471
472 struct MyLoader;
473
474 #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
475 impl Loader<i32, i32> for MyLoader {
476 type Error = ();
477
478 async fn load(&self, keys: &[i32]) -> Result<HashMap<i32, i32>, Self::Error> {
479 assert!(keys.len() <= 10);
480 Ok(keys.iter().copied().map(|k| (k, k)).collect())
481 }
482 }
483
484 #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
485 impl Loader<i64, i64> for MyLoader {
486 type Error = ();
487
488 async fn load(&self, keys: &[i64]) -> Result<HashMap<i64, i64>, Self::Error> {
489 assert!(keys.len() <= 10);
490 Ok(keys.iter().copied().map(|k| (k, k)).collect())
491 }
492 }
493
494 #[tokio::test]
495 async fn test_dataloader() {
496 let loader = Arc::new(DataLoader::new(MyLoader, tokio::spawn).max_batch_size(10));
497 assert_eq!(
498 futures_util::future::try_join_all((0..100i32).map({
499 let loader = loader.clone();
500 move |n| {
501 let loader = loader.clone();
502 async move { loader.load_one(n).await }
503 }
504 }))
505 .await
506 .unwrap(),
507 (0..100).map(Option::Some).collect::<Vec<_>>()
508 );
509
510 assert_eq!(
511 futures_util::future::try_join_all((0..100i64).map({
512 let loader = loader.clone();
513 move |n| {
514 let loader = loader.clone();
515 async move { loader.load_one(n).await }
516 }
517 }))
518 .await
519 .unwrap(),
520 (0..100).map(Option::Some).collect::<Vec<_>>()
521 );
522 }
523
524 #[tokio::test]
525 async fn test_duplicate_keys() {
526 let loader = Arc::new(DataLoader::new(MyLoader, tokio::spawn).max_batch_size(10));
527 assert_eq!(
528 futures_util::future::try_join_all([1, 3, 5, 1, 7, 8, 3, 7].iter().copied().map({
529 let loader = loader.clone();
530 move |n| {
531 let loader = loader.clone();
532 async move { loader.load_one(n).await }
533 }
534 }))
535 .await
536 .unwrap(),
537 [1, 3, 5, 1, 7, 8, 3, 7]
538 .iter()
539 .copied()
540 .map(Option::Some)
541 .collect::<Vec<_>>()
542 );
543 }
544
545 #[tokio::test]
546 async fn test_dataloader_load_empty() {
547 let loader = DataLoader::new(MyLoader, tokio::spawn);
548 assert!(
549 loader
550 .load_many::<i32, _, _>(vec![])
551 .await
552 .unwrap()
553 .is_empty()
554 );
555 }
556
557 #[tokio::test]
558 async fn test_dataloader_with_cache() {
559 let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default());
560 loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await;
561
562 assert_eq!(
564 loader.load_many(vec![1, 2, 3]).await.unwrap(),
565 vec![(1, 10), (2, 20), (3, 30)].into_iter().collect()
566 );
567
568 assert_eq!(
570 loader.load_many(vec![1, 5, 6]).await.unwrap(),
571 vec![(1, 10), (5, 5), (6, 6)].into_iter().collect()
572 );
573
574 assert_eq!(
576 loader.load_many(vec![8, 9, 10]).await.unwrap(),
577 vec![(8, 8), (9, 9), (10, 10)].into_iter().collect()
578 );
579
580 loader.clear::<i32, _>();
582 assert_eq!(
583 loader.load_many(vec![1, 2, 3]).await.unwrap(),
584 vec![(1, 1), (2, 2), (3, 3)].into_iter().collect()
585 );
586 }
587
588 #[tokio::test]
589 async fn test_dataloader_with_cache_hashmap_fnv() {
590 let loader = DataLoader::with_cache(
591 MyLoader,
592 tokio::spawn,
593 HashMapCache::<FnvBuildHasher>::new(),
594 );
595 loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await;
596
597 assert_eq!(
599 loader.load_many(vec![1, 2, 3]).await.unwrap(),
600 vec![(1, 10), (2, 20), (3, 30)].into_iter().collect()
601 );
602
603 assert_eq!(
605 loader.load_many(vec![1, 5, 6]).await.unwrap(),
606 vec![(1, 10), (5, 5), (6, 6)].into_iter().collect()
607 );
608
609 assert_eq!(
611 loader.load_many(vec![8, 9, 10]).await.unwrap(),
612 vec![(8, 8), (9, 9), (10, 10)].into_iter().collect()
613 );
614
615 loader.clear::<i32, _>();
617 assert_eq!(
618 loader.load_many(vec![1, 2, 3]).await.unwrap(),
619 vec![(1, 1), (2, 2), (3, 3)].into_iter().collect()
620 );
621 }
622
623 #[tokio::test]
624 async fn test_dataloader_disable_all_cache() {
625 let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default());
626 loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await;
627
628 loader.enable_all_cache(false);
630 assert_eq!(
631 loader.load_many(vec![1, 2, 3]).await.unwrap(),
632 vec![(1, 1), (2, 2), (3, 3)].into_iter().collect()
633 );
634
635 loader.enable_all_cache(true);
637 assert_eq!(
638 loader.load_many(vec![1, 2, 3]).await.unwrap(),
639 vec![(1, 10), (2, 20), (3, 30)].into_iter().collect()
640 );
641 }
642
643 #[tokio::test]
644 async fn test_dataloader_disable_cache() {
645 let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default());
646 loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await;
647
648 loader.enable_cache::<i32, _>(false);
650 assert_eq!(
651 loader.load_many(vec![1, 2, 3]).await.unwrap(),
652 vec![(1, 1), (2, 2), (3, 3)].into_iter().collect()
653 );
654
655 loader.enable_cache::<i32, _>(true);
657 assert_eq!(
658 loader.load_many(vec![1, 2, 3]).await.unwrap(),
659 vec![(1, 10), (2, 20), (3, 30)].into_iter().collect()
660 );
661 }
662
663 #[tokio::test]
664 async fn test_dataloader_dead_lock() {
665 struct MyDelayLoader;
666
667 #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
668 impl Loader<i32, i32> for MyDelayLoader {
669 type Error = ();
670
671 async fn load(&self, keys: &[i32]) -> Result<HashMap<i32, i32>, Self::Error> {
672 tokio::time::sleep(Duration::from_secs(1)).await;
673 Ok(keys.iter().copied().map(|k| (k, k)).collect())
674 }
675 }
676
677 let loader = Arc::new(
678 DataLoader::with_cache(MyDelayLoader, tokio::spawn, NoCache)
679 .delay(Duration::from_secs(1)),
680 );
681 let handle = tokio::spawn({
682 let loader = loader.clone();
683 async move {
684 loader.load_many(vec![1, 2, 3]).await.unwrap();
685 }
686 });
687
688 tokio::time::sleep(Duration::from_millis(500)).await;
689 handle.abort();
690 loader.load_many(vec![4, 5, 6]).await.unwrap();
691 }
692
693 #[tokio::test]
694 async fn test_load_different_keys() {
695 struct MyDelayLoader;
696 #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
697 impl Loader<i32, i32> for MyDelayLoader {
698 type Error = ();
699
700 async fn load(&self, keys: &[i32]) -> Result<HashMap<i32, i32>, Self::Error> {
701 tokio::time::sleep(Duration::from_secs(1)).await;
702 Ok(keys.iter().copied().map(|k| (k, k)).collect())
703 }
704 }
705 #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
706 impl Loader<i32, u32> for MyDelayLoader {
707 type Error = ();
708
709 async fn load(&self, keys: &[i32]) -> Result<HashMap<i32, u32>, Self::Error> {
710 tokio::time::sleep(Duration::from_secs(1)).await;
711 Ok(keys
712 .iter()
713 .copied()
714 .map(|k| (k, k.try_into().unwrap()))
715 .collect())
716 }
717 }
718
719 let loader = DataLoader::new(MyDelayLoader, tokio::spawn).delay(Duration::from_secs(1));
720
721 let x = join!(loader.load_one(1), loader.load_one(1));
722 let x1: u32 = x.0.unwrap().unwrap();
723 let x2: i32 = x.1.unwrap().unwrap();
724 assert_eq!(x2, 1i32);
725 assert_eq!(x1, 1u32);
726 }
727}