use alloc::collections::VecDeque;
use alloc::vec::Vec;
use crate::block::{BlockDevice, BlockId, StorageError};
pub const PAGE_SIZE: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageState {
Free,
Clean,
Dirty,
Pinned(u32),
}
#[derive(Clone)]
pub struct Page {
bytes: [u8; PAGE_SIZE],
}
impl Page {
pub fn zeroed() -> Self {
Self {
bytes: [0u8; PAGE_SIZE],
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8; PAGE_SIZE] {
&self.bytes
}
#[inline]
pub fn as_bytes_mut(&mut self) -> &mut [u8; PAGE_SIZE] {
&mut self.bytes
}
}
impl Default for Page {
fn default() -> Self {
Self::zeroed()
}
}
impl core::fmt::Debug for Page {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Page").field("size", &PAGE_SIZE).finish()
}
}
struct Frame {
block_id: BlockId,
page: Page,
state: PageState,
dirty_intent: bool,
}
pub struct BufferPool<D: BlockDevice> {
device: D,
capacity: usize,
frames: Vec<Frame>,
lru: VecDeque<usize>,
}
impl<D: BlockDevice> BufferPool<D> {
pub fn new(device: D, capacity: usize) -> Self {
assert!(capacity > 0, "buffer pool capacity must be non-zero");
assert_eq!(
D::BLOCK_SIZE,
PAGE_SIZE,
"page manager requires BLOCK_SIZE == PAGE_SIZE"
);
Self {
device,
capacity,
frames: Vec::new(),
lru: VecDeque::new(),
}
}
pub fn resident(&self) -> usize {
self.frames.len()
}
pub fn state_of(&self, block_id: BlockId) -> Option<PageState> {
self.frames
.iter()
.find(|f| f.block_id == block_id)
.map(|f| f.state)
}
fn find(&self, block_id: BlockId) -> Option<usize> {
self.frames.iter().position(|f| f.block_id == block_id)
}
fn touch(&mut self, idx: usize) {
if let Some(p) = self.lru.iter().position(|&i| i == idx) {
self.lru.remove(p);
}
self.lru.push_back(idx);
}
fn evict(&mut self) -> Result<Option<usize>, StorageError> {
let victim = self
.lru
.iter()
.copied()
.find(|&i| !matches!(self.frames[i].state, PageState::Pinned(_)));
let Some(idx) = victim else {
return Ok(None);
};
if self.frames[idx].state == PageState::Dirty {
let (block_id, bytes) = {
let f = &self.frames[idx];
(f.block_id, *f.page.as_bytes())
};
self.device.write_block(block_id, &bytes)?;
}
if let Some(p) = self.lru.iter().position(|&i| i == idx) {
self.lru.remove(p);
}
self.frames[idx].state = PageState::Free;
self.frames[idx].dirty_intent = false;
Ok(Some(idx))
}
pub fn fetch(&mut self, block_id: BlockId) -> Result<&Page, StorageError> {
if let Some(idx) = self.find(block_id) {
self.pin_frame(idx);
self.touch(idx);
return Ok(&self.frames[idx].page);
}
let idx = self.acquire_frame()?;
let mut page = Page::zeroed();
self.device.read_block(block_id, page.as_bytes_mut())?;
self.frames[idx] = Frame {
block_id,
page,
state: PageState::Pinned(1),
dirty_intent: false,
};
self.touch(idx);
Ok(&self.frames[idx].page)
}
pub fn fetch_mut(&mut self, block_id: BlockId) -> Result<&mut Page, StorageError> {
let idx = if let Some(idx) = self.find(block_id) {
self.pin_frame(idx);
idx
} else {
let idx = self.acquire_frame()?;
let mut page = Page::zeroed();
self.device.read_block(block_id, page.as_bytes_mut())?;
self.frames[idx] = Frame {
block_id,
page,
state: PageState::Pinned(1),
dirty_intent: false,
};
idx
};
self.touch(idx);
self.mark_dirty_pinned(idx);
Ok(&mut self.frames[idx].page)
}
fn pin_frame(&mut self, idx: usize) {
self.frames[idx].state = match self.frames[idx].state {
PageState::Pinned(n) => PageState::Pinned(n + 1),
_ => PageState::Pinned(1),
};
}
fn mark_dirty_pinned(&mut self, idx: usize) {
self.frames[idx].dirty_intent = true;
}
fn acquire_frame(&mut self) -> Result<usize, StorageError> {
if let Some(idx) = self.frames.iter().position(|f| f.state == PageState::Free) {
return Ok(idx);
}
if self.frames.len() < self.capacity {
self.frames.push(Frame {
block_id: BlockId::MAX,
page: Page::zeroed(),
state: PageState::Free,
dirty_intent: false,
});
return Ok(self.frames.len() - 1);
}
match self.evict()? {
Some(idx) => Ok(idx),
None => Err(StorageError::AllFramesPinned),
}
}
pub fn unpin(&mut self, block_id: BlockId) {
if let Some(idx) = self.find(block_id) {
if let PageState::Pinned(n) = self.frames[idx].state {
if n > 1 {
self.frames[idx].state = PageState::Pinned(n - 1);
} else if self.frames[idx].dirty_intent {
self.frames[idx].state = PageState::Dirty;
} else {
self.frames[idx].state = PageState::Clean;
}
}
}
}
pub fn flush_all(&mut self) -> Result<(), StorageError> {
for idx in 0..self.frames.len() {
if self.frames[idx].state == PageState::Dirty
|| (self.frames[idx].dirty_intent
&& matches!(self.frames[idx].state, PageState::Pinned(_)))
{
let (block_id, bytes) = {
let f = &self.frames[idx];
(f.block_id, *f.page.as_bytes())
};
self.device.write_block(block_id, &bytes)?;
self.frames[idx].dirty_intent = false;
if self.frames[idx].state == PageState::Dirty {
self.frames[idx].state = PageState::Clean;
}
}
}
self.device.sync()
}
pub fn into_device(self) -> D {
self.device
}
pub fn device_mut(&mut self) -> &mut D {
&mut self.device
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::InMemoryBlockDevice;
fn pool(blocks: u64, cap: usize) -> BufferPool<InMemoryBlockDevice> {
BufferPool::new(InMemoryBlockDevice::new(blocks), cap)
}
#[test]
fn fetch_pins_and_unpin_marks_clean() {
let mut p = pool(4, 2);
let _ = p.fetch(0).unwrap();
assert_eq!(p.state_of(0), Some(PageState::Pinned(1)));
p.unpin(0);
assert_eq!(p.state_of(0), Some(PageState::Clean));
}
#[test]
fn fetch_mut_marks_dirty_after_unpin_and_persists() {
let mut p = pool(4, 2);
{
let page = p.fetch_mut(1).unwrap();
page.as_bytes_mut()[0] = 0x42;
}
assert!(matches!(p.state_of(1), Some(PageState::Pinned(_))));
p.unpin(1);
assert_eq!(p.state_of(1), Some(PageState::Dirty));
p.flush_all().unwrap();
assert_eq!(p.state_of(1), Some(PageState::Clean));
let dev = p.into_device();
let mut buf = [0u8; PAGE_SIZE];
dev.read_block(1, &mut buf).unwrap();
assert_eq!(buf[0], 0x42);
}
#[test]
fn lru_evicts_least_recently_used_and_writes_back_dirty() {
let mut p = pool(8, 2);
{
p.fetch_mut(0).unwrap().as_bytes_mut()[0] = 9;
}
p.unpin(0);
let _ = p.fetch(1).unwrap();
p.unpin(1);
let _ = p.fetch(0).unwrap();
p.unpin(0);
let _ = p.fetch(2).unwrap();
p.unpin(2);
assert_eq!(p.state_of(1), None); assert!(p.state_of(0).is_some());
p.flush_all().unwrap();
let dev = p.into_device();
let mut buf = [0u8; PAGE_SIZE];
dev.read_block(0, &mut buf).unwrap();
assert_eq!(buf[0], 9);
}
#[test]
fn all_pinned_pool_errors() {
let mut p = pool(8, 1);
let _ = p.fetch(0).unwrap(); assert_eq!(p.fetch(1).err(), Some(StorageError::AllFramesPinned));
}
#[test]
fn double_pin_requires_double_unpin() {
let mut p = pool(4, 2);
let _ = p.fetch(0).unwrap();
let _ = p.fetch(0).unwrap();
assert_eq!(p.state_of(0), Some(PageState::Pinned(2)));
p.unpin(0);
assert_eq!(p.state_of(0), Some(PageState::Pinned(1)));
p.unpin(0);
assert_eq!(p.state_of(0), Some(PageState::Clean));
}
}