From e2f99a1ad26a12dbd39792de97e698d0c5873dbe Mon Sep 17 00:00:00 2001 From: Alecco Date: Sat, 19 Apr 2025 18:48:19 +0200 Subject: [PATCH] page_cache: implement resize --- core/storage/page_cache.rs | 102 +++++++++++++++++++++++++++++++++++-- core/storage/pager.rs | 7 ++- 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/core/storage/page_cache.rs b/core/storage/page_cache.rs index 5aeadb8ed..0d3047d9e 100644 --- a/core/storage/page_cache.rs +++ b/core/storage/page_cache.rs @@ -51,6 +51,12 @@ pub enum CacheError { KeyExists, } +#[derive(Debug, PartialEq)] +pub enum CacheResizeResult { + Done, + PendingEvictions, +} + impl PageCacheKey { pub fn new(pgno: usize, max_frame: Option) -> Self { Self { pgno, max_frame } @@ -136,9 +142,14 @@ impl DumbLruPageCache { Some(page) } - pub fn resize(&mut self, capacity: usize) { - let _ = capacity; - todo!(); + // To match SQLite behavior, just set capacity and try to shrink as much as possible. + // In case of failure, the caller should request further evictions (e.g. after I/O). + pub fn resize(&mut self, capacity: usize) -> CacheResizeResult { + self.capacity = capacity; + match self.make_room_for(0) { + Ok(_) => CacheResizeResult::Done, + Err(_) => CacheResizeResult::PendingEvictions, + } } fn detach( @@ -923,4 +934,89 @@ mod tests { assert_eq!(cache.peek(&key, false).unwrap().get().id, i); } } + + #[test] + fn test_resize_smaller_success() { + let mut cache = DumbLruPageCache::new(5); + for i in 1..=5 { + let _ = insert_page(&mut cache, i); + } + assert_eq!(cache.len(), 5); + let result = cache.resize(3); + assert_eq!(result, CacheResizeResult::Done); + assert_eq!(cache.len(), 3); + assert_eq!(cache.capacity, 3); + assert!(cache.insert(create_key(6), page_with_content(6)).is_ok()); + } + + #[test] + fn test_resize_larger() { + let mut cache = DumbLruPageCache::new(2); + let _ = insert_page(&mut cache, 1); + let _ = insert_page(&mut cache, 2); + assert_eq!(cache.len(), 2); + let result = cache.resize(5); + assert_eq!(result, CacheResizeResult::Done); + assert_eq!(cache.len(), 2); + assert_eq!(cache.capacity, 5); + assert!(cache.get(&create_key(1)).is_some()); + assert!(cache.get(&create_key(2)).is_some()); + for i in 3..=5 { + let _ = insert_page(&mut cache, i); + } + assert_eq!(cache.len(), 5); + assert!(cache.insert(create_key(4), page_with_content(4)).is_err()); + cache.verify_list_integrity(); + } + + #[test] + fn test_resize_with_active_references() { + let mut cache = DumbLruPageCache::new(5); + let page1 = page_with_content(1); + let page2 = page_with_content(2); + let page3 = page_with_content(3); + assert!(cache.insert(create_key(1), page1.clone()).is_ok()); + assert!(cache.insert(create_key(2), page2.clone()).is_ok()); + assert!(cache.insert(create_key(3), page3.clone()).is_ok()); + assert_eq!(cache.len(), 3); + cache.verify_list_integrity(); + assert_eq!(cache.resize(2), CacheResizeResult::PendingEvictions); + assert_eq!(cache.capacity, 2); + assert_eq!(cache.len(), 3); + drop(page2); + drop(page3); + assert_eq!(cache.resize(1), CacheResizeResult::Done); // Evicted 2 and 3 + assert_eq!(cache.len(), 1); + assert!(cache.insert(create_key(4), page_with_content(4)).is_err()); + cache.verify_list_integrity(); + } + + #[test] + fn test_resize_to_zero() { + let mut cache = DumbLruPageCache::new(5); + for i in 1..=3 { + let _ = insert_page(&mut cache, i); + } + assert_eq!(cache.len(), 3); + let result = cache.resize(0); + assert_eq!(result, CacheResizeResult::Done); // removed all 3 + assert_eq!(cache.len(), 0); + assert_eq!(cache.capacity, 0); + cache.verify_list_integrity(); + assert!(cache.insert(create_key(4), page_with_content(4)).is_err()); + } + + #[test] + fn test_resize_same_capacity() { + let mut cache = DumbLruPageCache::new(3); + for i in 1..=3 { + let _ = insert_page(&mut cache, i); + } + let result = cache.resize(3); + assert_eq!(result, CacheResizeResult::Done); // no-op + assert_eq!(cache.len(), 3); + assert_eq!(cache.capacity, 3); + cache.verify_list_integrity(); + assert!(cache.insert(create_key(4), page_with_content(4)).is_ok()); + } } diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 7b52100dd..96e552b55 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -13,7 +13,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use tracing::trace; -use super::page_cache::{CacheError, DumbLruPageCache, PageCacheKey}; +use super::page_cache::{CacheError, DumbLruPageCache, PageCacheKey, CacheResizeResult}; use super::wal::{CheckpointMode, CheckpointStatus}; pub struct PageInner { @@ -405,10 +405,9 @@ impl Pager { } /// Changes the size of the page cache. - // FIXME: handle no room in page cache - pub fn change_page_cache_size(&self, capacity: usize) { + pub fn change_page_cache_size(&self, capacity: usize) -> Result { let mut page_cache = self.page_cache.write(); - page_cache.resize(capacity) + Ok(page_cache.resize(capacity)) } pub fn add_dirty(&self, page_id: usize) {