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 {
59 assert!(capacity > 0, "buffer pool capacity must be > 0");
60 Self {
61 inner: Mutex::new(BufferPoolInner {
62 frames: Vec::new(),
63 page_table: AHashMap::new(),
64 clock_hand: 0,
65 capacity,
66 }),
67 }
68 }
69
70 pub fn fetch_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<Box<Page>> {
75 let mut inner = self.inner.lock();
76
77 if let Some(&frame_id) = inner.page_table.get(&page_id) {
79 let frame = &mut inner.frames[frame_id];
80 frame.pin_count += 1;
81 frame.reference = true;
82 trace!(page_id = page_id.0, frame_id, "buffer pool hit");
83 return Ok(frame.page.clone());
84 }
85
86 let frame_id = Self::find_victim(&mut inner)?;
88
89 if inner.frames[frame_id].dirty
91 && let Some(old_pid) = inner.frames[frame_id].page_id
92 {
93 pm.write_page(old_pid, &inner.frames[frame_id].page)?;
94 debug!(page_id = old_pid.0, frame_id, "flushed dirty victim");
95 }
96
97 if let Some(old_pid) = inner.frames[frame_id].page_id {
99 inner.page_table.remove(&old_pid);
100 }
101
102 let page = pm.read_page(page_id)?;
104 {
105 let frame = &mut inner.frames[frame_id];
106 *frame.page = *page;
107 frame.page_id = Some(page_id);
108 frame.pin_count = 1;
109 frame.dirty = false;
110 frame.reference = true;
111 }
112
113 inner.page_table.insert(page_id, frame_id);
114 trace!(
115 page_id = page_id.0,
116 frame_id, "loaded page into buffer pool"
117 );
118
119 Ok(inner.frames[frame_id].page.clone())
120 }
121
122 pub fn pin_page(&self, page_id: PageId) -> MenteResult<()> {
124 let mut inner = self.inner.lock();
125 match inner.page_table.get(&page_id) {
126 Some(&fid) => {
127 inner.frames[fid].pin_count += 1;
128 Ok(())
129 }
130 None => Err(MenteError::Storage(format!(
131 "page {} not in buffer pool",
132 page_id.0
133 ))),
134 }
135 }
136
137 pub fn unpin_page(&self, page_id: PageId, dirty: bool) -> MenteResult<()> {
139 let mut inner = self.inner.lock();
140 match inner.page_table.get(&page_id) {
141 Some(&fid) => {
142 let frame = &mut inner.frames[fid];
143 if frame.pin_count > 0 {
144 frame.pin_count -= 1;
145 }
146 if dirty {
147 frame.dirty = true;
148 }
149 Ok(())
150 }
151 None => Err(MenteError::Storage(format!(
152 "page {} not in buffer pool",
153 page_id.0
154 ))),
155 }
156 }
157
158 pub fn invalidate(&self, page_id: PageId) {
164 let mut inner = self.inner.lock();
165 if let Some(fid) = inner.page_table.remove(&page_id) {
166 let frame = &mut inner.frames[fid];
167 frame.page_id = None;
168 frame.pin_count = 0;
169 frame.dirty = false;
170 frame.reference = false;
171 debug!(page_id = page_id.0, "invalidated cached page");
172 }
173 }
174
175 pub fn update_page(&self, page_id: PageId, page: &Page) -> MenteResult<()> {
177 let mut inner = self.inner.lock();
178 match inner.page_table.get(&page_id) {
179 Some(&fid) => {
180 let frame = &mut inner.frames[fid];
181 *frame.page = page.clone();
182 frame.dirty = true;
183 Ok(())
184 }
185 None => Err(MenteError::Storage(format!(
186 "page {} not in buffer pool",
187 page_id.0
188 ))),
189 }
190 }
191
192 pub fn flush_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<()> {
194 let mut inner = self.inner.lock();
195 match inner.page_table.get(&page_id) {
196 Some(&fid) => {
197 let frame = &mut inner.frames[fid];
198 if frame.dirty {
199 pm.write_page(page_id, &frame.page)?;
200 frame.dirty = false;
201 debug!(page_id = page_id.0, "flushed page");
202 }
203 Ok(())
204 }
205 None => Err(MenteError::Storage(format!(
206 "page {} not in buffer pool",
207 page_id.0
208 ))),
209 }
210 }
211
212 pub fn flush_all(&self, pm: &mut PageManager) -> MenteResult<()> {
214 let mut inner = self.inner.lock();
215 for frame in &mut inner.frames {
216 if frame.dirty
217 && let Some(pid) = frame.page_id
218 {
219 pm.write_page(pid, &frame.page)?;
220 frame.dirty = false;
221 }
222 }
223 debug!("flushed all dirty pages");
224 Ok(())
225 }
226
227 fn find_victim(inner: &mut BufferPoolInner) -> MenteResult<FrameId> {
229 for i in 0..inner.frames.len() {
231 if inner.frames[i].page_id.is_none() {
232 return Ok(i);
233 }
234 }
235
236 if inner.frames.len() < inner.capacity {
238 inner.frames.push(Frame::new());
239 return Ok(inner.frames.len() - 1);
240 }
241
242 let cap = inner.capacity;
244 let max_sweeps = cap * 2;
245 for _ in 0..max_sweeps {
246 let idx = inner.clock_hand;
247 inner.clock_hand = (inner.clock_hand + 1) % cap;
248
249 let frame = &mut inner.frames[idx];
250 if frame.pin_count == 0 {
251 if !frame.reference {
252 return Ok(idx);
253 }
254 frame.reference = false;
255 }
256 }
257
258 Err(MenteError::Storage(
259 "buffer pool full: all pages are pinned".into(),
260 ))
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::page::Page;
268
269 fn setup() -> (tempfile::TempDir, PageManager) {
270 let dir = tempfile::tempdir().unwrap();
271 let pm = PageManager::open(dir.path()).unwrap();
272 (dir, pm)
273 }
274
275 #[test]
276 fn test_fetch_and_cache_hit() {
277 let (_dir, mut pm) = setup();
278 let pool = BufferPool::new(4);
279
280 let pid = pm.allocate_page().unwrap();
281 let mut page = Page::zeroed();
282 page.header.page_id = pid.0;
283 page.data[0..3].copy_from_slice(b"abc");
284 pm.write_page(pid, &page).unwrap();
285
286 let p1 = pool.fetch_page(pid, &mut pm).unwrap();
288 assert_eq!(&p1.data[0..3], b"abc");
289
290 pool.unpin_page(pid, false).unwrap();
292
293 let p2 = pool.fetch_page(pid, &mut pm).unwrap();
295 assert_eq!(&p2.data[0..3], b"abc");
296 pool.unpin_page(pid, false).unwrap();
297 }
298
299 #[test]
300 fn test_dirty_flush() {
301 let (_dir, mut pm) = setup();
302 let pool = BufferPool::new(4);
303
304 let pid = pm.allocate_page().unwrap();
305
306 let mut page = Page::zeroed();
307 page.header.page_id = pid.0;
308 page.data[0] = 42;
309 pm.write_page(pid, &page).unwrap();
310
311 let _ = pool.fetch_page(pid, &mut pm).unwrap();
313 let mut modified = Page::zeroed();
314 modified.header.page_id = pid.0;
315 modified.data[0] = 99;
316 pool.update_page(pid, &modified).unwrap();
317 pool.unpin_page(pid, true).unwrap();
318
319 pool.flush_page(pid, &mut pm).unwrap();
321
322 let on_disk = pm.read_page(pid).unwrap();
324 assert_eq!(on_disk.data[0], 99);
325 }
326
327 #[test]
328 fn test_eviction() {
329 let (_dir, mut pm) = setup();
330 let pool = BufferPool::new(2); let p1 = pm.allocate_page().unwrap();
334 let p2 = pm.allocate_page().unwrap();
335 let p3 = pm.allocate_page().unwrap();
336
337 for pid in [p1, p2, p3] {
338 let mut page = Page::zeroed();
339 page.header.page_id = pid.0;
340 page.data[0] = pid.0 as u8;
341 pm.write_page(pid, &page).unwrap();
342 }
343
344 let _ = pool.fetch_page(p1, &mut pm).unwrap();
346 pool.unpin_page(p1, false).unwrap();
347 let _ = pool.fetch_page(p2, &mut pm).unwrap();
348 pool.unpin_page(p2, false).unwrap();
349
350 let page3 = pool.fetch_page(p3, &mut pm).unwrap();
352 assert_eq!(page3.data[0], p3.0 as u8);
353 pool.unpin_page(p3, false).unwrap();
354 }
355
356 #[test]
357 fn test_all_pinned_error() {
358 let (_dir, mut pm) = setup();
359 let pool = BufferPool::new(2);
360
361 let p1 = pm.allocate_page().unwrap();
362 let p2 = pm.allocate_page().unwrap();
363 let p3 = pm.allocate_page().unwrap();
364
365 for pid in [p1, p2, p3] {
366 let mut page = Page::zeroed();
367 page.header.page_id = pid.0;
368 pm.write_page(pid, &page).unwrap();
369 }
370
371 let _ = pool.fetch_page(p1, &mut pm).unwrap();
373 let _ = pool.fetch_page(p2, &mut pm).unwrap();
374
375 assert!(pool.fetch_page(p3, &mut pm).is_err());
377 }
378
379 #[test]
380 fn test_flush_all() {
381 let (_dir, mut pm) = setup();
382 let pool = BufferPool::new(4);
383
384 let p1 = pm.allocate_page().unwrap();
385 let p2 = pm.allocate_page().unwrap();
386
387 for pid in [p1, p2] {
388 let mut page = Page::zeroed();
389 page.header.page_id = pid.0;
390 pm.write_page(pid, &page).unwrap();
391 }
392
393 let _ = pool.fetch_page(p1, &mut pm).unwrap();
394 let _ = pool.fetch_page(p2, &mut pm).unwrap();
395
396 let mut mod1 = Page::zeroed();
397 mod1.data[0] = 0xAA;
398 pool.update_page(p1, &mod1).unwrap();
399
400 let mut mod2 = Page::zeroed();
401 mod2.data[0] = 0xBB;
402 pool.update_page(p2, &mod2).unwrap();
403
404 pool.unpin_page(p1, true).unwrap();
405 pool.unpin_page(p2, true).unwrap();
406
407 pool.flush_all(&mut pm).unwrap();
408
409 let d1 = pm.read_page(p1).unwrap();
410 let d2 = pm.read_page(p2).unwrap();
411 assert_eq!(d1.data[0], 0xAA);
412 assert_eq!(d2.data[0], 0xBB);
413 }
414}