shardline_cache/memory/
mod.rs1use std::{
2 future::Future,
3 num::{NonZeroU64, NonZeroUsize},
4 sync::Arc,
5 time::Duration,
6};
7
8use tokio::sync::{Notify, RwLock};
9use tokio::time::Instant;
10
11use crate::{
12 AsyncReconstructionCache, ReconstructionCacheError, ReconstructionCacheFuture,
13 ReconstructionCacheKey,
14};
15
16mod inner;
17
18#[cfg(test)]
19mod tests;
20
21use inner::{CacheInner, MemoryEntry};
22
23#[derive(Debug, Clone)]
25pub struct MemoryReconstructionCache {
26 ttl: Duration,
27 max_entries: NonZeroUsize,
28 inner: Arc<RwLock<CacheInner>>,
29}
30
31impl MemoryReconstructionCache {
32 #[must_use]
34 pub fn new(ttl_seconds: NonZeroU64, max_entries: NonZeroUsize) -> Self {
35 Self {
36 ttl: Duration::from_secs(ttl_seconds.get()),
37 max_entries,
38 inner: Arc::new(RwLock::new(CacheInner::new())),
39 }
40 }
41
42 pub async fn get_or_load<F, Fut>(
52 &self,
53 key: &ReconstructionCacheKey,
54 loader: F,
55 ) -> Result<Option<Vec<u8>>, ReconstructionCacheError>
56 where
57 F: FnOnce() -> Fut,
58 Fut: Future<Output = Result<Vec<u8>, ReconstructionCacheError>>,
59 {
60 {
62 let inner = self.inner.read().await;
63 let now = Instant::now();
64 if let Some(entry) = inner.entries.get(key)
65 && entry.expires_at > now
66 {
67 return Ok(Some(entry.payload.as_ref().clone()));
68 }
69 }
70
71 let (should_load, notify) = {
73 let mut inner = self.inner.write().await;
74 let now = Instant::now();
75
76 if let Some(entry) = inner.entries.get(key)
78 && entry.expires_at > now
79 {
80 return Ok(Some(entry.payload.as_ref().clone()));
81 }
82
83 let (should_load, notify) = if inner.loading.contains_key(key) {
88 let dummy = Arc::new(Notify::new());
92 let existing = inner.loading.get(key).unwrap_or(&dummy);
93 (false, Arc::clone(existing))
94 } else {
95 let new_notify = Arc::new(Notify::new());
96 inner.loading.insert(key.clone(), Arc::clone(&new_notify));
97 if let Some(entry) = inner.entries.get(key)
99 && entry.expires_at <= now
100 {
101 inner.remove(key);
102 }
103 (true, new_notify)
104 };
105 (should_load, notify)
106 };
107
108 if should_load {
109 let result = loader().await;
110 match result {
111 Ok(payload) => {
112 self.put(key, &payload).await?;
113 Ok(Some(payload))
114 }
115 Err(e) => {
116 let mut inner = self.inner.write().await;
118 inner.loading.remove(key);
119 notify.notify_waiters();
120 Err(e)
121 }
122 }
123 } else {
124 notify.notified().await;
126 let inner = self.inner.read().await;
127 let now = Instant::now();
128 if let Some(entry) = inner.entries.get(key)
129 && entry.expires_at > now
130 {
131 Ok(Some(entry.payload.as_ref().clone()))
132 } else {
133 Ok(None)
134 }
135 }
136 }
137}
138
139impl AsyncReconstructionCache for MemoryReconstructionCache {
140 fn ready(&self) -> ReconstructionCacheFuture<'_, ()> {
141 Box::pin(async { Ok(()) })
142 }
143
144 fn get<'operation>(
145 &'operation self,
146 key: &'operation ReconstructionCacheKey,
147 ) -> ReconstructionCacheFuture<'operation, Option<Vec<u8>>> {
148 Box::pin(async move {
149 let now = Instant::now();
150 {
151 let inner = self.inner.read().await;
152 if let Some(entry) = inner.entries.get(key)
153 && entry.expires_at > now
154 {
155 return Ok(Some(entry.payload.as_ref().clone()));
156 } else if !inner.loading.contains_key(key) {
157 return Ok(None);
158 }
159 }
160
161 let mut inner = self.inner.write().await;
162
163 if let Some(entry) = inner.entries.get(key)
164 && entry.expires_at > now
165 {
166 return Ok(Some(entry.payload.as_ref().clone()));
167 }
168
169 if let Some(notify) = inner.loading.get(key) {
170 let notify = Arc::clone(notify);
171 drop(inner);
172 tokio::select! {
177 () = notify.notified() => {}
178 () = tokio::time::sleep(Duration::from_secs(30)) => {
179 let mut write_guard = self.inner.write().await;
180 write_guard.loading.remove(key);
181 return Ok(None);
182 }
183 }
184
185 let read_inner = self.inner.read().await;
186 if let Some(entry) = read_inner.entries.get(key)
187 && entry.expires_at > Instant::now()
188 {
189 return Ok(Some(entry.payload.as_ref().clone()));
190 }
191 return Ok(None);
192 }
193
194 let notify = Arc::new(Notify::new());
195 inner.loading.insert(key.clone(), Arc::clone(¬ify));
196
197 let should_remove = inner
198 .entries
199 .get(key)
200 .is_some_and(|entry| entry.expires_at <= now);
201 if should_remove {
202 inner.remove(key);
203 }
204 Ok(None)
205 })
206 }
207
208 fn put<'operation>(
209 &'operation self,
210 key: &'operation ReconstructionCacheKey,
211 payload: &'operation [u8],
212 ) -> ReconstructionCacheFuture<'operation, ()> {
213 Box::pin(async move {
214 let now = Instant::now();
215 let expires_at = now.checked_add(self.ttl).unwrap_or(now);
216 let mut inner = self.inner.write().await;
217 if !inner.entries.contains_key(key) && inner.entries.len() >= self.max_entries.get() {
218 inner.evict_oldest();
219 }
220 let seq = inner.next_seq;
221 inner.next_seq = inner.next_seq.saturating_add(1);
222 inner.insert(
223 key,
224 MemoryEntry {
225 payload: Arc::new(payload.to_vec()),
226 expires_at,
227 inserted_at: now,
228 seq,
229 },
230 );
231 if let Some(notify) = inner.loading.remove(key) {
232 notify.notify_waiters();
233 }
234 Ok(())
235 })
236 }
237
238 fn delete<'operation>(
239 &'operation self,
240 key: &'operation ReconstructionCacheKey,
241 ) -> ReconstructionCacheFuture<'operation, bool> {
242 Box::pin(async move {
243 let mut inner = self.inner.write().await;
244 Ok(inner.remove(key).is_some())
245 })
246 }
247}