1#[cfg(target_arch = "wasm32")]
7use std::sync::Mutex;
8use std::{
9 num::NonZeroUsize,
10 sync::{
11 atomic::{AtomicU64, Ordering},
12 Arc,
13 },
14};
15
16use lru::LruCache;
17#[cfg(not(target_arch = "wasm32"))]
18use parking_lot::Mutex;
19
20use crate::{
21 page::{Page, PageId, PageManager},
22 StorageError,
23};
24
25macro_rules! lock {
29 ($mutex:expr) => {{
30 #[cfg(not(target_arch = "wasm32"))]
31 {
32 $mutex.lock()
33 }
34 #[cfg(target_arch = "wasm32")]
35 {
36 $mutex.lock().unwrap()
37 }
38 }};
39}
40
41#[derive(Debug, Default)]
43pub struct BufferPoolStats {
44 hits: AtomicU64,
46 misses: AtomicU64,
48 evictions: AtomicU64,
50}
51
52impl BufferPoolStats {
53 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn hits(&self) -> u64 {
60 self.hits.load(Ordering::Relaxed)
61 }
62
63 pub fn misses(&self) -> u64 {
65 self.misses.load(Ordering::Relaxed)
66 }
67
68 pub fn evictions(&self) -> u64 {
70 self.evictions.load(Ordering::Relaxed)
71 }
72
73 pub fn hit_rate(&self) -> f64 {
75 let hits = self.hits();
76 let misses = self.misses();
77 let total = hits + misses;
78 if total == 0 {
79 0.0
80 } else {
81 hits as f64 / total as f64
82 }
83 }
84
85 fn record_hit(&self) {
87 self.hits.fetch_add(1, Ordering::Relaxed);
88 }
89
90 fn record_miss(&self) {
92 self.misses.fetch_add(1, Ordering::Relaxed);
93 }
94
95 fn record_eviction(&self) {
97 self.evictions.fetch_add(1, Ordering::Relaxed);
98 }
99}
100
101#[derive(Debug)]
103pub struct BufferPool {
104 cache: Arc<Mutex<LruCache<PageId, Page>>>,
106 page_manager: Arc<PageManager>,
108 capacity: usize,
110 stats: BufferPoolStats,
112}
113
114impl BufferPool {
115 pub fn new(page_manager: Arc<PageManager>, capacity: usize) -> Self {
121 let capacity = if capacity == 0 { 1000 } else { capacity };
122 let cache = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(capacity).unwrap())));
123
124 BufferPool { cache, page_manager, capacity, stats: BufferPoolStats::new() }
125 }
126
127 pub fn get_page(&self, page_id: PageId) -> Result<Page, StorageError> {
135 {
137 let mut cache = lock!(self.cache);
138
139 if let Some(page) = cache.get(&page_id) {
140 self.stats.record_hit();
141 return Ok(page.clone());
142 }
143 }
144
145 self.stats.record_miss();
147 let page = self.page_manager.read_page(page_id)?;
148
149 self.put_page_internal(page.clone())?;
151
152 Ok(page)
153 }
154
155 pub fn put_page(&self, page: Page) -> Result<(), StorageError> {
160 self.put_page_internal(page)
161 }
162
163 fn put_page_internal(&self, page: Page) -> Result<(), StorageError> {
165 let mut cache = lock!(self.cache);
166
167 if let Some((_evicted_id, mut evicted_page)) = cache.push(page.id, page) {
169 if evicted_page.dirty {
171 drop(cache); self.page_manager.write_page(&mut evicted_page)?;
173 }
174 self.stats.record_eviction();
175 }
176
177 Ok(())
178 }
179
180 pub fn flush_dirty(&self) -> Result<(), StorageError> {
182 let cache = lock!(self.cache);
183
184 let dirty_pages: Vec<(PageId, Page)> = cache
186 .iter()
187 .filter(|(_, page)| page.dirty)
188 .map(|(id, page)| (*id, page.clone()))
189 .collect();
190
191 drop(cache); for (_id, mut page) in dirty_pages {
195 self.page_manager.write_page(&mut page)?;
196
197 let mut cache = lock!(self.cache);
199 if let Some(cached_page) = cache.get_mut(&page.id) {
200 cached_page.mark_clean();
201 }
202 }
203
204 Ok(())
205 }
206
207 pub fn evict(&self, page_id: PageId) -> Result<(), StorageError> {
212 let mut cache = lock!(self.cache);
213
214 if let Some(mut page) = cache.pop(&page_id) {
215 if page.dirty {
217 drop(cache); self.page_manager.write_page(&mut page)?;
219 }
220 self.stats.record_eviction();
221 }
222
223 Ok(())
224 }
225
226 pub fn capacity(&self) -> usize {
228 self.capacity
229 }
230
231 pub fn size(&self) -> Result<usize, StorageError> {
233 let cache = lock!(self.cache);
234 Ok(cache.len())
235 }
236
237 pub fn stats(&self) -> &BufferPoolStats {
239 &self.stats
240 }
241}
242
243#[cfg(test)]
244#[cfg(not(target_arch = "wasm32"))]
245mod tests {
246 use tempfile::TempDir;
247
248 use super::*;
249 use crate::NativeStorage;
250
251 #[test]
252 fn test_buffer_pool_creation() {
253 let temp_dir = TempDir::new().unwrap();
254 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
255 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
256 let buffer_pool = BufferPool::new(page_manager, 10);
257
258 assert_eq!(buffer_pool.capacity(), 10);
259 assert_eq!(buffer_pool.size().unwrap(), 0);
260 }
261
262 #[test]
263 fn test_cache_hit() {
264 let temp_dir = TempDir::new().unwrap();
265 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
266 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
267 let buffer_pool = BufferPool::new(page_manager.clone(), 10);
268
269 let mut page = Page::new(1);
271 page.data[0] = 42;
272 page.mark_dirty();
273 page_manager.write_page(&mut page).unwrap();
274
275 let page1 = buffer_pool.get_page(1).unwrap();
277 assert_eq!(page1.data[0], 42);
278 assert_eq!(buffer_pool.stats().misses(), 1);
279 assert_eq!(buffer_pool.stats().hits(), 0);
280
281 let page2 = buffer_pool.get_page(1).unwrap();
283 assert_eq!(page2.data[0], 42);
284 assert_eq!(buffer_pool.stats().misses(), 1);
285 assert_eq!(buffer_pool.stats().hits(), 1);
286 }
287
288 #[test]
289 fn test_cache_miss() {
290 let temp_dir = TempDir::new().unwrap();
291 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
292 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
293 let buffer_pool = BufferPool::new(page_manager.clone(), 10);
294
295 let page = buffer_pool.get_page(1).unwrap();
297 assert_eq!(page.id, 1);
298 assert_eq!(buffer_pool.stats().misses(), 1);
299 assert_eq!(buffer_pool.stats().hits(), 0);
300 }
301
302 #[test]
303 fn test_eviction() {
304 let temp_dir = TempDir::new().unwrap();
305 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
306 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
307 let buffer_pool = BufferPool::new(page_manager.clone(), 3);
308
309 for i in 1..=3 {
311 let mut page = Page::new(i);
312 page.data[0] = i as u8;
313 buffer_pool.put_page(page).unwrap();
314 }
315
316 assert_eq!(buffer_pool.size().unwrap(), 3);
317 assert_eq!(buffer_pool.stats().evictions(), 0);
318
319 let page4 = Page::new(4);
321 buffer_pool.put_page(page4).unwrap();
322
323 assert_eq!(buffer_pool.size().unwrap(), 3);
324 assert_eq!(buffer_pool.stats().evictions(), 1);
325 }
326
327 #[test]
328 fn test_dirty_page_write_on_eviction() {
329 let temp_dir = TempDir::new().unwrap();
330 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
331 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
332 let buffer_pool = BufferPool::new(page_manager.clone(), 2);
333
334 let mut page1 = Page::new(1);
336 page1.data[0] = 42;
337 page1.mark_dirty();
338 buffer_pool.put_page(page1).unwrap();
339
340 let page2 = Page::new(2);
342 buffer_pool.put_page(page2).unwrap();
343
344 let page3 = Page::new(3);
346 buffer_pool.put_page(page3).unwrap();
347
348 assert_eq!(buffer_pool.stats().evictions(), 1);
349
350 let read_page = page_manager.read_page(1).unwrap();
352 assert_eq!(read_page.data[0], 42);
353 }
354
355 #[test]
356 fn test_flush_dirty_pages() {
357 let temp_dir = TempDir::new().unwrap();
358 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
359 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
360 let buffer_pool = BufferPool::new(page_manager.clone(), 10);
361
362 for i in 1..=5 {
364 let mut page = Page::new(i);
365 page.data[0] = i as u8;
366 page.mark_dirty();
367 buffer_pool.put_page(page).unwrap();
368 }
369
370 buffer_pool.flush_dirty().unwrap();
372
373 for i in 1..=5 {
375 let page = page_manager.read_page(i).unwrap();
376 assert_eq!(page.data[0], i as u8);
377 }
378 }
379
380 #[test]
381 fn test_cache_statistics() {
382 let temp_dir = TempDir::new().unwrap();
383 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
384 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
385 let buffer_pool = BufferPool::new(page_manager.clone(), 10);
386
387 buffer_pool.get_page(1).unwrap(); buffer_pool.get_page(1).unwrap(); buffer_pool.get_page(2).unwrap(); buffer_pool.get_page(1).unwrap(); buffer_pool.get_page(2).unwrap(); assert_eq!(buffer_pool.stats().hits(), 3);
395 assert_eq!(buffer_pool.stats().misses(), 2);
396 assert_eq!(buffer_pool.stats().hit_rate(), 0.6);
397 }
398
399 #[test]
400 fn test_manual_eviction() {
401 let temp_dir = TempDir::new().unwrap();
402 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
403 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
404 let buffer_pool = BufferPool::new(page_manager.clone(), 10);
405
406 let mut page = Page::new(1);
408 page.data[0] = 42;
409 page.mark_dirty();
410 buffer_pool.put_page(page).unwrap();
411
412 assert_eq!(buffer_pool.size().unwrap(), 1);
413
414 buffer_pool.evict(1).unwrap();
416
417 assert_eq!(buffer_pool.size().unwrap(), 0);
418 assert_eq!(buffer_pool.stats().evictions(), 1);
419
420 let read_page = page_manager.read_page(1).unwrap();
422 assert_eq!(read_page.data[0], 42);
423 }
424
425 #[test]
426 fn test_zero_capacity_defaults_to_1000() {
427 let temp_dir = TempDir::new().unwrap();
428 let storage = Arc::new(NativeStorage::new(temp_dir.path()).unwrap());
429 let page_manager = Arc::new(PageManager::new("test.db", storage).unwrap());
430 let buffer_pool = BufferPool::new(page_manager, 0);
431
432 assert_eq!(buffer_pool.capacity(), 1000);
433 }
434}