1use super::*;
4use crate::async_lru_cache::AsyncLruCacheBackend;
5use tracing::trace;
6
7struct L2CacheBackend<S: Storage> {
9 file: Arc<S>,
11
12 header: Arc<Header>,
14}
15
16struct RefBlockCacheBackend<S: Storage> {
18 file: Arc<S>,
20
21 header: Arc<Header>,
23}
24
25impl<S: Storage> L2CacheBackend<S> {
26 pub fn new(file: Arc<S>, header: Arc<Header>) -> Self {
30 L2CacheBackend { file, header }
31 }
32}
33
34#[maybe_async(AFIT)]
35impl<S: Storage> AsyncLruCacheBackend for L2CacheBackend<S> {
36 type Key = HostCluster;
37 type Value = L2Table;
38
39 async fn load(&self, l2_cluster: HostCluster) -> io::Result<L2Table> {
40 trace!("Loading L2 table");
41
42 L2Table::load(
43 self.file.as_ref(),
44 &self.header,
45 l2_cluster,
46 self.header.l2_entries(),
47 )
48 .await
49 }
50
51 async fn flush(&self, l2_cluster: HostCluster, l2_table: &L2Table) -> io::Result<()> {
52 trace!("Flushing L2 table");
53 l2_table
54 .write_if_modified(self.file.as_ref(), l2_cluster)
55 .await
56 }
57
58 unsafe fn evict(&self, _l2_cluster: HostCluster, l2_table: L2Table) {
59 trace!(
60 "Evicting L2 table {}",
61 l2_table.get_offset().unwrap_or(HostOffset(0))
62 );
63 l2_table.clear_modified();
64 }
65}
66
67impl<S: Storage> RefBlockCacheBackend<S> {
68 pub fn new(file: Arc<S>, header: Arc<Header>) -> Self {
72 RefBlockCacheBackend { file, header }
73 }
74}
75
76#[maybe_async(AFIT)]
77impl<S: Storage> AsyncLruCacheBackend for RefBlockCacheBackend<S> {
78 type Key = HostCluster;
79 type Value = RefBlock;
80
81 async fn load(&self, rb_cluster: HostCluster) -> io::Result<RefBlock> {
82 RefBlock::load(self.file.as_ref(), &self.header, rb_cluster).await
83 }
84
85 async fn flush(&self, rb_cluster: HostCluster, refblock: &RefBlock) -> io::Result<()> {
86 refblock
87 .write_if_modified(self.file.as_ref(), rb_cluster)
88 .await
89 }
90
91 unsafe fn evict(&self, _rb_cluster: HostCluster, refblock: RefBlock) {
92 refblock.clear_modified();
93 }
94}
95
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
98enum CacheDependency {
99 #[default]
101 None,
102 L2DependsOnRb,
104 RbDependsOnL2,
106}
107
108pub(super) struct MetadataCaches<S: Storage> {
135 l2: AsyncLruCache<HostCluster, L2Table, L2CacheBackend<S>>,
137
138 rb: AsyncLruCache<HostCluster, RefBlock, RefBlockCacheBackend<S>>,
140
141 direction: RwLock<CacheDependency>,
147}
148
149#[maybe_async]
150impl<S: Storage> MetadataCaches<S> {
151 pub fn new(file: &Arc<S>, header: &Arc<Header>, l2_entries: usize, rb_entries: usize) -> Self {
156 let l2_backend = L2CacheBackend::new(Arc::clone(file), Arc::clone(header));
157 let rb_backend = RefBlockCacheBackend::new(Arc::clone(file), Arc::clone(header));
158
159 MetadataCaches {
160 l2: AsyncLruCache::new(l2_backend, l2_entries),
161 rb: AsyncLruCache::new(rb_backend, rb_entries),
162 direction: Default::default(),
163 }
164 }
165
166 pub async fn l2_depends_on_rb(&self) -> io::Result<()> {
170 let mut dir = self.direction.write().await;
171 if *dir == CacheDependency::L2DependsOnRb {
172 return Ok(());
173 }
174 if *dir == CacheDependency::RbDependsOnL2 {
175 self.l2.flush().await?;
176 }
177 *dir = CacheDependency::L2DependsOnRb;
178 Ok(())
179 }
180
181 pub async fn rb_depends_on_l2(&self) -> io::Result<()> {
185 let mut dir = self.direction.write().await;
186 if *dir == CacheDependency::RbDependsOnL2 {
187 return Ok(());
188 }
189 if *dir == CacheDependency::L2DependsOnRb {
190 self.rb.flush().await?;
191 }
192 *dir = CacheDependency::RbDependsOnL2;
193 Ok(())
194 }
195
196 pub async fn flush_all(&self) -> io::Result<()> {
198 let dir = self.direction.read().await;
199 if *dir == CacheDependency::L2DependsOnRb {
200 self.rb.flush().await?;
201 self.l2.flush().await?;
202 } else {
203 self.l2.flush().await?;
204 self.rb.flush().await?;
205 }
206
207 Ok(())
208 }
209
210 pub async fn l2_get_or_insert(&self, cluster_index: HostCluster) -> io::Result<Arc<L2Table>> {
214 let dir = self.direction.read().await;
215 if let Some(l2) = self.l2.get_or_insert(cluster_index, false).await? {
216 return Ok(l2);
217 }
218
219 if *dir == CacheDependency::L2DependsOnRb {
220 self.rb.flush().await?;
221 }
222
223 let l2 = self.l2.get_or_insert(cluster_index, true).await?.unwrap();
225 Ok(l2)
226 }
227
228 pub async fn l2_insert(&self, table: Arc<L2Table>) -> io::Result<()> {
234 let cluster = table
235 .get_cluster()
236 .ok_or_else(|| io::Error::other("L2 table does not have a cluster"))?;
237 let dir = self.direction.read().await;
238 if !self.l2.insert(cluster, Arc::clone(&table), false).await? {
239 if *dir == CacheDependency::L2DependsOnRb {
240 self.rb.flush().await?;
241 }
242
243 let inserted = self.l2.insert(cluster, table, true).await?;
244 assert!(inserted);
246 }
247
248 Ok(())
249 }
250
251 pub async unsafe fn invalidate_l2(&self) -> io::Result<()> {
256 unsafe { self.l2.invalidate() }.await
257 }
258
259 pub async fn rb_get_or_insert(&self, cluster_index: HostCluster) -> io::Result<Arc<RefBlock>> {
263 let dir = self.direction.read().await;
264 if let Some(rb) = self.rb.get_or_insert(cluster_index, false).await? {
265 return Ok(rb);
266 }
267
268 if *dir == CacheDependency::RbDependsOnL2 {
269 self.l2.flush().await?;
270 }
271
272 let rb = self.rb.get_or_insert(cluster_index, true).await?.unwrap();
274 Ok(rb)
275 }
276
277 pub async fn rb_insert(&self, rb: Arc<RefBlock>) -> io::Result<()> {
283 let cluster = rb
284 .get_cluster()
285 .ok_or_else(|| io::Error::other("Refblock does not have a cluster"))?;
286 let dir = self.direction.read().await;
287 if !self.rb.insert(cluster, Arc::clone(&rb), false).await? {
288 if *dir == CacheDependency::RbDependsOnL2 {
289 self.l2.flush().await?;
290 }
291
292 let inserted = self.rb.insert(cluster, rb, true).await?;
293 assert!(inserted);
295 }
296
297 Ok(())
298 }
299
300 pub async fn flush_rb(&self) -> io::Result<()> {
302 let dir = self.direction.read().await;
303 if *dir == CacheDependency::RbDependsOnL2 {
304 self.l2.flush().await?;
305 }
306 self.rb.flush().await
307 }
308
309 pub async unsafe fn invalidate_rb(&self) -> io::Result<()> {
314 unsafe { self.rb.invalidate() }.await
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::null::Null;
322
323 fn make_test_caches() -> MetadataCaches<Null> {
324 let null = Arc::new(Null::new(1 << 16));
325 let header = Arc::new(Header::new(16, 1, None, None, None));
326 MetadataCaches::new(&null, &header, 16, 16)
327 }
328
329 #[cfg(feature = "sync")]
330 fn block_on<T>(v: T) -> T {
331 v
332 }
333
334 #[cfg(feature = "async")]
335 fn block_on<F: std::future::Future>(f: F) -> F::Output {
336 tokio::runtime::Builder::new_current_thread()
337 .build()
338 .unwrap()
339 .block_on(f)
340 }
341
342 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
344 async fn test_direction_switch() {
345 let caches = make_test_caches();
346
347 caches.l2_depends_on_rb().await.unwrap();
348 caches.l2_depends_on_rb().await.unwrap();
349 caches.rb_depends_on_l2().await.unwrap();
350 caches.rb_depends_on_l2().await.unwrap();
351 caches.l2_depends_on_rb().await.unwrap();
352 }
353
354 #[test]
361 fn test_cross_cache_eviction_no_deadlock() {
362 use std::thread;
363
364 let null = Arc::new(Null::new(1 << 20));
365 let header = Arc::new(Header::new(16, 1, None, None, None));
366 let caches = Arc::new(MetadataCaches::new(&null, &header, 1, 1));
367
368 block_on(caches.l2_depends_on_rb()).unwrap();
369
370 let mut l2_entry = L2Table::new_cleared(&header);
373 l2_entry.set_cluster(HostCluster(0));
374 l2_entry.clear_modified();
375 let mut rb_entry = RefBlock::new_cleared(null.as_ref(), &header).unwrap();
376 rb_entry.set_cluster(HostCluster(0));
377 rb_entry.clear_modified();
378
379 block_on(caches.l2_insert(Arc::new(l2_entry))).unwrap();
380 block_on(caches.rb_insert(Arc::new(rb_entry))).unwrap();
381
382 let c1 = Arc::clone(&caches);
383 let c2 = Arc::clone(&caches);
384
385 let barrier = Arc::new(std::sync::Barrier::new(2));
386 let bar1 = Arc::clone(&barrier);
387 let bar2 = Arc::clone(&barrier);
388
389 let header1 = Arc::clone(&header);
391 let t1 = thread::spawn(move || {
392 let mut entry = L2Table::new_cleared(&header1);
393 entry.set_cluster(HostCluster(1));
394 entry.clear_modified();
395 bar1.wait();
396 block_on(c1.l2_insert(Arc::new(entry))).unwrap();
397 });
398
399 let header2 = Arc::clone(&header);
401 let null2 = Arc::new(Null::new(1 << 20));
402 let t2 = thread::spawn(move || {
403 let mut entry = RefBlock::new_cleared(null2.as_ref(), &header2).unwrap();
404 entry.set_cluster(HostCluster(1));
405 entry.clear_modified();
406 bar2.wait();
407 block_on(c2.rb_insert(Arc::new(entry))).unwrap();
408 });
409
410 t1.join().unwrap();
411 t2.join().unwrap();
412 }
413
414 #[test]
417 fn test_concurrent_direction_switch() {
418 use std::thread;
419
420 let caches = Arc::new(make_test_caches());
421
422 let c1 = Arc::clone(&caches);
423 let c2 = Arc::clone(&caches);
424
425 let barrier = Arc::new(std::sync::Barrier::new(2));
426 let bar1 = Arc::clone(&barrier);
427 let bar2 = Arc::clone(&barrier);
428
429 let t1 = thread::spawn(move || {
430 bar1.wait();
431 block_on(c1.l2_depends_on_rb()).unwrap();
432 });
433
434 let t2 = thread::spawn(move || {
435 bar2.wait();
436 block_on(c2.rb_depends_on_l2()).unwrap();
437 });
438
439 t1.join().unwrap();
440 t2.join().unwrap();
441 }
442
443 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
445 async fn test_direction_switch_flushes_opposing() {
446 let null = Arc::new(Null::new(1 << 20));
447 let header = Arc::new(Header::new(16, 1, None, None, None));
448 let caches = MetadataCaches::new(&null, &header, 16, 16);
449
450 let mut rb_entry = RefBlock::new_cleared(null.as_ref(), &header).unwrap();
452 rb_entry.set_cluster(HostCluster(0));
453 let rb_arc = Arc::new(rb_entry);
455 caches.rb_insert(Arc::clone(&rb_arc)).await.unwrap();
456
457 assert!(rb_arc.is_modified());
458
459 caches.l2_depends_on_rb().await.unwrap();
461 caches.rb_depends_on_l2().await.unwrap();
462
463 assert!(!rb_arc.is_modified());
465 }
466
467 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
472 async fn test_dep_flushed_before_eviction() {
473 let null = Arc::new(Null::new(1 << 20));
474 let header = Arc::new(Header::new(16, 1, None, None, None));
475 let caches = MetadataCaches::new(&null, &header, 1, 16);
476
477 caches.l2_depends_on_rb().await.unwrap();
478
479 let mut rb_entry = RefBlock::new_cleared(null.as_ref(), &header).unwrap();
481 rb_entry.set_cluster(HostCluster(0));
482 let rb_arc = Arc::new(rb_entry);
483 caches.rb_insert(Arc::clone(&rb_arc)).await.unwrap();
484
485 let mut l2_entry = L2Table::new_cleared(&header);
487 l2_entry.set_cluster(HostCluster(0));
488 l2_entry.clear_modified();
489 caches.l2_insert(Arc::new(l2_entry)).await.unwrap();
490
491 let mut l2_entry2 = L2Table::new_cleared(&header);
494 l2_entry2.set_cluster(HostCluster(1));
495 l2_entry2.clear_modified();
496 caches.l2_insert(Arc::new(l2_entry2)).await.unwrap();
497
498 assert!(
500 !rb_arc.is_modified(),
501 "rb entry must be flushed before l2 eviction"
502 );
503 }
504}