conmonrs/streaming_server/
bounded_map.rs1use std::{collections::VecDeque, fmt::Debug, time::Duration};
5use tokio::time::Instant;
7use tracing::debug;
8
9pub(super) struct BoundedMap<K, V> {
15 entries: VecDeque<(K, Instant, V)>,
17 capacity: usize,
18 max_duration: Duration,
19}
20
21impl<K, V> Debug for BoundedMap<K, V> {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("BoundedMap")
24 .field("len", &self.entries.len())
25 .field("max_duration", &self.max_duration)
26 .finish()
27 }
28}
29
30impl<K, V> BoundedMap<K, V>
31where
32 K: Eq + Debug,
33{
34 pub(super) fn insert(&mut self, k: K, v: V) -> Result<(), V> {
39 let now = Instant::now();
40 self.remove_expired(now);
41
42 if self.entries.len() >= self.capacity {
43 return Err(v);
44 }
45 self.entries.push_back((k, now, v));
46 Ok(())
47 }
48
49 pub(super) fn remove(&mut self, k: &K) -> Option<V> {
53 let now = Instant::now();
54 let entry = self.position(k).and_then(|pos| self.entries.remove(pos));
57 self.remove_expired(now);
58
59 let (_, inserted, value) = entry?;
60 if now - inserted > self.max_duration {
61 debug!("Max duration expired for key: {k:?}");
62 None
63 } else {
64 Some(value)
65 }
66 }
67
68 pub(super) fn contains_key(&self, k: &K) -> bool {
71 self.get(k).is_some()
72 }
73
74 pub(super) fn get(&self, k: &K) -> Option<&V> {
76 let now = Instant::now();
77 self.entries
78 .iter()
79 .find(|(key, inserted, _)| key == k && now - *inserted <= self.max_duration)
80 .map(|(_, _, value)| value)
81 }
82
83 pub(super) fn sweep(&mut self) {
85 self.remove_expired(Instant::now());
86 }
87
88 fn position(&self, k: &K) -> Option<usize> {
89 self.entries.iter().position(|(key, _, _)| key == k)
90 }
91
92 fn remove_expired(&mut self, now: Instant) {
95 let mut expired_count = 0;
96 while let Some((_, inserted, _)) = self.entries.front() {
97 if now - *inserted > self.max_duration {
98 self.entries.pop_front();
99 expired_count += 1;
100 } else {
101 break;
102 }
103 }
104
105 if expired_count > 0 {
106 debug!("Removed {expired_count} timed out elements");
107 }
108 }
109
110 pub(super) fn is_empty(&self) -> bool {
112 self.entries.is_empty()
113 }
114
115 #[cfg(test)]
117 pub(super) fn len(&self) -> usize {
118 self.entries.len()
119 }
120}
121
122pub(crate) const DEFAULT_CAPACITY: usize = 1000;
127
128const MAX_AGE: Duration = Duration::from_secs(60);
132
133impl<K, V> Default for BoundedMap<K, V> {
134 fn default() -> Self {
135 Self {
136 entries: VecDeque::new(),
137 capacity: DEFAULT_CAPACITY,
138 max_duration: MAX_AGE,
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use tokio::time::advance;
147
148 #[tokio::test(start_paused = true)]
149 async fn contains_key_leaves_the_element() {
150 let mut sut = BoundedMap::default();
151 assert_eq!(sut.insert(1, "one"), Ok(()));
152 assert!(sut.contains_key(&1));
153 assert!(!sut.contains_key(&2));
154 assert_eq!(sut.remove(&1), Some("one"));
155
156 assert_eq!(sut.insert(3, "three"), Ok(()));
157 advance(sut.max_duration + Duration::from_secs(1)).await;
158 assert!(!sut.contains_key(&3));
159 }
160
161 #[tokio::test(start_paused = true)]
162 async fn bounded_map_test() {
163 let mut sut = BoundedMap {
164 entries: VecDeque::new(),
165 capacity: 2,
166 max_duration: Duration::new(60 * 60, 0),
167 };
168
169 assert_eq!(sut.len(), 0);
170
171 assert_eq!(sut.insert(0, 0), Ok(()));
173 assert_eq!(sut.len(), 1);
174
175 assert_eq!(sut.insert(1, 0), Ok(()));
177 assert_eq!(sut.len(), 2);
178 assert!(sut.remove(&1).is_some());
179 assert_eq!(sut.len(), 1);
180 assert_eq!(sut.insert(1, 0), Ok(()));
181
182 assert_eq!(sut.insert(2, 2), Err(2));
184 assert_eq!(sut.len(), 2);
185 assert!(sut.remove(&2).is_none());
186
187 assert!(sut.remove(&0).is_some());
188 assert!(sut.remove(&1).is_some());
189
190 sut.max_duration = Duration::from_millis(100);
192 assert_eq!(sut.insert(0, 0), Ok(()));
193 assert_eq!(sut.insert(1, 0), Ok(()));
194 advance(Duration::from_millis(200)).await;
195
196 assert_eq!(sut.insert(2, 0), Ok(()));
198 assert_eq!(sut.len(), 1);
199 assert!(sut.remove(&0).is_none());
200
201 advance(Duration::from_millis(200)).await;
203 assert!(sut.remove(&2).is_none());
204
205 assert_eq!(sut.insert(0, 0), Ok(()));
207 assert_eq!(sut.insert(1, 0), Ok(()));
208 advance(Duration::from_millis(200)).await;
209 assert!(sut.remove(&2).is_none());
210 assert_eq!(sut.len(), 0);
211
212 assert_eq!(sut.insert(0, 0), Ok(()));
214 advance(Duration::from_millis(200)).await;
215 assert!(!sut.is_empty());
216 sut.sweep();
217 assert!(sut.is_empty());
218 }
219
220 #[tokio::test(start_paused = true)]
221 async fn expired_elements_are_not_handed_out() {
222 let mut sut = BoundedMap::default();
223 assert_eq!(sut.insert(1, "one"), Ok(()));
224 assert_eq!(sut.insert(2, "two"), Ok(()));
225 advance(sut.max_duration + Duration::from_secs(1)).await;
226 assert_eq!(sut.insert(3, "three"), Ok(()));
227
228 assert_eq!(sut.remove(&1), None);
231 assert_eq!(sut.len(), 1);
232 assert_eq!(sut.remove(&3), Some("three"));
233 }
234}