cache_service/
in_memory_cache.rs1use 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}