1use crate::error::{Result, TdbError};
7use crate::storage::file_manager::FileManager;
8use crate::storage::page::{Page, PageId, PageType};
9use dashmap::DashMap;
10use parking_lot::{Mutex, RwLock};
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::Arc;
13
14pub type FrameId = usize;
16
17enum LoadSource {
19 Disk,
21 New(PageType),
23}
24
25struct BufferFrame {
27 page: RwLock<Option<Page>>,
29 access_count: AtomicU64,
31 pin_count: AtomicU64,
33}
34
35impl BufferFrame {
36 fn new() -> Self {
37 Self {
38 page: RwLock::new(None),
39 access_count: AtomicU64::new(0),
40 pin_count: AtomicU64::new(0),
41 }
42 }
43
44 fn is_pinned(&self) -> bool {
45 self.pin_count.load(Ordering::Acquire) > 0
46 }
47
48 fn pin(&self) {
49 self.pin_count.fetch_add(1, Ordering::AcqRel);
50 }
51
52 fn unpin(&self) {
53 let prev = self.pin_count.fetch_sub(1, Ordering::AcqRel);
54 debug_assert!(prev > 0, "Unpin called on unpinned frame");
55 }
56
57 fn access(&self) {
58 self.access_count.fetch_add(1, Ordering::AcqRel);
59 }
60
61 fn get_access_count(&self) -> u64 {
62 self.access_count.load(Ordering::Acquire)
63 }
64}
65
66pub struct BufferPool {
71 frames: Vec<BufferFrame>,
73 page_table: DashMap<PageId, FrameId>,
75 file_manager: Arc<FileManager>,
77 clock_hand: AtomicU64,
79 load_lock: Mutex<()>,
84 stats: BufferPoolStats,
86}
87
88#[derive(Debug, Default)]
90pub struct BufferPoolStats {
91 pub total_fetches: AtomicU64,
93 pub cache_hits: AtomicU64,
95 pub cache_misses: AtomicU64,
97 pub evictions: AtomicU64,
99 pub writes: AtomicU64,
101}
102
103impl Clone for BufferPoolStats {
104 fn clone(&self) -> Self {
105 Self {
106 total_fetches: AtomicU64::new(self.total_fetches.load(Ordering::Relaxed)),
107 cache_hits: AtomicU64::new(self.cache_hits.load(Ordering::Relaxed)),
108 cache_misses: AtomicU64::new(self.cache_misses.load(Ordering::Relaxed)),
109 evictions: AtomicU64::new(self.evictions.load(Ordering::Relaxed)),
110 writes: AtomicU64::new(self.writes.load(Ordering::Relaxed)),
111 }
112 }
113}
114
115impl BufferPoolStats {
116 pub fn hit_rate(&self) -> f64 {
118 let total = self.total_fetches.load(Ordering::Relaxed);
119 if total == 0 {
120 0.0
121 } else {
122 let hits = self.cache_hits.load(Ordering::Relaxed);
123 hits as f64 / total as f64
124 }
125 }
126
127 pub fn reset(&self) {
129 self.total_fetches.store(0, Ordering::Relaxed);
130 self.cache_hits.store(0, Ordering::Relaxed);
131 self.cache_misses.store(0, Ordering::Relaxed);
132 self.evictions.store(0, Ordering::Relaxed);
133 self.writes.store(0, Ordering::Relaxed);
134 }
135
136 pub fn snapshot(&self) -> Self {
138 Self {
139 total_fetches: AtomicU64::new(self.total_fetches.load(Ordering::Relaxed)),
140 cache_hits: AtomicU64::new(self.cache_hits.load(Ordering::Relaxed)),
141 cache_misses: AtomicU64::new(self.cache_misses.load(Ordering::Relaxed)),
142 evictions: AtomicU64::new(self.evictions.load(Ordering::Relaxed)),
143 writes: AtomicU64::new(self.writes.load(Ordering::Relaxed)),
144 }
145 }
146}
147
148impl BufferPool {
149 pub fn new(pool_size: usize, file_manager: Arc<FileManager>) -> Self {
151 let mut frames = Vec::with_capacity(pool_size);
152 for _ in 0..pool_size {
153 frames.push(BufferFrame::new());
154 }
155
156 Self {
157 frames,
158 page_table: DashMap::new(),
159 file_manager,
160 clock_hand: AtomicU64::new(0),
161 load_lock: Mutex::new(()),
162 stats: BufferPoolStats::default(),
163 }
164 }
165
166 pub fn file_manager(&self) -> &Arc<FileManager> {
169 &self.file_manager
170 }
171
172 pub fn fetch_page(&self, page_id: PageId) -> Result<PageGuard<'_>> {
174 self.stats.total_fetches.fetch_add(1, Ordering::Relaxed);
175
176 if let Some(guard) = self.try_hit(page_id) {
180 self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
181 return Ok(guard);
182 }
183
184 let _load = self.load_lock.lock();
186
187 if let Some(guard) = self.try_hit(page_id) {
190 self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
191 return Ok(guard);
192 }
193
194 self.stats.cache_misses.fetch_add(1, Ordering::Relaxed);
195 let frame_id = self.evict_and_load(page_id, LoadSource::Disk)?;
196 Ok(PageGuard {
197 frame_id,
198 page_id,
199 buffer_pool: self,
200 })
201 }
202
203 pub fn new_page(&self, page_type: PageType) -> Result<PageGuard<'_>> {
205 let _load = self.load_lock.lock();
208 let page_id = self.file_manager.allocate_page()?;
209 let frame_id = self.evict_and_load(page_id, LoadSource::New(page_type))?;
210 Ok(PageGuard {
211 frame_id,
212 page_id,
213 buffer_pool: self,
214 })
215 }
216
217 fn try_hit(&self, page_id: PageId) -> Option<PageGuard<'_>> {
225 let frame_id = *self.page_table.get(&page_id)?;
226 let frame = &self.frames[frame_id];
227
228 let read = frame.page.read();
230 let holds_page = read.as_ref().map(|p| p.page_id()) == Some(page_id);
231 let still_mapped = self.page_table.get(&page_id).map(|e| *e) == Some(frame_id);
232 if holds_page && still_mapped {
233 frame.pin();
234 frame.access();
235 drop(read);
236 Some(PageGuard {
237 frame_id,
238 page_id,
239 buffer_pool: self,
240 })
241 } else {
242 None
243 }
244 }
245
246 fn evict_and_load(&self, page_id: PageId, source: LoadSource) -> Result<FrameId> {
253 let pool_size = self.frames.len();
254 let mut checked = 0usize;
255 let max_checks = pool_size * 4; loop {
258 if checked >= max_checks {
259 return Err(TdbError::BufferPoolFull);
260 }
261
262 let hand = self.clock_hand.fetch_add(1, Ordering::AcqRel) as usize % pool_size;
263 let frame = &self.frames[hand];
264
265 if frame.is_pinned() {
267 checked += 1;
268 continue;
269 }
270
271 let mut page_guard = frame.page.write();
273
274 if frame.is_pinned() {
277 drop(page_guard);
278 checked += 1;
279 continue;
280 }
281
282 if page_guard.is_some() {
283 if frame.get_access_count() > 0 {
285 frame.access_count.fetch_sub(1, Ordering::AcqRel);
286 drop(page_guard);
287 checked += 1;
288 continue;
289 }
290
291 let is_dirty = page_guard.as_ref().map(|p| p.is_dirty()).unwrap_or(false);
293 let old_id = page_guard.as_ref().map(|p| p.page_id());
294 if is_dirty {
295 if let Some(mut old_page) = page_guard.take() {
296 self.file_manager.write_page(&mut old_page)?;
297 self.stats.writes.fetch_add(1, Ordering::Relaxed);
298 }
299 } else {
300 *page_guard = None;
301 }
302 if let Some(old_id) = old_id {
303 self.page_table.remove(&old_id);
304 }
305 self.stats.evictions.fetch_add(1, Ordering::Relaxed);
306 }
307
308 let new_page = match source {
310 LoadSource::Disk => self.file_manager.read_page(page_id)?,
311 LoadSource::New(page_type) => Page::new(page_id, page_type),
312 };
313 *page_guard = Some(new_page);
314
315 self.page_table.insert(page_id, hand);
319 frame.pin();
320 frame.access();
321 drop(page_guard);
322 return Ok(hand);
323 }
324 }
325
326 pub fn flush_page(&self, page_id: PageId) -> Result<()> {
328 if let Some(frame_entry) = self.page_table.get(&page_id) {
329 let frame_id = *frame_entry;
330 let frame = &self.frames[frame_id];
331 let mut page_guard = frame.page.write();
332
333 if let Some(page) = page_guard.as_mut() {
334 if page.is_dirty() {
335 self.file_manager.write_page(page)?;
336 self.stats.writes.fetch_add(1, Ordering::Relaxed);
337 }
338 }
339 }
340 Ok(())
341 }
342
343 pub fn flush_all(&self) -> Result<()> {
345 for entry in self.page_table.iter() {
346 let frame_id = *entry.value();
347 let frame = &self.frames[frame_id];
348 let mut page_guard = frame.page.write();
349
350 if let Some(page) = page_guard.as_mut() {
351 if page.is_dirty() {
352 self.file_manager.write_page(page)?;
353 self.stats.writes.fetch_add(1, Ordering::Relaxed);
354 }
355 }
356 }
357 self.file_manager.flush()?;
358 Ok(())
359 }
360
361 pub fn discard_page(&self, page_id: PageId) -> bool {
376 let _load = self.load_lock.lock();
379
380 let frame_id = match self.page_table.get(&page_id).map(|e| *e) {
381 Some(fid) => fid,
382 None => return true, };
384
385 let frame = &self.frames[frame_id];
386 if frame.is_pinned() {
387 return false;
389 }
390
391 let mut page_guard = frame.page.write();
392 if page_guard.as_ref().map(|p| p.page_id()) == Some(page_id) {
394 *page_guard = None;
395 frame.access_count.store(0, Ordering::Release);
396 self.page_table.remove(&page_id);
397 }
398 true
399 }
400
401 fn unpin_page(&self, frame_id: FrameId) {
403 self.frames[frame_id].unpin();
404 }
405
406 pub fn stats(&self) -> BufferPoolStats {
408 self.stats.snapshot()
409 }
410
411 pub fn pool_size(&self) -> usize {
413 self.frames.len()
414 }
415
416 pub fn cached_pages(&self) -> usize {
418 self.page_table.len()
419 }
420}
421
422pub struct PageGuard<'a> {
426 frame_id: FrameId,
427 page_id: PageId,
428 buffer_pool: &'a BufferPool,
429}
430
431impl<'a> PageGuard<'a> {
432 pub fn page(&self) -> parking_lot::RwLockReadGuard<'_, Option<Page>> {
438 let guard = self.buffer_pool.frames[self.frame_id].page.read();
439 debug_assert!(
440 guard.as_ref().map(|p| p.page_id()) == Some(self.page_id),
441 "PageGuard identity mismatch on frame {}: expected page {}",
442 self.frame_id,
443 self.page_id
444 );
445 guard
446 }
447
448 pub fn page_mut(&self) -> parking_lot::RwLockWriteGuard<'_, Option<Page>> {
450 let guard = self.buffer_pool.frames[self.frame_id].page.write();
451 debug_assert!(
452 guard.as_ref().map(|p| p.page_id()) == Some(self.page_id),
453 "PageGuard identity mismatch on frame {}: expected page {}",
454 self.frame_id,
455 self.page_id
456 );
457 guard
458 }
459
460 pub fn page_checked(&self) -> Result<parking_lot::RwLockReadGuard<'_, Option<Page>>> {
467 let guard = self.buffer_pool.frames[self.frame_id].page.read();
468 match guard.as_ref().map(|p| p.page_id()) {
469 Some(actual) if actual == self.page_id => Ok(guard),
470 Some(actual) => Err(TdbError::PageIdMismatch {
471 expected: self.page_id,
472 actual,
473 }),
474 None => Err(TdbError::PageNotFound(self.page_id)),
475 }
476 }
477
478 pub fn page_mut_checked(&self) -> Result<parking_lot::RwLockWriteGuard<'_, Option<Page>>> {
481 let guard = self.buffer_pool.frames[self.frame_id].page.write();
482 match guard.as_ref().map(|p| p.page_id()) {
483 Some(actual) if actual == self.page_id => Ok(guard),
484 Some(actual) => Err(TdbError::PageIdMismatch {
485 expected: self.page_id,
486 actual,
487 }),
488 None => Err(TdbError::PageNotFound(self.page_id)),
489 }
490 }
491
492 pub fn page_id(&self) -> PageId {
494 self.page_id
495 }
496}
497
498impl<'a> Drop for PageGuard<'a> {
499 fn drop(&mut self) {
500 self.buffer_pool.unpin_page(self.frame_id);
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use tempfile::NamedTempFile;
508
509 #[test]
510 fn test_buffer_pool_creation() {
511 let temp_file = NamedTempFile::new().unwrap();
512 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
513 let bp = BufferPool::new(10, fm);
514
515 assert_eq!(bp.pool_size(), 10);
516 assert_eq!(bp.cached_pages(), 0);
517 }
518
519 #[test]
520 fn test_buffer_pool_new_page() {
521 let temp_file = NamedTempFile::new().unwrap();
522 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
523 let bp = BufferPool::new(10, fm);
524
525 let guard = bp.new_page(PageType::BTreeLeaf).unwrap();
526 assert_eq!(guard.page_id(), 0);
527 assert_eq!(bp.cached_pages(), 1);
528 }
529
530 #[test]
531 fn test_buffer_pool_fetch_page() {
532 let temp_file = NamedTempFile::new().unwrap();
533 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
534 let bp = BufferPool::new(10, fm.clone());
535
536 let page_id = {
538 let guard = bp.new_page(PageType::BTreeLeaf).unwrap();
539 let mut page = guard.page_mut();
540 page.as_mut().unwrap().write_at(0, b"test data").unwrap();
541 guard.page_id()
542 };
543
544 bp.flush_page(page_id).unwrap();
546
547 let guard = bp.fetch_page(page_id).unwrap();
549 let page = guard.page();
550 let data = page.as_ref().unwrap().read_at(0, 9).unwrap();
551 assert_eq!(data, b"test data");
552 }
553
554 #[test]
555 fn test_buffer_pool_cache_hit() {
556 let temp_file = NamedTempFile::new().unwrap();
557 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
558 let bp = BufferPool::new(10, fm);
559
560 let guard1 = bp.new_page(PageType::BTreeLeaf).unwrap();
561 let page_id = guard1.page_id();
562 drop(guard1);
563
564 let _guard2 = bp.fetch_page(page_id).unwrap();
566
567 let stats = bp.stats();
568 assert!(stats.cache_hits.load(Ordering::Relaxed) > 0);
569 }
570
571 #[test]
572 fn test_buffer_pool_eviction() {
573 let temp_file = NamedTempFile::new().unwrap();
574 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
575 let bp = BufferPool::new(3, fm); let _g1 = bp.new_page(PageType::BTreeLeaf).unwrap();
579 let _g2 = bp.new_page(PageType::BTreeLeaf).unwrap();
580 let _g3 = bp.new_page(PageType::BTreeLeaf).unwrap();
581
582 assert_eq!(bp.cached_pages(), 3);
583
584 drop(_g1);
586 drop(_g2);
587 drop(_g3);
588
589 let _g4 = bp.new_page(PageType::BTreeLeaf).unwrap();
591
592 let stats = bp.stats();
593 assert!(stats.evictions.load(Ordering::Relaxed) > 0);
594 }
595
596 #[test]
597 fn test_buffer_pool_flush_all() {
598 let temp_file = NamedTempFile::new().unwrap();
599 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
600 let bp = BufferPool::new(10, fm);
601
602 {
604 let g1 = bp.new_page(PageType::BTreeLeaf).unwrap();
605 let mut page1 = g1.page_mut();
606 page1.as_mut().unwrap().write_at(0, b"data1").unwrap();
607 }
608 {
609 let g2 = bp.new_page(PageType::BTreeLeaf).unwrap();
610 let mut page2 = g2.page_mut();
611 page2.as_mut().unwrap().write_at(0, b"data2").unwrap();
612 }
613
614 bp.flush_all().unwrap();
615
616 let stats = bp.stats();
617 assert!(stats.writes.load(Ordering::Relaxed) >= 2);
618 }
619
620 #[test]
621 fn test_buffer_pool_hit_rate() {
622 let temp_file = NamedTempFile::new().unwrap();
623 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
624 let bp = BufferPool::new(10, fm);
625
626 let guard = bp.new_page(PageType::BTreeLeaf).unwrap();
627 let page_id = guard.page_id();
628 drop(guard);
629
630 for _ in 0..5 {
632 let _g = bp.fetch_page(page_id).unwrap();
633 }
634
635 let stats = bp.stats();
636 assert!(stats.hit_rate() > 0.8); }
638
639 #[test]
640 fn test_buffer_pool_pin_prevents_eviction() {
641 let temp_file = NamedTempFile::new().unwrap();
642 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
643 let bp = BufferPool::new(2, fm); let g1 = bp.new_page(PageType::BTreeLeaf).unwrap();
646 let page_id1 = g1.page_id();
647 let g2 = bp.new_page(PageType::BTreeLeaf).unwrap();
650 drop(g2);
651
652 let result = bp.new_page(PageType::BTreeLeaf);
654
655 assert!(result.is_ok());
657
658 assert!(bp.page_table.contains_key(&page_id1));
660 }
661
662 #[test]
663 fn test_page_guard_checked_matches_identity() {
664 let temp_file = NamedTempFile::new().unwrap();
665 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
666 let bp = BufferPool::new(10, fm);
667
668 let guard = bp.new_page(PageType::BTreeLeaf).unwrap();
669 assert!(guard.page_checked().is_ok());
673 assert!(guard.page_mut_checked().is_ok());
674 }
675
676 #[test]
677 fn test_buffer_pool_concurrent_no_cross_page_corruption() {
678 use std::thread;
679
680 let temp_file = NamedTempFile::new().unwrap();
681 let fm = Arc::new(FileManager::open(temp_file.path(), false).unwrap());
682 let bp = Arc::new(BufferPool::new(8, fm));
685 let num_pages: u64 = 64;
686
687 for _ in 0..num_pages {
689 let guard = bp.new_page(PageType::BTreeLeaf).unwrap();
690 let pid = guard.page_id();
691 {
692 let mut page = guard.page_mut();
693 page.as_mut()
694 .unwrap()
695 .write_at(0, &pid.to_le_bytes())
696 .unwrap();
697 }
698 }
699 bp.flush_all().unwrap();
700
701 let mut handles = Vec::new();
702 for _ in 0..8 {
703 let bp = bp.clone();
704 handles.push(thread::spawn(move || {
705 for i in 0..1000u64 {
706 let pid = i % num_pages;
707 let guard = bp.fetch_page(pid).expect("fetch");
708 let page_lock = guard.page_checked().expect("frame identity");
709 let page = page_lock.as_ref().expect("resident");
710 let bytes = page.read_at(0, 8).expect("read");
711 let mut arr = [0u8; 8];
712 arr.copy_from_slice(bytes);
713 assert_eq!(
714 u64::from_le_bytes(arr),
715 pid,
716 "buffer frame returned bytes belonging to a different page"
717 );
718 }
719 }));
720 }
721 for h in handles {
722 h.join().expect("thread panicked (corruption detected)");
723 }
724 }
725}