1use std::{
4 collections::BTreeMap,
5 num::NonZeroUsize,
6 ops::Range,
7 sync::{Arc, Weak},
8};
9
10use async_trait::async_trait;
11use bytes::{Bytes, BytesMut};
12use futures_util::{StreamExt, TryStreamExt, stream};
13use parking_lot::Mutex;
14use tokio::sync::{Mutex as AsyncMutex, Semaphore};
15
16use crate::{RangeCache, RangeError, cache::ReadPlan};
17
18#[async_trait]
23pub trait RangeReader<K>: Send + Sync {
24 type Error: std::error::Error + Send + Sync + 'static;
26
27 async fn read_range(&self, key: &K, range: Range<usize>) -> Result<Bytes, Self::Error>;
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct ReaderConfig {
34 max_fetch_concurrency: NonZeroUsize,
35}
36
37impl ReaderConfig {
38 #[must_use]
40 pub const fn new(max_fetch_concurrency: NonZeroUsize) -> Self {
41 Self {
42 max_fetch_concurrency,
43 }
44 }
45
46 #[must_use]
48 pub const fn max_fetch_concurrency(self) -> NonZeroUsize {
49 self.max_fetch_concurrency
50 }
51}
52
53#[derive(Debug, thiserror::Error)]
55pub enum ReadError<E> {
56 #[error(transparent)]
58 Range(#[from] RangeError),
59 #[error("range source failed: {0}")]
61 Source(#[source] E),
62 #[error("source returned {actual} bytes for {range:?}; expected {expected}")]
64 ShortRead {
65 range: Range<usize>,
67 expected: usize,
69 actual: usize,
71 },
72}
73
74#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
75struct InFlightKey<K> {
76 key: K,
77 start: usize,
78 end: usize,
79}
80
81struct InFlightRegistry<K: Ord> {
82 entries: Mutex<BTreeMap<InFlightKey<K>, Weak<InFlightEntry>>>,
83}
84
85impl<K: Ord> Default for InFlightRegistry<K> {
86 fn default() -> Self {
87 Self {
88 entries: Mutex::new(BTreeMap::new()),
89 }
90 }
91}
92
93struct InFlightEntry {
94 gate: AsyncMutex<()>,
95 successful_response: Mutex<Option<Bytes>>,
96}
97
98impl<K: Ord + Clone> InFlightRegistry<K> {
99 fn register(self: &Arc<Self>, request: InFlightKey<K>) -> InFlightRegistration<K> {
100 let entry = {
101 let mut entries = self.entries.lock();
102 let current = entries.get(&request).and_then(Weak::upgrade);
103 let entry = current.unwrap_or_else(|| {
104 Arc::new(InFlightEntry {
105 gate: AsyncMutex::new(()),
106 successful_response: Mutex::new(None),
107 })
108 });
109 entries.insert(request.clone(), Arc::downgrade(&entry));
110 entry
111 };
112 InFlightRegistration {
113 registry: Arc::clone(self),
114 request,
115 entry,
116 }
117 }
118}
119
120struct InFlightRegistration<K: Ord> {
121 registry: Arc<InFlightRegistry<K>>,
122 request: InFlightKey<K>,
123 entry: Arc<InFlightEntry>,
124}
125
126impl<K: Ord> Drop for InFlightRegistration<K> {
127 fn drop(&mut self) {
128 let mut entries = self.registry.entries.lock();
129 let registered_is_self = entries
130 .get(&self.request)
131 .is_some_and(|registered| registered.ptr_eq(&Arc::downgrade(&self.entry)));
132 if registered_is_self && Arc::strong_count(&self.entry) == 1 {
133 entries.remove(&self.request);
134 }
135 }
136}
137
138pub struct CachedReader<K: Ord, R: ?Sized> {
140 source: Arc<R>,
141 cache: RangeCache<K>,
142 config: ReaderConfig,
143 fetch_limit: Arc<Semaphore>,
144 in_flight: Arc<InFlightRegistry<K>>,
145}
146
147impl<K: Ord, R: ?Sized> Clone for CachedReader<K, R> {
148 fn clone(&self) -> Self {
149 Self {
150 source: Arc::clone(&self.source),
151 cache: self.cache.clone(),
152 config: self.config,
153 fetch_limit: Arc::clone(&self.fetch_limit),
154 in_flight: Arc::clone(&self.in_flight),
155 }
156 }
157}
158
159impl<K: Ord, R: ?Sized> CachedReader<K, R> {
160 #[must_use]
162 pub fn new(source: Arc<R>, cache: RangeCache<K>, config: ReaderConfig) -> Self {
163 Self {
164 source,
165 cache,
166 config,
167 fetch_limit: Arc::new(Semaphore::new(config.max_fetch_concurrency.get())),
168 in_flight: Arc::new(InFlightRegistry::default()),
169 }
170 }
171
172 #[must_use]
174 pub const fn cache(&self) -> &RangeCache<K> {
175 &self.cache
176 }
177
178 #[must_use]
180 pub const fn source(&self) -> &Arc<R> {
181 &self.source
182 }
183
184 #[must_use]
186 pub const fn config(&self) -> ReaderConfig {
187 self.config
188 }
189}
190
191impl<K, R> CachedReader<K, R>
192where
193 K: Ord + Clone,
194 R: RangeReader<K> + ?Sized,
195{
196 pub async fn read(&self, key: &K, range: Range<usize>) -> Result<Bytes, ReadError<R::Error>> {
212 let (mut cached, missing) = match self.cache.read_plan(key, range.clone())? {
213 ReadPlan::Complete(bytes) => return Ok(bytes),
214 ReadPlan::Fetch { cached, missing } => (cached, missing),
215 };
216
217 let fetched = stream::iter(missing)
218 .map(|gap| self.fetch_gap(key, gap))
219 .buffer_unordered(self.config.max_fetch_concurrency.get())
220 .try_collect::<Vec<_>>()
221 .await?;
222 cached.extend(fetched);
223 cached.sort_unstable_by_key(|(chunk_range, _)| chunk_range.start);
224
225 if cached.len() == 1 && cached[0].0 == range {
226 return Ok(cached.pop().expect("single chunk exists").1);
227 }
228
229 let mut reconstructed = BytesMut::with_capacity(range.len());
230 let mut cursor = range.start;
231 for (chunk_range, bytes) in cached {
232 assert_eq!(chunk_range.start, cursor, "read plan has no gaps");
233 assert_eq!(chunk_range.len(), bytes.len(), "chunk length is exact");
234 reconstructed.extend_from_slice(&bytes);
235 cursor = chunk_range.end;
236 }
237 assert_eq!(cursor, range.end, "read plan covers the request");
238 Ok(reconstructed.freeze())
239 }
240
241 async fn fetch_gap(
242 &self,
243 key: &K,
244 range: Range<usize>,
245 ) -> Result<(Range<usize>, Bytes), ReadError<R::Error>> {
246 let registration = self.in_flight.register(InFlightKey {
247 key: key.clone(),
248 start: range.start,
249 end: range.end,
250 });
251 let _request_guard = registration.entry.gate.lock().await;
252
253 if let Some(bytes) = registration.entry.successful_response.lock().clone() {
254 return Ok((range, bytes));
255 }
256
257 if let Some(bytes) = self.cache.get(key, range.clone())? {
258 return Ok((range, bytes));
259 }
260
261 let _fetch_permit = self
262 .fetch_limit
263 .acquire()
264 .await
265 .expect("private fetch semaphore remains open");
266 let bytes = self
267 .source
268 .read_range(key, range.clone())
269 .await
270 .map_err(ReadError::Source)?;
271 let expected = range.len();
272 if bytes.len() != expected {
273 return Err(ReadError::ShortRead {
274 range,
275 expected,
276 actual: bytes.len(),
277 });
278 }
279
280 let _ = self
281 .cache
282 .insert(key.clone(), range.clone(), bytes.clone())?;
283 *registration.entry.successful_response.lock() = Some(bytes.clone());
284 Ok((range, bytes))
285 }
286}
287
288#[async_trait]
289impl<K, R> RangeReader<K> for CachedReader<K, R>
290where
291 K: Ord + Clone + Send + Sync,
292 R: RangeReader<K> + ?Sized,
293{
294 type Error = ReadError<R::Error>;
295
296 async fn read_range(&self, key: &K, range: Range<usize>) -> Result<Bytes, Self::Error> {
297 self.read(key, range).await
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use std::{convert::Infallible, num::NonZeroUsize, sync::Arc, time::Duration};
304
305 use async_trait::async_trait;
306 use bytes::Bytes;
307 use tokio::sync::Semaphore;
308
309 use super::{CachedReader, RangeReader, ReaderConfig};
310 use crate::{CacheCapacity, RangeCache};
311
312 struct BlockingSource {
313 started: Semaphore,
314 }
315
316 #[async_trait]
317 impl RangeReader<String> for BlockingSource {
318 type Error = Infallible;
319
320 async fn read_range(
321 &self,
322 _key: &String,
323 range: std::ops::Range<usize>,
324 ) -> Result<Bytes, Self::Error> {
325 self.started.add_permits(1);
326 tokio::time::sleep(Duration::from_secs(60)).await;
327 Ok(Bytes::from(vec![0; range.len()]))
328 }
329 }
330
331 #[tokio::test]
332 async fn cancelled_leader_removes_its_in_flight_registration() {
333 let source = Arc::new(BlockingSource {
334 started: Semaphore::new(0),
335 });
336 let reader = CachedReader::new(
337 Arc::clone(&source),
338 RangeCache::new(CacheCapacity::Unbounded),
339 ReaderConfig::new(NonZeroUsize::new(1).expect("non-zero")),
340 );
341 let task_reader = reader.clone();
342 let task = tokio::spawn(async move {
343 let key = String::from("key");
344 task_reader.read(&key, 0..4).await
345 });
346 source
347 .started
348 .acquire()
349 .await
350 .expect("source semaphore remains open")
351 .forget();
352 task.abort();
353 assert!(task.await.expect_err("task was cancelled").is_cancelled());
354
355 tokio::task::yield_now().await;
356 assert!(reader.in_flight.entries.lock().is_empty());
357 }
358}