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}