simple_async_cache_rs/lib.rs
1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::hash::Hash;
4use std::sync::Arc;
5use std::time::{SystemTime, UNIX_EPOCH};
6use tokio::sync::Mutex;
7
8struct InnerCacheLayer<K, V> {
9 pub map: HashMap<K, Arc<Mutex<Option<V>>>>,
10 pub expiration_map: HashMap<u64, Vec<K>>,
11}
12
13pub struct AsyncCacheStore<K, V> {
14 inner: Mutex<InnerCacheLayer<K, V>>,
15}
16
17impl<K: 'static + Eq + Hash + Debug + Sync + Send + Clone, V: 'static + Sync + Send>
18 AsyncCacheStore<K, V>
19{
20 /// Construct a new [`AsyncCacheStore`] instance.
21 /// Note: expire is the number of seconds for the cached value to expire.
22 ///
23 /// **Panic**:
24 /// If you set expire to less than 3 seconds.
25 /// This limitaion exists because we expire value only every seconds, meaning there could be desynchronizations with a TTL lower than 3.
26 ///
27 /// ```rust
28 /// use simple_async_cache_rs::AsyncCacheStore;
29 ///
30 /// #[tokio::main]
31 /// async fn main() {
32 /// let cache_ttl = 60; // number of seconds before the cached item is expired.
33 /// let store: AsyncCacheStore<u64, String> = AsyncCacheStore::new();
34 /// }
35 /// ```
36 pub fn new() -> Arc<Self> {
37 let a = Arc::new(AsyncCacheStore {
38 inner: Mutex::new(InnerCacheLayer {
39 map: HashMap::new(),
40 expiration_map: HashMap::new(),
41 }),
42 });
43 let cloned = a.clone();
44 let first_refresh = SystemTime::now()
45 .duration_since(UNIX_EPOCH)
46 .unwrap()
47 .as_secs()
48 - 1;
49
50 let mut timer = tokio::time::interval(tokio::time::Duration::from_secs(1));
51
52 tokio::spawn(async move {
53 let mut n = first_refresh;
54 loop {
55 timer.tick().await;
56 let mut lock = cloned.inner.lock().await;
57 match lock.expiration_map.remove(&n) {
58 Some(expired) => {
59 for item in expired {
60 lock.map.remove(&item);
61 }
62 }
63 None => {}
64 }
65 n += 1;
66 }
67 });
68 a
69 }
70
71 /// Fetch the key from the cache or creates with the supplied TTL in seconds.
72 /// Returns an [`std::sync::Arc`] to the [`tokio::sync::Mutex`] for the key containing an Option.
73 /// The [`tokio::sync::Mutex`] prevents DogPile effect.
74 ///
75 /// ```rust
76 /// let cache = store.get("key_1".to_string(), 10).await;
77 /// let mut result = cache.lock().await;
78 /// match &mut *result {
79 /// Some(val) => {
80 /// // You can get here the cached value for key_1 if it is already available.
81 /// }
82 /// None => {
83 /// // There is no existing entry for key_1, you can do any expansive task to get the value and store it then.
84 /// *result = Some("This is the content for key_1.".to_string());
85 /// }
86 /// }
87 /// ```
88 pub async fn get(&self, key: K, ttl: u64) -> Arc<Mutex<Option<V>>> {
89 let mut lock = self.inner.lock().await;
90 match lock.map.get(&key) {
91 Some(v) => v.clone(),
92 None => {
93 let v = Arc::new(Mutex::new(None));
94 lock.map.insert(key.clone(), v.clone());
95 lock.expiration_map
96 .entry(
97 SystemTime::now()
98 .duration_since(UNIX_EPOCH)
99 .unwrap()
100 .as_secs()
101 + ttl,
102 )
103 .or_default()
104 .push(key);
105
106 v
107 }
108 }
109 }
110
111 pub async fn exists(&self, key: K) -> bool {
112 let lock = self.inner.lock().await;
113 lock.map.get(&key).is_some()
114 }
115
116 pub async fn ready(&self, key: K) -> bool {
117 let lock = self.inner.lock().await;
118 match lock.map.get(&key) {
119 Some(v) => v.lock().await.is_some(),
120 None => false,
121 }
122 }
123
124 /// Expire immediatly the an item from the cache.
125 pub async fn expire(&self, key: &K) {
126 let mut lock = self.inner.lock().await;
127 lock.map.remove(key);
128 }
129
130 pub async fn clone_inner_map(&self) -> HashMap<K, Option<V>> where V: Clone {
131 let lock = self.inner.lock().await;
132 let mut hm = HashMap::new();
133 for (k, v) in lock.map.iter() {
134 hm.insert(k.clone(), v.lock().await.clone());
135 }
136 hm
137 }
138}