cache_service/
in_memory_cache.rs

1use std::collections::HashMap;
2use std::marker::PhantomData;
3use std::sync::{Arc, Mutex};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::SetPayload;
7
8#[derive(Debug)]
9struct CacheValue {
10    value: String,
11    timestamp: u64,
12    ttl: u64,
13}
14
15#[derive(Debug, PartialEq)]
16pub enum InMemoryCacheError {
17    EmptyKey,
18}
19
20pub trait TimeSource {
21    fn now(&self) -> u64;
22}
23
24pub struct SystemTimeSource;
25
26impl TimeSource for SystemTimeSource {
27    fn now(&self) -> u64 {
28        SystemTime::now()
29            .duration_since(UNIX_EPOCH)
30            .expect("Time went backwards")
31            .as_secs()
32    }
33}
34
35impl Default for SystemTimeSource {
36    fn default() -> Self {
37        SystemTimeSource
38    }
39}
40
41pub struct InMemoryCache<T: TimeSource = SystemTimeSource> {
42    values: Arc<Mutex<HashMap<String, CacheValue>>>,
43    #[cfg(test)]
44    time_source: T,
45    #[cfg(not(test))]
46    time_source: SystemTimeSource,
47    _marker: PhantomData<T>,
48    hits: Arc<Mutex<u64>>,
49}
50
51impl<T: TimeSource> InMemoryCache<T> {
52    pub fn get(&mut self, key: &str) -> Option<String> {
53        let values = self.values.lock().unwrap();
54        values.get(key).map(|value| value.value.to_owned())
55    }
56
57    pub fn set(&mut self, payload: SetPayload) -> Result<String, InMemoryCacheError> {
58        if payload.key.is_empty() {
59            return Err(InMemoryCacheError::EmptyKey);
60        }
61
62        let mut hits = self.hits.lock().unwrap();
63        let mut values = self.values.lock().unwrap();
64        *hits += 1;
65        let now = self.time_source.now();
66
67        if *hits % 50000 == 0 {
68            values.retain(|_, value| now < value.timestamp + value.ttl);
69        } else if let Some(cached_value) = values.get(payload.key) {
70            println!("{:?}", now >= cached_value.timestamp + cached_value.ttl);
71            if now >= cached_value.timestamp + cached_value.ttl {
72                values.remove(payload.key);
73            }
74        }
75
76        Ok(values
77            .entry(payload.key.to_owned())
78            .or_insert(CacheValue {
79                value: payload.value.to_owned(),
80                timestamp: now,
81                ttl: payload.ttl,
82            })
83            .value
84            .to_owned())
85    }
86}
87
88impl InMemoryCache<SystemTimeSource> {
89    pub fn new() -> InMemoryCache<SystemTimeSource> {
90        InMemoryCache {
91            values: Arc::new(Mutex::new(HashMap::new())),
92            time_source: SystemTimeSource,
93            _marker: PhantomData,
94            hits: Arc::new(Mutex::new(0)),
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    impl<T: TimeSource> InMemoryCache<T> {
104        fn new_with_time_source(time_source: T) -> InMemoryCache<T> {
105            InMemoryCache {
106                time_source,
107                values: Arc::new(Mutex::new(HashMap::new())),
108                _marker: PhantomData,
109                hits: Arc::new(Mutex::new(0)),
110            }
111        }
112
113        fn set_hits(&mut self, hits: u64) {
114            let mut hits_val = self.hits.lock().unwrap();
115            *hits_val = hits;
116        }
117
118        fn get_values_length(&self) -> usize {
119            self.values.lock().unwrap().len()
120        }
121
122        fn get_value(&self, key: &str) -> String {
123            self.values
124                .lock()
125                .unwrap()
126                .get(key)
127                .unwrap()
128                .value
129                .to_owned()
130        }
131    }
132
133    struct MockTimeSource {
134        now: u64,
135    }
136
137    impl MockTimeSource {
138        fn new(now: u64) -> Self {
139            MockTimeSource { now }
140        }
141
142        fn advance(&mut self, secs: u64) {
143            self.now += secs;
144        }
145    }
146
147    impl TimeSource for MockTimeSource {
148        fn now(&self) -> u64 {
149            self.now
150        }
151    }
152
153    #[test]
154    fn it_should_create_empty_cache() {
155        let cache = InMemoryCache::new();
156        assert_eq!(cache.get_values_length(), 0);
157    }
158
159    #[test]
160    fn it_should_return_value() {
161        let mut cache = InMemoryCache::new();
162        let result = cache
163            .set(SetPayload {
164                key: "key",
165                value: "value",
166                ttl: 1,
167            })
168            .expect("Should not fail");
169        assert_eq!(result, "value");
170    }
171
172    #[test]
173    fn it_should_store_value_in_cache() {
174        let mut cache = InMemoryCache::new();
175        assert_eq!(cache.get_values_length(), 0);
176        cache
177            .set(SetPayload {
178                key: "key",
179                value: "value",
180                ttl: 1,
181            })
182            .expect("Should not fail");
183        assert_eq!(cache.get_values_length(), 1);
184        let result = cache.get_value("key");
185        assert_eq!(result, "value");
186    }
187
188    #[test]
189    fn it_should_cache_value_for_ttl() {
190        let mut cache = InMemoryCache::new();
191        cache
192            .set(SetPayload {
193                key: "key",
194                value: "value",
195                ttl: 1,
196            })
197            .expect("Should not fail");
198        let cached = cache.set(SetPayload {
199            key: "key",
200            value: "value123",
201            ttl: 1,
202        });
203        assert_eq!(cached.unwrap(), "value");
204    }
205
206    #[test]
207    fn it_should_change_value_on_expiry() {
208        let mut cache = InMemoryCache::new_with_time_source(MockTimeSource::new(0));
209        cache
210            .set(SetPayload {
211                key: "key",
212                value: "value",
213                ttl: 1,
214            })
215            .expect("Should not fail");
216        cache.time_source.advance(2);
217        let cached = cache.set(SetPayload {
218            key: "key",
219            value: "value123",
220            ttl: 1,
221        });
222        assert_eq!(cached.unwrap(), "value123");
223    }
224
225    #[test]
226    fn it_should_resolve_fast_on_big_cache() {
227        let now = SystemTime::now();
228
229        let mut cache = InMemoryCache::new();
230        for i in 0..100000 {
231            cache
232                .set(SetPayload {
233                    key: &format!("key{}", i),
234                    value: &format!("value{}", i),
235                    ttl: 100,
236                })
237                .expect("Should not fail");
238        }
239        let result = cache.set(SetPayload {
240            key: "key30",
241            value: "value",
242            ttl: 100,
243        });
244        let elapsed = now.elapsed().unwrap().as_millis();
245        assert_eq!(&result.unwrap(), "value30");
246        assert!(
247            elapsed < 500,
248            "Elapsed time: {}, should be less than 500",
249            elapsed
250        );
251    }
252
253    #[test]
254    fn it_should_get_rid_off_all_expired_keys_periodically() {
255        let mut cache = InMemoryCache::new_with_time_source(MockTimeSource::new(0));
256        cache
257            .set(SetPayload {
258                key: "key49999",
259                value: "value49999",
260                ttl: 1,
261            })
262            .expect("Should not fail");
263        cache.set_hits(49999);
264        cache.time_source.advance(2);
265        cache
266            .set(SetPayload {
267                key: "key50000",
268                value: "value50000",
269                ttl: 1,
270            })
271            .expect("Should not fail");
272
273        assert_eq!(cache.get_values_length(), 1);
274    }
275
276    #[test]
277    fn it_should_return_error_when_key_is_empty() {
278        let mut cache = InMemoryCache::new();
279        let result = cache.set(SetPayload {
280            key: "",
281            value: "value",
282            ttl: 1,
283        });
284        assert!(matches!(result, Err(InMemoryCacheError::EmptyKey)));
285    }
286}