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