reddb_server/storage/engine/
freelist.rs1use super::page::{Page, PageType, HEADER_SIZE, PAGE_SIZE};
30
31pub const FREE_IDS_PER_TRUNK: usize = (PAGE_SIZE - HEADER_SIZE - 8) / 4;
33
34const NEXT_TRUNK_OFFSET: usize = HEADER_SIZE;
36
37const COUNT_OFFSET: usize = HEADER_SIZE + 4;
39
40const PAGE_IDS_OFFSET: usize = HEADER_SIZE + 8;
42
43#[derive(Debug, Clone)]
45pub enum FreeListError {
46 Empty,
48 Corrupted(String),
50 InvalidTrunk(u32),
52}
53
54impl std::fmt::Display for FreeListError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::Empty => write!(f, "Freelist is empty"),
58 Self::Corrupted(msg) => write!(f, "Freelist corrupted: {}", msg),
59 Self::InvalidTrunk(id) => write!(f, "Invalid trunk page: {}", id),
60 }
61 }
62}
63
64impl std::error::Error for FreeListError {}
65
66#[derive(Debug)]
71pub struct FreeList {
72 trunk_head: u32,
74 free_pages: Vec<u32>,
76 total_free: u32,
78 dirty: bool,
80}
81
82impl FreeList {
83 pub fn new() -> Self {
85 Self {
86 trunk_head: 0,
87 free_pages: Vec::new(),
88 total_free: 0,
89 dirty: false,
90 }
91 }
92
93 pub fn from_header(trunk_head: u32, total_free: u32) -> Self {
95 Self {
96 trunk_head,
97 free_pages: Vec::new(),
98 total_free,
99 dirty: false,
100 }
101 }
102
103 pub fn trunk_head(&self) -> u32 {
105 self.trunk_head
106 }
107
108 pub fn total_free(&self) -> u32 {
110 self.total_free
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.free_pages.is_empty() && self.trunk_head == 0
116 }
117
118 pub fn is_dirty(&self) -> bool {
120 self.dirty
121 }
122
123 pub fn mark_clean(&mut self) {
125 self.dirty = false;
126 }
127
128 pub fn allocate(&mut self) -> Option<u32> {
132 if let Some(page_id) = self.free_pages.pop() {
134 self.total_free = self.total_free.saturating_sub(1);
135 self.dirty = true;
136 return Some(page_id);
137 }
138
139 None
142 }
143
144 pub fn free(&mut self, page_id: u32) {
146 self.free_pages.push(page_id);
147 self.total_free += 1;
148 self.dirty = true;
149 }
150
151 pub fn free_batch(&mut self, page_ids: &[u32]) {
153 self.free_pages.extend_from_slice(page_ids);
154 self.total_free += page_ids.len() as u32;
155 self.dirty = true;
156 }
157
158 pub fn in_memory_count(&self) -> usize {
160 self.free_pages.len()
161 }
162
163 pub fn load_from_trunk(&mut self, trunk: &Page) -> Result<Option<u32>, FreeListError> {
165 if trunk
167 .page_type()
168 .map_err(|_| FreeListError::InvalidTrunk(trunk.page_id()))?
169 != PageType::FreelistTrunk
170 {
171 return Err(FreeListError::InvalidTrunk(trunk.page_id()));
172 }
173
174 let data = trunk.as_bytes();
175
176 let next_trunk = u32::from_le_bytes([
178 data[NEXT_TRUNK_OFFSET],
179 data[NEXT_TRUNK_OFFSET + 1],
180 data[NEXT_TRUNK_OFFSET + 2],
181 data[NEXT_TRUNK_OFFSET + 3],
182 ]);
183
184 let count = u32::from_le_bytes([
186 data[COUNT_OFFSET],
187 data[COUNT_OFFSET + 1],
188 data[COUNT_OFFSET + 2],
189 data[COUNT_OFFSET + 3],
190 ]) as usize;
191
192 if count > FREE_IDS_PER_TRUNK {
193 return Err(FreeListError::Corrupted(format!(
194 "Trunk has {} entries, max is {}",
195 count, FREE_IDS_PER_TRUNK
196 )));
197 }
198
199 self.free_pages.push(trunk.page_id());
200
201 for i in 0..count {
203 let offset = PAGE_IDS_OFFSET + i * 4;
204 let page_id = u32::from_le_bytes([
205 data[offset],
206 data[offset + 1],
207 data[offset + 2],
208 data[offset + 3],
209 ]);
210 self.free_pages.push(page_id);
211 }
212 self.total_free = self.total_free.saturating_add(count as u32 + 1);
213 self.dirty = true;
214
215 self.trunk_head = next_trunk;
217
218 Ok(if next_trunk != 0 {
219 Some(next_trunk)
220 } else {
221 None
222 })
223 }
224
225 pub fn create_trunk(&mut self, trunk_page_id: u32, next_trunk: u32) -> Page {
230 let mut trunk = Page::new(PageType::FreelistTrunk, trunk_page_id);
231 let data = trunk.as_bytes_mut();
232
233 data[NEXT_TRUNK_OFFSET..NEXT_TRUNK_OFFSET + 4].copy_from_slice(&next_trunk.to_le_bytes());
235
236 let count = self.free_pages.len().min(FREE_IDS_PER_TRUNK);
238
239 data[COUNT_OFFSET..COUNT_OFFSET + 4].copy_from_slice(&(count as u32).to_le_bytes());
241
242 for i in 0..count {
244 let page_id = self.free_pages.pop().unwrap();
245 let offset = PAGE_IDS_OFFSET + i * 4;
246 data[offset..offset + 4].copy_from_slice(&page_id.to_le_bytes());
247 }
248
249 trunk.update_checksum();
250 self.dirty = true;
251
252 trunk
253 }
254
255 pub fn flush_to_trunks(
260 &mut self,
261 threshold: usize,
262 mut allocate_page: impl FnMut() -> u32,
263 ) -> Vec<Page> {
264 let mut trunks = Vec::new();
265
266 while self.free_pages.len() > threshold {
267 let trunk_page_id = if let Some(id) = self.free_pages.pop() {
270 self.total_free = self.total_free.saturating_sub(1);
271 id
272 } else {
273 allocate_page()
274 };
275
276 let trunk = self.create_trunk(trunk_page_id, self.trunk_head);
278 self.trunk_head = trunk_page_id;
279
280 trunks.push(trunk);
281 }
282
283 trunks
284 }
285
286 pub fn merge_all_trunks(
290 &mut self,
291 load_page: impl Fn(u32) -> Option<Page>,
292 ) -> Result<Vec<u32>, FreeListError> {
293 let mut reclaimed_trunks = Vec::new();
294
295 while self.trunk_head != 0 {
296 let trunk_id = self.trunk_head;
297 let trunk = load_page(trunk_id).ok_or(FreeListError::InvalidTrunk(trunk_id))?;
298
299 self.load_from_trunk(&trunk)?;
300 reclaimed_trunks.push(trunk_id);
301 }
302
303 Ok(reclaimed_trunks)
304 }
305}
306
307impl Default for FreeList {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn trunk_publication_does_not_extend_a_file_with_free_pages() {
319 let mut list = FreeList::new();
320 list.free_batch(&[100, 101, 102]);
321 let trunks = list.flush_to_trunks(0, || panic!("must reuse a free page"));
322 assert_eq!(trunks.len(), 1);
323 assert_eq!(trunks[0].page_id(), 102);
324 let mut loaded = FreeList::new();
325 loaded.load_from_trunk(&trunks[0]).expect("load trunk");
326 let mut pages = Vec::new();
327 while let Some(id) = loaded.allocate() {
328 pages.push(id);
329 }
330 pages.sort_unstable();
331 assert_eq!(pages, [100, 101, 102]);
332 }
333
334 #[test]
335 fn test_freelist_basic() {
336 let mut fl = FreeList::new();
337
338 assert!(fl.is_empty());
339 assert_eq!(fl.total_free(), 0);
340
341 fl.free(10);
343 fl.free(20);
344 fl.free(30);
345
346 assert!(!fl.is_empty());
347 assert_eq!(fl.total_free(), 3);
348
349 assert_eq!(fl.allocate(), Some(30));
351 assert_eq!(fl.allocate(), Some(20));
352 assert_eq!(fl.allocate(), Some(10));
353 assert_eq!(fl.allocate(), None);
354
355 assert!(fl.is_empty());
356 }
357
358 #[test]
359 fn test_freelist_batch() {
360 let mut fl = FreeList::new();
361
362 fl.free_batch(&[1, 2, 3, 4, 5]);
363 assert_eq!(fl.total_free(), 5);
364 assert_eq!(fl.in_memory_count(), 5);
365 }
366
367 #[test]
368 fn test_freelist_dirty() {
369 let mut fl = FreeList::new();
370
371 assert!(!fl.is_dirty());
372
373 fl.free(1);
374 assert!(fl.is_dirty());
375
376 fl.mark_clean();
377 assert!(!fl.is_dirty());
378
379 fl.allocate();
380 assert!(fl.is_dirty());
381 }
382
383 #[test]
384 fn test_trunk_page_creation() {
385 let mut fl = FreeList::new();
386
387 for i in 0..100 {
389 fl.free(i);
390 }
391
392 let trunk = fl.create_trunk(999, 0);
394
395 assert_eq!(trunk.page_type().unwrap(), PageType::FreelistTrunk);
396 assert_eq!(trunk.page_id(), 999);
397
398 assert!(fl.in_memory_count() < 100);
400 }
401
402 #[test]
403 fn test_trunk_page_load() {
404 let mut fl = FreeList::new();
405
406 for i in 0..50 {
408 fl.free(i);
409 }
410
411 let trunk = fl.create_trunk(999, 0);
412 let pages_in_trunk = 50 - fl.in_memory_count();
413
414 fl.free_pages.clear();
416
417 fl.load_from_trunk(&trunk).unwrap();
419
420 assert_eq!(fl.in_memory_count(), pages_in_trunk + 1);
421 }
422
423 #[test]
424 fn test_trunk_page_reuse() {
425 let mut original = FreeList::new();
426
427 for i in 0..8 {
428 original.free(i);
429 }
430
431 let trunk = original.create_trunk(999, 0);
432
433 let mut fl = FreeList::new();
434 fl.load_from_trunk(&trunk).unwrap();
435
436 let mut ids = Vec::new();
437 while let Some(id) = fl.allocate() {
438 ids.push(id);
439 }
440
441 assert!(ids.contains(&999));
442 }
443
444 #[test]
445 fn test_trunk_chain() {
446 let mut fl = FreeList::new();
447
448 for i in 0..2000 {
450 fl.free(i);
451 }
452
453 let mut next_trunk_id = 10000u32;
455 let trunks = fl.flush_to_trunks(100, || {
456 let id = next_trunk_id;
457 next_trunk_id += 1;
458 id
459 });
460
461 assert!(!trunks.is_empty());
462 assert!(fl.in_memory_count() <= 100);
463 }
464
465 #[test]
466 fn test_free_ids_per_trunk() {
467 assert_eq!(FREE_IDS_PER_TRUNK, 4086);
470 assert_eq!(FREE_IDS_PER_TRUNK, 4086);
471 }
472}