1#[cfg(test)]
18mod tests;
19
20use std::collections::{BTreeMap, BTreeSet};
21use std::future::Future;
22use std::hash::Hash;
23use std::io;
24use std::num::NonZeroU64;
25use std::ops::Range;
26use std::sync::Arc;
27use std::task::Poll;
28
29use bytes::{Bytes, BytesMut};
30use common_base::range_read::RangeReader;
31use futures::channel::{mpsc, oneshot};
32use futures::{StreamExt, future};
33
34use crate::metrics::{CacheMetrics, CacheSizeMetric};
35use crate::page::PageLayout;
36use crate::{Cache, RemovalCause};
37
38pub enum PageEvent {
40 Lookup { hits: usize, misses: usize },
42 Loaded { bytes: u64 },
44 Residency { usage: u64, capacity: u64 },
46 Evicted { cause: RemovalCause },
48}
49
50#[derive(Clone)]
52pub struct PageCacheObserver {
53 pub bytes: CacheSizeMetric,
54 pub loads: Option<Arc<CacheMetrics>>,
55 pub events: Arc<dyn Fn(PageEvent) + Send + Sync>,
56}
57
58#[derive(Debug, Default)]
60pub struct PageFetchStats {
61 pub cache_hit: usize,
62 pub cache_miss: usize,
63 pub cached_bytes: u64,
65 pub cached_fragments: usize,
66 pub requested_bytes: u64,
68 pub aligned_bytes: u64,
70 pub unique_requested_bytes: u64,
72}
73
74pub struct PagedCache<K> {
78 cache: Cache<(K, u64), Arc<CachedPage>>,
79 page_size: u64,
80 observer: PageCacheObserver,
81}
82
83impl<K: Hash + Eq + Clone + Send + Sync + 'static> PagedCache<K> {
84 pub fn new(
86 name: &str,
87 capacity: u64,
88 page_size: u64,
89 weigher: impl Fn(&(K, u64), &Bytes) -> u32 + Send + Sync + 'static,
90 observer: PageCacheObserver,
91 ) -> Self {
92 let events = observer.events.clone();
93 let mut cache = Cache::new(
94 name,
95 capacity,
96 move |key, value: &Arc<CachedPage>| weigher(key, &value.bytes),
97 move |_, _, cause| {
98 events(PageEvent::Evicted { cause });
99 },
100 )
101 .with_size_metric(observer.bytes.clone());
102 if let Some(metrics) = &observer.loads {
103 cache = cache.with_metrics(metrics.clone());
104 }
105 Self {
106 cache,
107 page_size,
108 observer,
109 }
110 }
111
112 pub fn invalidate_if(
114 &self,
115 predicate: impl Fn(&K) -> bool + Send + Sync + 'static,
116 ) -> io::Result<()> {
117 self.cache
118 .invalidate_if(move |(file, _), _| predicate(file))
119 }
120
121 pub async fn contains(&self, file: &K, page_id: u64) -> bool {
123 self.cache.get(&(file.clone(), page_id)).await.is_some()
124 }
125
126 pub async fn known_file_size(&self, file: &K, ranges: &[Range<u64>]) -> Option<u64> {
128 let layout = PageLayout::new(NonZeroU64::new(self.page_size)?, u64::MAX);
129 for range in ranges {
130 for id in layout.page_ids(range.clone()).ok()? {
131 if let Some(page) = self.cache.get(&(file.clone(), id)).await {
132 return Some(page.file_size);
133 }
134 }
135 }
136 None
137 }
138
139 pub async fn run_pending_tasks(&self) {
141 self.cache.run_pending_tasks().await;
142 self.observe_residency();
143 }
144
145 pub async fn fetch<R: RangeReader + ?Sized>(
148 &self,
149 file: K,
150 file_size: u64,
151 ranges: &[Range<u64>],
152 reader: &R,
153 ) -> Result<(Vec<Bytes>, PageFetchStats), Arc<io::Error>> {
154 if ranges.iter().any(|r| r.start > r.end || r.end > file_size) {
155 return Err(invalid_request());
156 }
157 let page_size = NonZeroU64::new(self.page_size).ok_or_else(invalid_request)?;
158 let layout = PageLayout::new(page_size, file_size);
159 let mut stats = PageFetchStats::default();
160 let mut ids = BTreeSet::new();
161 for range in ranges {
162 stats.requested_bytes = stats
163 .requested_bytes
164 .checked_add(range.end - range.start)
165 .ok_or_else(invalid_request)?;
166 ids.extend(layout.page_ids(range.clone()).map_err(Arc::new)?);
167 }
168 stats.unique_requested_bytes = unique_requested_bytes(ranges);
169 let mut pages = BTreeMap::new();
170 let mut missing = Vec::new();
171 for id in ids {
172 let range = layout.page_range(id).ok_or_else(invalid_request)?;
173 stats.aligned_bytes += range.end - range.start;
174 if let Some(page) = self.cache.get(&(file.clone(), id)).await {
175 check_file_size(&page, file_size)?;
176 pages.insert(id, page.bytes.clone());
177 } else {
178 missing.push(id);
179 }
180 }
181 for range in ranges {
182 for id in layout.page_ids(range.clone()).map_err(Arc::new)? {
183 if pages.contains_key(&id) {
184 let slice = layout
185 .request_slice(id, range.clone())
186 .map_err(Arc::new)?
187 .ok_or_else(invalid_request)?;
188 stats.cached_bytes += slice.len() as u64;
189 stats.cached_fragments += 1;
190 }
191 }
192 }
193 stats.cache_hit = pages.len();
194 stats.cache_miss = missing.len();
195 (self.observer.events)(PageEvent::Lookup {
196 hits: stats.cache_hit,
197 misses: stats.cache_miss,
198 });
199 if !missing.is_empty() {
200 let (requests, receiver) = mpsc::unbounded();
201 let batch_size = missing.len().max(1);
202 let mut loads = missing
203 .into_iter()
204 .map(|id| {
205 let requests = requests.clone();
206 let key = (file.clone(), id);
207 Box::pin(future::maybe_done(async move {
208 let range = layout.page_range(id).ok_or_else(invalid_request)?;
209 let page = self
210 .cache
211 .try_get_with(&key, async {
212 let (send, receive) =
213 oneshot::channel::<Result<Bytes, Arc<io::Error>>>();
214 requests
215 .unbounded_send((range.clone(), send))
216 .map_err(|_| io::Error::other("Page read batch closed"))?;
217 let bytes = receive
218 .await
219 .map_err(|_| io::Error::other("Page read batch cancelled"))?
220 .map_err(|error: Arc<io::Error>| {
221 io::Error::new(error.kind(), error)
222 })?;
223 if bytes.len() as u64 != range.end - range.start {
224 return Err(io::Error::new(
225 io::ErrorKind::UnexpectedEof,
226 "Invalid page response length",
227 ));
228 }
229 Ok(self
231 .create_cached_page(file_size, Bytes::copy_from_slice(&bytes)))
232 })
233 .await?;
234 check_file_size(&page, file_size)?;
235 Ok::<_, Arc<io::Error>>((id, page.bytes.clone()))
236 }))
237 })
238 .collect::<Vec<_>>();
239 let loads = future::poll_fn(move |cx| {
242 let mut complete = true;
243 for load in &mut loads {
244 complete &= load.as_mut().poll(cx).is_ready();
245 if let Some(Err(error)) = load.as_mut().output_mut() {
246 return Poll::Ready(Err(error.clone()));
247 }
248 }
249 if !complete {
250 return Poll::Pending;
251 }
252 Poll::Ready(
253 loads
254 .iter_mut()
255 .map(|load| load.as_mut().take_output().ok_or_else(invalid_request)?)
256 .collect::<Result<Vec<_>, _>>(),
257 )
258 });
259 drop(requests);
262 let read_batches = async {
263 let mut batches = receiver.ready_chunks(batch_size);
264 while let Some(batch) = batches.next().await {
265 let (ranges, replies): (Vec<_>, Vec<_>) = batch.into_iter().unzip();
266 let result = reader.read_vec(&ranges).await.and_then(|bytes| {
267 if bytes.len() == ranges.len() {
268 Ok(bytes)
269 } else {
270 Err(io::Error::new(
271 io::ErrorKind::InvalidData,
272 "Invalid page response count",
273 ))
274 }
275 });
276 match result {
277 Ok(bytes) => {
278 for (reply, bytes) in replies.into_iter().zip(bytes) {
279 let _ = reply.send(Ok(bytes));
280 }
281 }
282 Err(error) => {
283 let error = Arc::new(error);
284 for reply in replies {
285 let _ = reply.send(Err(error.clone()));
286 }
287 }
288 }
289 }
290 Ok::<(), Arc<io::Error>>(())
291 };
292 let (loaded, ()) = future::try_join(loads, read_batches).await?;
293 pages.extend(loaded);
294 }
295 let mut output = Vec::with_capacity(ranges.len());
296 for range in ranges {
297 if range.is_empty() {
298 output.push(Bytes::new());
299 continue;
300 }
301 let ids = layout.page_ids(range.clone()).map_err(Arc::new)?;
302 if ids.end - ids.start == 1 {
303 let slice = layout
304 .request_slice(ids.start, range.clone())
305 .map_err(Arc::new)?
306 .ok_or_else(invalid_request)?;
307 let page = pages.get(&ids.start).ok_or_else(invalid_request)?;
308 if slice.end > page.len() {
309 return Err(invalid_request());
310 }
311 output.push(page.slice(slice));
312 continue;
313 }
314 let len = usize::try_from(range.end - range.start).map_err(|_| invalid_request())?;
315 let mut assembled = BytesMut::with_capacity(len);
316 for id in ids {
317 let slice = layout
318 .request_slice(id, range.clone())
319 .map_err(Arc::new)?
320 .ok_or_else(invalid_request)?;
321 let page = pages.get(&id).ok_or_else(invalid_request)?;
322 if slice.end > page.len() {
323 return Err(invalid_request());
324 }
325 assembled.extend_from_slice(&page[slice]);
326 }
327 if assembled.len() != len {
328 return Err(invalid_request());
329 }
330 output.push(assembled.freeze());
331 }
332 self.observe_residency();
333 Ok((output, stats))
334 }
335
336 fn create_cached_page(&self, file_size: u64, bytes: Bytes) -> Arc<CachedPage> {
337 (self.observer.events)(PageEvent::Loaded {
338 bytes: bytes.len() as u64,
339 });
340 Arc::new(CachedPage { file_size, bytes })
341 }
342
343 fn observe_residency(&self) {
344 (self.observer.events)(PageEvent::Residency {
345 usage: self.cache.weighted_size(),
346 capacity: self.cache.capacity(),
347 });
348 }
349}
350
351struct CachedPage {
352 file_size: u64,
353 bytes: Bytes,
354}
355
356fn invalid_request() -> Arc<io::Error> {
357 Arc::new(io::Error::new(
358 io::ErrorKind::InvalidInput,
359 "Invalid cached page range",
360 ))
361}
362
363fn check_file_size(page: &CachedPage, expected: u64) -> Result<(), Arc<io::Error>> {
364 if page.file_size != expected {
365 return Err(Arc::new(io::Error::new(
366 io::ErrorKind::InvalidInput,
367 "Cached content identity used with inconsistent file sizes",
368 )));
369 }
370 Ok(())
371}
372
373fn unique_requested_bytes(ranges: &[Range<u64>]) -> u64 {
375 let mut ranges = ranges.to_vec();
376 ranges.sort_unstable_by_key(|range| range.start);
377 let mut end = 0;
378 let mut bytes = 0;
379 for range in ranges {
380 bytes += range.end.saturating_sub(range.start.max(end));
381 end = end.max(range.end);
382 }
383 bytes
384}