akar_storage/
page_manager.rs1use crate::free_space_manager::{FreeSpaceManager, INVALID_PAGE_IDX, PageRange};
10use std::path::PathBuf;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13
14pub struct PageManager {
16 db_path: PathBuf,
18 page_size: usize,
20 total_pages: AtomicU64,
22 fsm: Arc<FreeSpaceManager>,
24 next_page: AtomicU64,
27}
28
29impl PageManager {
30 pub fn new(db_path: PathBuf, page_size: usize, existing_pages: u64, fsm: Arc<FreeSpaceManager>) -> Self {
35 Self {
36 db_path,
37 page_size,
38 total_pages: AtomicU64::new(existing_pages),
39 fsm,
40 next_page: AtomicU64::new(existing_pages),
41 }
42 }
43
44 pub fn page_size(&self) -> usize {
46 self.page_size
47 }
48
49 pub fn total_pages(&self) -> u64 {
51 self.total_pages.load(Ordering::Acquire)
52 }
53
54 pub fn allocate_page(&self) -> u64 {
59 if let Some(range) = self.fsm.pop_free_pages(1) {
61 return range.start_page_idx;
62 }
63
64 let page_idx = self.next_page.fetch_add(1, Ordering::SeqCst);
66 self.total_pages.fetch_add(1, Ordering::SeqCst);
67 page_idx
68 }
69
70 pub fn free_page(&self, page_idx: u64) {
74 if page_idx == INVALID_PAGE_IDX {
75 return;
76 }
77 self.fsm.add_free_pages(PageRange::new(page_idx, 1));
78 }
79
80 pub fn free_pages(&self, start_page_idx: u64, num_pages: u64) {
82 if num_pages == 0 || start_page_idx == INVALID_PAGE_IDX {
83 return;
84 }
85 self.fsm.add_free_pages(PageRange::new(start_page_idx, num_pages));
86 }
87
88 pub fn fsm(&self) -> &Arc<FreeSpaceManager> {
90 &self.fsm
91 }
92
93 pub fn page_offset(&self, page_idx: u64) -> u64 {
95 page_idx * self.page_size as u64
96 }
97
98 #[cfg(not(target_arch = "wasm32"))]
103 pub fn extend_file(&self, num_pages: u64) -> std::io::Result<u64> {
104 use std::io::Write;
105
106 let start_page = self.total_pages.load(Ordering::Acquire);
107 let extend_bytes = (num_pages as usize) * self.page_size;
108
109 let mut file = std::fs::OpenOptions::new()
110 .create(true)
111 .append(true)
112 .open(&self.db_path)?;
113
114 let zeros = vec![0u8; extend_bytes];
116 file.write_all(&zeros)?;
117 file.flush()?;
118
119 self.total_pages.fetch_add(num_pages, Ordering::SeqCst);
120
121 Ok(start_page)
122 }
123
124 #[cfg(target_arch = "wasm32")]
125 pub fn extend_file(&self, num_pages: u64) -> std::io::Result<u64> {
126 let start_page = self.total_pages.load(Ordering::Acquire);
127 self.total_pages.fetch_add(num_pages, Ordering::SeqCst);
128 Ok(start_page)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::sync::Arc;
136
137 fn make_pm() -> (PageManager, tempfile::TempDir) {
138 let dir = tempfile::tempdir().unwrap();
139 let db_path = dir.path().join("test.db");
140 let fsm = Arc::new(FreeSpaceManager::new());
141 let pm = PageManager::new(db_path, 4096, 0, fsm);
142 (pm, dir)
143 }
144
145 #[test]
146 fn test_allocate_extends_file() {
147 let (pm, _dir) = make_pm();
148 assert_eq!(pm.total_pages(), 0);
149
150 let p1 = pm.allocate_page();
151 assert_eq!(p1, 0);
152 assert_eq!(pm.total_pages(), 1);
153
154 let p2 = pm.allocate_page();
155 assert_eq!(p2, 1);
156 assert_eq!(pm.total_pages(), 2);
157 }
158
159 #[test]
160 fn test_free_and_reuse() {
161 let (pm, _dir) = make_pm();
162
163 let p1 = pm.allocate_page();
164 let _p2 = pm.allocate_page();
165 assert_eq!(pm.total_pages(), 2);
166
167 pm.free_page(p1);
169
170 let p3 = pm.allocate_page();
171 assert_eq!(p3, p1); assert_eq!(pm.total_pages(), 2); }
174
175 #[test]
176 fn test_allocate_multiple_no_fsm() {
177 let (pm, _dir) = make_pm();
178
179 for i in 0..10 {
180 let p = pm.allocate_page();
181 assert_eq!(p, i);
182 }
183 assert_eq!(pm.total_pages(), 10);
184 }
185}