1use std::{
2 fmt::Debug,
3 hash::{BuildHasher, Hash},
4 marker::PhantomData,
5 time::{Duration, Instant},
6};
7
8use parking_lot::{Mutex, MutexGuard};
9
10use super::{
11 linked_arena::{LinkedArena, LinkedNode},
12 Policy, Prune,
13};
14use crate::LightCache;
15
16pub struct TtlPolicy<K, V> {
18 inner: Mutex<TtlPolicyInner<K>>,
19 phantom: PhantomData<V>,
21}
22
23pub struct TtlPolicyInner<K> {
24 ttl: Duration,
25 arena: LinkedArena<K, TtlNode<K>>,
26}
27
28impl<K, V> TtlPolicy<K, V> {
29 pub fn new(ttl: Duration) -> Self {
30 TtlPolicy {
31 inner: Mutex::new(TtlPolicyInner {
32 ttl,
33 arena: LinkedArena::new(),
34 }),
35 phantom: PhantomData,
36 }
37 }
38}
39
40impl<K, V> Policy<K, V> for TtlPolicy<K, V>
41where
42 K: Copy + Eq + Hash,
43 V: Clone + Sync,
44{
45 type Inner = TtlPolicyInner<K>;
46
47 #[inline]
48 fn lock_inner(&self) -> MutexGuard<'_, Self::Inner> {
49 self.inner.lock()
50 }
51
52 fn get<S: BuildHasher>(&self, key: &K, cache: &LightCache<K, V, S, Self>) -> Option<V> {
53 let _lock = self.lock_and_prune(cache);
54
55 cache.get_no_policy(key)
56 }
57
58 fn insert<S: BuildHasher>(&self, key: K, value: V, cache: &LightCache<K, V, S, Self>) -> Option<V> {
59 {
60 let mut inner = self.lock_and_prune(cache);
61
62 if let Some((idx, node)) = inner.arena.get_node_mut(&key) {
64 node.creation = Instant::now();
65
66 inner.arena.move_to_head(idx);
67 } else {
68 inner.arena.insert_head(key);
69 }
70 }
71
72 cache.insert_no_policy(key, value)
73 }
74
75 fn remove<S: BuildHasher>(&self, key: &K, cache: &LightCache<K, V, S, Self>) -> Option<V> {
76 {
77 let mut inner = self.lock_inner();
78
79 inner.prune(cache);
80 inner.arena.remove_item(key);
81 }
82
83 cache.remove_no_policy(key)
84 }
85}
86
87impl<K, V> Prune<K, V, TtlPolicy<K, V>> for TtlPolicyInner<K>
88where
89 K: Copy + Eq + Hash,
90 V: Clone + Sync,
91{
92 fn prune<S: BuildHasher>(&mut self, cache: &LightCache<K, V, S, TtlPolicy<K, V>>) {
93 while let Some((idx, tail)) = self.arena.tail() {
94 if tail.should_evict(self.ttl) {
95 let (_, n) = self.arena.remove(idx);
96 cache.remove_no_policy(n.item());
97 } else {
98 break;
99 }
100 }
101 }
102}
103
104struct TtlNode<K> {
105 key: K,
106 creation: Instant,
107 parent: Option<usize>,
108 child: Option<usize>,
109}
110
111impl<K> LinkedNode<K> for TtlNode<K>
112where
113 K: Copy + Eq + Hash,
114{
115 fn new(key: K, parent: Option<usize>, child: Option<usize>) -> Self {
116 TtlNode {
117 key,
118 creation: Instant::now(),
119 parent,
120 child,
121 }
122 }
123
124 fn item(&self) -> &K {
125 &self.key
126 }
127
128 fn prev(&self) -> Option<usize> {
129 self.parent
130 }
131
132 fn next(&self) -> Option<usize> {
133 self.child
134 }
135
136 fn set_prev(&mut self, parent: Option<usize>) {
137 self.parent = parent;
138 }
139
140 fn set_next(&mut self, child: Option<usize>) {
141 self.child = child;
142 }
143}
144
145impl<K> Debug for TtlNode<K> {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_struct("TtlNode")
148 .field("creation", &self.creation)
149 .field("parent", &self.parent)
150 .field("child", &self.child)
151 .finish()
152 }
153}
154
155impl<K> TtlNode<K> {
156 fn duration_since_creation(&self) -> Duration {
157 Instant::now().duration_since(self.creation)
158 }
159
160 fn should_evict(&self, ttl: Duration) -> bool {
161 self.duration_since_creation() > ttl
162 }
163}
164
165#[cfg(test)]
166mod test {
167 use hashbrown::hash_map::DefaultHashBuilder;
168
169 use super::*;
170 use std::time::Duration;
171
172 fn duration_seconds(seconds: u64) -> Duration {
173 Duration::from_secs(seconds)
174 }
175
176 fn sleep_seconds(seconds: u64) {
177 std::thread::sleep(duration_seconds(seconds));
178 }
179
180 fn insert_n<S>(cache: &LightCache<i32, i32, S, TtlPolicy<i32, i32>>, n: usize)
181 where
182 S: BuildHasher,
183 {
184 for i in 0..n {
185 cache.insert(i as i32, i as i32);
186 }
187 }
188
189 fn cache<K, V>(ttl: Duration) -> LightCache<K, V, DefaultHashBuilder, TtlPolicy<K, V>>
190 where
191 K: Copy + Eq + Hash,
192 V: Clone + Sync,
193 {
194 LightCache::from_parts(TtlPolicy::new(ttl), Default::default())
195 }
196
197 #[test]
198 fn test_basic_scenario_1() {
201 let cache = cache::<i32, i32>(duration_seconds(1));
202
203 insert_n(&cache, 5);
204
205 sleep_seconds(2);
206
207 insert_n(&cache, 2);
208
209 assert_eq!(cache.len(), 2);
211 let policy = cache.policy().lock_inner();
212
213 assert_eq!(policy.arena.nodes.len(), 2);
214 assert_eq!(policy.arena.head, Some(1));
215 assert_eq!(policy.arena.tail, Some(0));
216 }
217
218 #[test]
219 fn test_basic_scenario_2() {
220 let cache = cache::<i32, i32>(duration_seconds(1));
221
222 insert_n(&cache, 10);
223
224 cache.remove(&0);
225
226 sleep_seconds(2);
227
228 insert_n(&cache, 2);
229
230 assert_eq!(cache.len(), 2);
231 }
232}