From 3ac9c2d95e53b7641d8fdb8d4b13914a9b1e6aee Mon Sep 17 00:00:00 2001 From: nym21 Date: Tue, 22 Jul 2025 21:26:50 +0200 Subject: [PATCH] vecs: part 5 --- crates/brk_vecs/src/file/layout.rs | 55 ++++++++---- crates/brk_vecs/src/file/mod.rs | 135 +++++++++++++++++++--------- crates/brk_vecs/src/file/reader.rs | 2 +- crates/brk_vecs/src/file/region.rs | 10 +-- crates/brk_vecs/src/file/regions.rs | 16 ++-- 5 files changed, 148 insertions(+), 70 deletions(-) diff --git a/crates/brk_vecs/src/file/layout.rs b/crates/brk_vecs/src/file/layout.rs index d3a78f215..cfc30b4bd 100644 --- a/crates/brk_vecs/src/file/layout.rs +++ b/crates/brk_vecs/src/file/layout.rs @@ -8,7 +8,6 @@ use super::{PAGE_SIZE, Region, Regions}; #[derive(Debug)] pub struct Layout { start_to_index: BTreeMap, - /// key: start, value: gap start_to_hole: BTreeMap, } @@ -21,7 +20,7 @@ impl From<&Regions> for Layout { value .as_array() - .into_iter() + .iter() .enumerate() .flat_map(|(index, opt)| opt.as_ref().map(|region| (index, region))) .for_each(|(index, region)| { @@ -43,24 +42,34 @@ impl From<&Regions> for Layout { } impl Layout { - pub fn get_last_region(&self) -> Option { + pub fn get_last_region(&self) -> Option<(u64, usize)> { self.start_to_index .last_key_value() - .map(|(_, index)| *index) + .map(|(start, index)| (*start, *index)) } - pub fn find_smallest_adequate_hole(&self, reserved: u64) -> Option { - self.start_to_hole - .iter() - .filter(|(_, gap)| **gap >= reserved) - .map(|(start, gap)| (gap, start)) - .collect::>() - .pop_first() - .map(|(_, s)| *s) + pub fn get_last_region_index(&self) -> Option { + self.get_last_region().map(|(_, index)| index) + } + + pub fn is_last_region(&self, index: usize) -> bool { + let last = self.get_last_region(); + let is_last = last.is_some_and(|(_, other_index)| index == other_index); + if is_last { + debug_assert!(self.start_to_hole.range(last.unwrap().0..).next().is_none()); + } + is_last } pub fn insert_region(&mut self, start: u64, index: usize) { - assert!(self.start_to_index.insert(start, index).is_none()) + debug_assert!(self.start_to_index.insert(start, index).is_none()) + // TODO: Other checks related to holes ? + } + + pub fn move_region(&mut self, start: u64, index: usize, region: &Region) -> Result<()> { + self.remove_region(index, region)?; + self.insert_region(start, index); + Ok(()) } pub fn remove_region(&mut self, index: usize, region: &Region) -> Result<()> { @@ -107,6 +116,16 @@ impl Layout { self.start_to_hole.get(&start).copied() } + pub fn find_smallest_adequate_hole(&self, reserved: u64) -> Option { + self.start_to_hole + .iter() + .filter(|(_, gap)| **gap >= reserved) + .map(|(start, gap)| (gap, start)) + .collect::>() + .pop_first() + .map(|(_, s)| *s) + } + pub fn remove_or_compress_hole_to_right(&mut self, start: u64, compress_by: u64) { if let Some(gap) = self.start_to_hole.remove(&start) && gap != compress_by @@ -121,14 +140,14 @@ impl Layout { } fn widen_hole_to_the_left_if_any(&mut self, start: u64, widen_by: u64) -> Option { - assert!(start % PAGE_SIZE == 0); + debug_assert!(start % PAGE_SIZE == 0); if widen_by > start { panic!("Hole too small") } let gap = self.start_to_hole.remove(&start)?; - assert!(widen_by % PAGE_SIZE == 0); + debug_assert!(widen_by % PAGE_SIZE == 0); let start = start - widen_by; let gap = gap + widen_by; @@ -137,17 +156,17 @@ impl Layout { { *prev_gap += gap; } else { - assert!(self.start_to_hole.insert(start, gap).is_none()); + debug_assert!(self.start_to_hole.insert(start, gap).is_none()); } Some(start) } fn widen_hole_to_the_right_if_any(&mut self, start: u64, widen_by: u64) -> Option { - assert!(start % PAGE_SIZE == 0); + debug_assert!(start % PAGE_SIZE == 0); let gap = self.start_to_hole.get_mut(&start)?; - assert!(widen_by % PAGE_SIZE == 0); + debug_assert!(widen_by % PAGE_SIZE == 0); *gap += widen_by; let next_hole_start = start + *gap; diff --git a/crates/brk_vecs/src/file/mod.rs b/crates/brk_vecs/src/file/mod.rs index 10f1b0ed2..5f9c639ab 100644 --- a/crates/brk_vecs/src/file/mod.rs +++ b/crates/brk_vecs/src/file/mod.rs @@ -21,6 +21,7 @@ use region::*; use regions::*; pub const PAGE_SIZE: u64 = 4096; +pub const PAGE_SIZE_MINUS_1: u64 = PAGE_SIZE - 1; pub struct File { regions: RwLock, @@ -53,9 +54,9 @@ impl File { }) } - /// len % PAGE_SIZE == 0 pub fn set_min_len(&self, len: u64) -> Result<()> { - assert!(len % PAGE_SIZE == 0); + let len = Self::ceil_number_to_page_size_multiple(len); + if self.file.read().metadata()?.len() < len { let mut mmap = self.mmap.write(); let file = self.file.write(); @@ -67,6 +68,13 @@ impl File { } } + pub fn set_min_regions(&self, regions: usize) -> Result<()> { + self.regions + .write() + .set_min_len((regions * SIZE_OF_REGION) as u64)?; + self.set_min_len(regions as u64 * PAGE_SIZE) + } + pub fn get_or_create(&self, id: String) -> Result { if let Some(index) = self.regions.read().get_region_index_from_id(id.clone()) { return Ok(index); @@ -79,7 +87,7 @@ impl File { start } else { let start = layout - .get_last_region() + .get_last_region_index() .map(|index| { let region_opt = regions.get_region_from_index(index); let region = region_opt.as_ref().unwrap().read(); @@ -121,88 +129,126 @@ impl File { self.write_all_at_(region, data, Some(at)) } - fn write_all_at_(&mut self, region: usize, data: &[u8], at: Option) -> Result<()> { - let Some(region) = self.regions.read().get_region_from_index(region) else { + fn write_all_at_(&mut self, region_index: usize, data: &[u8], at: Option) -> Result<()> { + let Some(region) = self.regions.read().get_region_from_index(region_index) else { return Err(Error::Str("Unknown region")); }; let region_lock = region.read(); let start = region_lock.start(); let reserved = region_lock.reserved(); let left = region_lock.left(); + let len = region_lock.len(); + let end = start + len; let data_len = data.len() as u64; drop(region_lock); let new_left = at.map_or_else(|| left, |at| reserved - (at - start)); let new_len = reserved - new_left; + let write_start = at.unwrap_or(start + len); // Write to reserved space if possible if new_left >= data_len { - Self::write_to_mmap(&self.mmap.read(), at.unwrap_or(start), data); + self.write(write_start, data); + let regions = self.regions.read(); let mut region_lock = region.write(); region_lock.set_len(new_len); - - // TODO: Flush layout + regions.write_to_mmap(®ion_lock, region_index); return Ok(()); } let mut layout_lock = self.layout.write(); - let hole_start = start + reserved; - let hole = layout_lock.get_hole(hole_start); + let new_len = len + data_len; + debug_assert!(new_len > reserved); + let mut new_reserved = reserved; + while new_len < new_reserved { + new_reserved *= 2; + } + let added_reserve = new_reserved - reserved; - // Expand region to the right if possible - if hole.is_some_and(|gap| gap >= reserved) { - Self::write_to_mmap(&self.mmap.read(), at.unwrap_or(start), data); + // If is last continue writing + if layout_lock.is_last_region(region_index) { + self.set_min_len(start + new_reserved)?; - layout_lock.remove_or_compress_hole_to_right(hole_start, reserved); - drop(layout_lock); + self.write(write_start, data); + let regions = self.regions.read(); let mut region_lock = region.write(); region_lock.set_len(new_len); - region_lock.set_reserved(reserved * 2); - - // TODO: Flush layout + region_lock.set_reserved(new_reserved); + regions.write_to_mmap(®ion_lock, region_index); return Ok(()); } - let reserved = reserved * 2; + // Expand region to the right if gap is wide enough + let hole_start = start + reserved; + let gap = layout_lock.get_hole(hole_start); + if gap.is_some_and(|gap| gap >= added_reserve) { + self.write(write_start, data); - // Find hole big enough to move the current region or the next region depending on which is smaller to if possible - if let Some(hole_start) = layout_lock.find_smallest_adequate_hole(reserved) { - layout_lock.remove_or_compress_hole_to_right(hole_start, reserved); - // TODO: Before every drop of layout.write flush to disk + layout_lock.remove_or_compress_hole_to_right(hole_start, added_reserve); drop(layout_lock); - // write - Self::write_to_mmap(&self.mmap.read(), at.unwrap_or(start), data); - + let regions = self.regions.read(); let mut region_lock = region.write(); + region_lock.set_len(new_len); + region_lock.set_reserved(new_reserved); + regions.write_to_mmap(®ion_lock, region_index); + return Ok(()); + } + + // Find hole big enough to move the region + if let Some(hole_start) = layout_lock.find_smallest_adequate_hole(new_reserved) { + self.write(hole_start, &self.mmap.read()[start as usize..end as usize]); + self.write(hole_start + len, data); + + let regions = self.regions.read(); + let mut region_lock = region.write(); + + layout_lock.remove_or_compress_hole_to_right(hole_start, new_reserved); + layout_lock.move_region(hole_start, region_index, ®ion_lock)?; + region_lock.set_start(hole_start); region_lock.set_len(new_len); - region_lock.set_reserved(reserved * 2); + region_lock.set_reserved(new_reserved); + regions.write_to_mmap(®ion_lock, region_index); - // TODO: create hole in prev position + drop(layout_lock); - Self::write_to_mmap(&self.mmap.read(), at.unwrap_or(start), data); + self.punch_hole(start, reserved)?; - // TODO: Flush layout return Ok(()); } - // copy region to new position then lock and update region meta then remove + // Write at the end + let regions = self.regions.read(); + let mut region_lock = region.write(); + let (last_region_start, last_region_index) = layout_lock.get_last_region().unwrap(); + let new_start = last_region_start + + regions + .get_region_from_index(last_region_index) + .unwrap() + .read() + .reserved(); + self.set_min_len(new_start + new_reserved)?; - // let old_length = region_lock.len(); - // let new_length = old_length + data_len as u64; + self.write(new_start, &self.mmap.read()[start as usize..end as usize]); + self.write(new_start + len, data); - // self.layout.ho + region_lock.set_start(new_start); + region_lock.set_len(new_len); + region_lock.set_reserved(new_reserved); + regions.write_to_mmap(®ion_lock, region_index); - todo!(); + self.punch_hole(start, reserved)?; Ok(()) } - fn write_to_mmap(mmap: &MmapMut, start: u64, data: &[u8]) { + fn write(&self, start: u64, data: &[u8]) { + let mmap = self.mmap.read(); + let data_len = data.len(); let start = start as usize; let end = start + data_len; @@ -223,6 +269,7 @@ impl File { let mut region_ = region.write(); let start = region_.start(); let len = region_.len(); + let reserved = region_.reserved(); if from <= start { return Err(Error::Str("Truncating too much")); @@ -232,10 +279,14 @@ impl File { region_.set_len(from); - // TODO: Widen hole if present and needed (if truncating a big portion) - // Not needed in BRK and with hole punching it's not a big deal but good to have nonetheless - - self.punch_hole(from, region_.left()) + let end = start + reserved; + let start = Self::ceil_number_to_page_size_multiple(from); + if start > end { + unreachable!("Should not be possible"); + } else if start < end { + self.punch_hole(start, end - start)?; + } + Ok(()) } pub fn remove(&self, index: usize) -> Result>>> { @@ -284,6 +335,10 @@ impl File { Ok(()) } + + fn ceil_number_to_page_size_multiple(num: u64) -> u64 { + (num + PAGE_SIZE_MINUS_1) & !PAGE_SIZE_MINUS_1 + } } #[repr(C)] diff --git a/crates/brk_vecs/src/file/reader.rs b/crates/brk_vecs/src/file/reader.rs index 431e639be..a203ebf3d 100644 --- a/crates/brk_vecs/src/file/reader.rs +++ b/crates/brk_vecs/src/file/reader.rs @@ -17,7 +17,7 @@ impl<'a> Reader<'a> { } pub fn read(&self, offset: u64, len: u64) -> &[u8] { - assert!(offset + len < self.region.len()); + debug_assert!(offset + len < self.region.len()); let start = self.region.start() + offset; let end = start + len; &self.mmap[start as usize..end as usize] diff --git a/crates/brk_vecs/src/file/region.rs b/crates/brk_vecs/src/file/region.rs index 054eafac9..253a343a1 100644 --- a/crates/brk_vecs/src/file/region.rs +++ b/crates/brk_vecs/src/file/region.rs @@ -16,10 +16,10 @@ pub const SIZE_OF_REGION: usize = size_of::(); impl Region { pub fn new(start: u64, length: u64, reserved: u64) -> Self { - assert!(reserved > 0); - assert!(start % PAGE_SIZE == 0); - assert!(reserved % PAGE_SIZE == 0); - assert!(length <= reserved); + debug_assert!(reserved > 0); + debug_assert!(start % PAGE_SIZE == 0); + debug_assert!(reserved % PAGE_SIZE == 0); + debug_assert!(length <= reserved); Self { start, @@ -33,7 +33,7 @@ impl Region { } pub fn set_start(&mut self, start: u64) { - assert!(start % PAGE_SIZE == 0); + debug_assert!(start % PAGE_SIZE == 0); self.start = start } diff --git a/crates/brk_vecs/src/file/regions.rs b/crates/brk_vecs/src/file/regions.rs index 8e8a50169..33cf190a9 100644 --- a/crates/brk_vecs/src/file/regions.rs +++ b/crates/brk_vecs/src/file/regions.rs @@ -79,6 +79,14 @@ impl Regions { }) } + pub fn set_min_len(&mut self, len: u64) -> Result<()> { + if self.index_to_region_mmap.len() < len as usize { + self.index_to_region_file.set_len(len)?; + self.index_to_region_mmap = unsafe { MmapMut::map_mut(&self.index_to_region_file)? }; + } + Ok(()) + } + pub fn create_region(&mut self, id: String, start: u64) -> Result { let index = self .index_to_region @@ -93,11 +101,7 @@ impl Regions { self.index_to_region .push(Some(Arc::new(RwLock::new(region.clone())))); - let end = index * SIZE_OF_REGION + SIZE_OF_REGION; - if self.index_to_region_mmap.len() < end { - self.index_to_region_file.set_len(end as u64); - self.index_to_region_mmap = unsafe { MmapMut::map_mut(&self.index_to_region_file)? }; - } + self.set_min_len(((index + 1) * SIZE_OF_REGION) as u64)?; self.write_to_mmap(®ion, index); @@ -150,7 +154,7 @@ impl Regions { &self.index_to_region } - fn write_to_mmap(&self, region: &Region, index: usize) { + pub fn write_to_mmap(&self, region: &Region, index: usize) { let start = index * SIZE_OF_REGION; let end = start + SIZE_OF_REGION; let mmap = &self.index_to_region_mmap;