1use ahash::AHashMap;
8use parking_lot::Mutex;
9
10use crate::page::{Page, PageId, PageManager};
11use mentedb_core::error::{MenteError, MenteResult};
12use tracing::{debug, trace};
13
14type FrameId = usize;
15
16struct Frame {
18 page: Box<Page>,
19 page_id: Option<PageId>,
20 pin_count: u32,
21 dirty: bool,
22 reference: bool,
24}
25
26impl Frame {
27 fn new() -> Self {
28 Self {
29 page: Box::new(Page::zeroed()),
30 page_id: None,
31 pin_count: 0,
32 dirty: false,
33 reference: false,
34 }
35 }
36}
37
38struct BufferPoolInner {
39 frames: Vec<Frame>,
40 page_table: AHashMap<PageId, FrameId>,
41 clock_hand: usize,
42 capacity: usize,
43}
44
45pub struct BufferPool {
47 inner: Mutex<BufferPoolInner>,
48}
49
50impl BufferPool {
51 pub fn new(capacity: usize) -> Self {
53 assert!(capacity > 0, "buffer pool capacity must be > 0");
54 let frames = (0..capacity).map(|_| Frame::new()).collect();
55 Self {
56 inner: Mutex::new(BufferPoolInner {
57 frames,
58 page_table: AHashMap::with_capacity(capacity),
59 clock_hand: 0,
60 capacity,
61 }),
62 }
63 }
64
65 pub fn fetch_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<Box<Page>> {
70 let mut inner = self.inner.lock();
71
72 if let Some(&frame_id) = inner.page_table.get(&page_id) {
74 let frame = &mut inner.frames[frame_id];
75 frame.pin_count += 1;
76 frame.reference = true;
77 trace!(page_id = page_id.0, frame_id, "buffer pool hit");
78 return Ok(frame.page.clone());
79 }
80
81 let frame_id = Self::find_victim(&mut inner)?;
83
84 if inner.frames[frame_id].dirty
86 && let Some(old_pid) = inner.frames[frame_id].page_id
87 {
88 pm.write_page(old_pid, &inner.frames[frame_id].page)?;
89 debug!(page_id = old_pid.0, frame_id, "flushed dirty victim");
90 }
91
92 if let Some(old_pid) = inner.frames[frame_id].page_id {
94 inner.page_table.remove(&old_pid);
95 }
96
97 let page = pm.read_page(page_id)?;
99 {
100 let frame = &mut inner.frames[frame_id];
101 *frame.page = *page;
102 frame.page_id = Some(page_id);
103 frame.pin_count = 1;
104 frame.dirty = false;
105 frame.reference = true;
106 }
107
108 inner.page_table.insert(page_id, frame_id);
109 trace!(
110 page_id = page_id.0,
111 frame_id, "loaded page into buffer pool"
112 );
113
114 Ok(inner.frames[frame_id].page.clone())
115 }
116
117 pub fn pin_page(&self, page_id: PageId) -> MenteResult<()> {
119 let mut inner = self.inner.lock();
120 match inner.page_table.get(&page_id) {
121 Some(&fid) => {
122 inner.frames[fid].pin_count += 1;
123 Ok(())
124 }
125 None => Err(MenteError::Storage(format!(
126 "page {} not in buffer pool",
127 page_id.0
128 ))),
129 }
130 }
131
132 pub fn unpin_page(&self, page_id: PageId, dirty: bool) -> MenteResult<()> {
134 let mut inner = self.inner.lock();
135 match inner.page_table.get(&page_id) {
136 Some(&fid) => {
137 let frame = &mut inner.frames[fid];
138 if frame.pin_count > 0 {
139 frame.pin_count -= 1;
140 }
141 if dirty {
142 frame.dirty = true;
143 }
144 Ok(())
145 }
146 None => Err(MenteError::Storage(format!(
147 "page {} not in buffer pool",
148 page_id.0
149 ))),
150 }
151 }
152
153 pub fn invalidate(&self, page_id: PageId) {
159 let mut inner = self.inner.lock();
160 if let Some(fid) = inner.page_table.remove(&page_id) {
161 let frame = &mut inner.frames[fid];
162 frame.page_id = None;
163 frame.pin_count = 0;
164 frame.dirty = false;
165 frame.reference = false;
166 debug!(page_id = page_id.0, "invalidated cached page");
167 }
168 }
169
170 pub fn update_page(&self, page_id: PageId, page: &Page) -> MenteResult<()> {
172 let mut inner = self.inner.lock();
173 match inner.page_table.get(&page_id) {
174 Some(&fid) => {
175 let frame = &mut inner.frames[fid];
176 *frame.page = page.clone();
177 frame.dirty = true;
178 Ok(())
179 }
180 None => Err(MenteError::Storage(format!(
181 "page {} not in buffer pool",
182 page_id.0
183 ))),
184 }
185 }
186
187 pub fn flush_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<()> {
189 let mut inner = self.inner.lock();
190 match inner.page_table.get(&page_id) {
191 Some(&fid) => {
192 let frame = &mut inner.frames[fid];
193 if frame.dirty {
194 pm.write_page(page_id, &frame.page)?;
195 frame.dirty = false;
196 debug!(page_id = page_id.0, "flushed page");
197 }
198 Ok(())
199 }
200 None => Err(MenteError::Storage(format!(
201 "page {} not in buffer pool",
202 page_id.0
203 ))),
204 }
205 }
206
207 pub fn flush_all(&self, pm: &mut PageManager) -> MenteResult<()> {
209 let mut inner = self.inner.lock();
210 for frame in &mut inner.frames {
211 if frame.dirty
212 && let Some(pid) = frame.page_id
213 {
214 pm.write_page(pid, &frame.page)?;
215 frame.dirty = false;
216 }
217 }
218 debug!("flushed all dirty pages");
219 Ok(())
220 }
221
222 fn find_victim(inner: &mut BufferPoolInner) -> MenteResult<FrameId> {
224 let cap = inner.capacity;
225
226 for i in 0..cap {
228 if inner.frames[i].page_id.is_none() {
229 return Ok(i);
230 }
231 }
232
233 let max_sweeps = cap * 2;
235 for _ in 0..max_sweeps {
236 let idx = inner.clock_hand;
237 inner.clock_hand = (inner.clock_hand + 1) % cap;
238
239 let frame = &mut inner.frames[idx];
240 if frame.pin_count == 0 {
241 if !frame.reference {
242 return Ok(idx);
243 }
244 frame.reference = false;
245 }
246 }
247
248 Err(MenteError::Storage(
249 "buffer pool full: all pages are pinned".into(),
250 ))
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use crate::page::Page;
258
259 fn setup() -> (tempfile::TempDir, PageManager) {
260 let dir = tempfile::tempdir().unwrap();
261 let pm = PageManager::open(dir.path()).unwrap();
262 (dir, pm)
263 }
264
265 #[test]
266 fn test_fetch_and_cache_hit() {
267 let (_dir, mut pm) = setup();
268 let pool = BufferPool::new(4);
269
270 let pid = pm.allocate_page().unwrap();
271 let mut page = Page::zeroed();
272 page.header.page_id = pid.0;
273 page.data[0..3].copy_from_slice(b"abc");
274 pm.write_page(pid, &page).unwrap();
275
276 let p1 = pool.fetch_page(pid, &mut pm).unwrap();
278 assert_eq!(&p1.data[0..3], b"abc");
279
280 pool.unpin_page(pid, false).unwrap();
282
283 let p2 = pool.fetch_page(pid, &mut pm).unwrap();
285 assert_eq!(&p2.data[0..3], b"abc");
286 pool.unpin_page(pid, false).unwrap();
287 }
288
289 #[test]
290 fn test_dirty_flush() {
291 let (_dir, mut pm) = setup();
292 let pool = BufferPool::new(4);
293
294 let pid = pm.allocate_page().unwrap();
295
296 let mut page = Page::zeroed();
297 page.header.page_id = pid.0;
298 page.data[0] = 42;
299 pm.write_page(pid, &page).unwrap();
300
301 let _ = pool.fetch_page(pid, &mut pm).unwrap();
303 let mut modified = Page::zeroed();
304 modified.header.page_id = pid.0;
305 modified.data[0] = 99;
306 pool.update_page(pid, &modified).unwrap();
307 pool.unpin_page(pid, true).unwrap();
308
309 pool.flush_page(pid, &mut pm).unwrap();
311
312 let on_disk = pm.read_page(pid).unwrap();
314 assert_eq!(on_disk.data[0], 99);
315 }
316
317 #[test]
318 fn test_eviction() {
319 let (_dir, mut pm) = setup();
320 let pool = BufferPool::new(2); let p1 = pm.allocate_page().unwrap();
324 let p2 = pm.allocate_page().unwrap();
325 let p3 = pm.allocate_page().unwrap();
326
327 for pid in [p1, p2, p3] {
328 let mut page = Page::zeroed();
329 page.header.page_id = pid.0;
330 page.data[0] = pid.0 as u8;
331 pm.write_page(pid, &page).unwrap();
332 }
333
334 let _ = pool.fetch_page(p1, &mut pm).unwrap();
336 pool.unpin_page(p1, false).unwrap();
337 let _ = pool.fetch_page(p2, &mut pm).unwrap();
338 pool.unpin_page(p2, false).unwrap();
339
340 let page3 = pool.fetch_page(p3, &mut pm).unwrap();
342 assert_eq!(page3.data[0], p3.0 as u8);
343 pool.unpin_page(p3, false).unwrap();
344 }
345
346 #[test]
347 fn test_all_pinned_error() {
348 let (_dir, mut pm) = setup();
349 let pool = BufferPool::new(2);
350
351 let p1 = pm.allocate_page().unwrap();
352 let p2 = pm.allocate_page().unwrap();
353 let p3 = pm.allocate_page().unwrap();
354
355 for pid in [p1, p2, p3] {
356 let mut page = Page::zeroed();
357 page.header.page_id = pid.0;
358 pm.write_page(pid, &page).unwrap();
359 }
360
361 let _ = pool.fetch_page(p1, &mut pm).unwrap();
363 let _ = pool.fetch_page(p2, &mut pm).unwrap();
364
365 assert!(pool.fetch_page(p3, &mut pm).is_err());
367 }
368
369 #[test]
370 fn test_flush_all() {
371 let (_dir, mut pm) = setup();
372 let pool = BufferPool::new(4);
373
374 let p1 = pm.allocate_page().unwrap();
375 let p2 = pm.allocate_page().unwrap();
376
377 for pid in [p1, p2] {
378 let mut page = Page::zeroed();
379 page.header.page_id = pid.0;
380 pm.write_page(pid, &page).unwrap();
381 }
382
383 let _ = pool.fetch_page(p1, &mut pm).unwrap();
384 let _ = pool.fetch_page(p2, &mut pm).unwrap();
385
386 let mut mod1 = Page::zeroed();
387 mod1.data[0] = 0xAA;
388 pool.update_page(p1, &mod1).unwrap();
389
390 let mut mod2 = Page::zeroed();
391 mod2.data[0] = 0xBB;
392 pool.update_page(p2, &mod2).unwrap();
393
394 pool.unpin_page(p1, true).unwrap();
395 pool.unpin_page(p2, true).unwrap();
396
397 pool.flush_all(&mut pm).unwrap();
398
399 let d1 = pm.read_page(p1).unwrap();
400 let d2 = pm.read_page(p2).unwrap();
401 assert_eq!(d1.data[0], 0xAA);
402 assert_eq!(d2.data[0], 0xBB);
403 }
404}