mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-26 10:18:10 -07:00
283 lines
8.3 KiB
Rust
283 lines
8.3 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
fs::{self, OpenOptions},
|
|
io::{Cursor, Read},
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
|
|
use brk_core::{Error, Result};
|
|
use memmap2::MmapMut;
|
|
use parking_lot::{RwLock, RwLockWriteGuard};
|
|
use zerocopy::{FromBytes, IntoBytes};
|
|
|
|
use super::{
|
|
Identifier, PAGE_SIZE,
|
|
region::{Region, SIZE_OF_REGION},
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Regions {
|
|
id_to_index: HashMap<String, usize>,
|
|
id_to_index_path: PathBuf,
|
|
index_to_region: Vec<Option<Arc<RwLock<Region>>>>,
|
|
index_to_region_file: fs::File,
|
|
index_to_region_mmap: MmapMut,
|
|
}
|
|
|
|
impl Regions {
|
|
pub fn open(path: &Path) -> Result<Self> {
|
|
let path = path.join("regions");
|
|
|
|
fs::create_dir_all(&path)?;
|
|
|
|
let id_to_index_path = path.join("id_to_index");
|
|
|
|
let id_to_index: HashMap<String, usize> =
|
|
deserialize_hashmap_binary(&fs::read(&id_to_index_path).unwrap_or_default())
|
|
.unwrap_or_default();
|
|
|
|
let index_to_region_file = OpenOptions::new()
|
|
.read(true)
|
|
.create(true)
|
|
.write(true)
|
|
.truncate(false)
|
|
.open(path.join("index_to_region"))?;
|
|
|
|
let index_to_region_mmap = unsafe { MmapMut::map_mut(&index_to_region_file)? };
|
|
|
|
let mut index_to_region: Vec<Option<Arc<RwLock<Region>>>> = vec![];
|
|
|
|
id_to_index
|
|
.iter()
|
|
.try_for_each(|(_, &index)| -> Result<()> {
|
|
let start = index * SIZE_OF_REGION;
|
|
let end = start + SIZE_OF_REGION;
|
|
let region = Region::read_from_bytes(&index_to_region_mmap[start..end])?;
|
|
if index_to_region.len() < index + 1 {
|
|
index_to_region.resize_with(index + 1, Default::default);
|
|
}
|
|
index_to_region
|
|
.get_mut(index)
|
|
.unwrap()
|
|
.replace(Arc::new(RwLock::new(region)));
|
|
Ok(())
|
|
})?;
|
|
|
|
// TODO: Removes Nones from vec if needed, update map accordingly and save them
|
|
|
|
Ok(Self {
|
|
id_to_index,
|
|
id_to_index_path,
|
|
index_to_region,
|
|
index_to_region_file,
|
|
index_to_region_mmap,
|
|
})
|
|
}
|
|
|
|
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<(usize, Arc<RwLock<Region>>)> {
|
|
let index = self
|
|
.index_to_region
|
|
.iter()
|
|
.enumerate()
|
|
.find(|(_, opt)| opt.is_none())
|
|
.map(|(index, _)| index)
|
|
.unwrap_or_else(|| self.index_to_region.len());
|
|
|
|
let region = Region::new(start, 0, PAGE_SIZE);
|
|
|
|
self.set_min_len(((index + 1) * SIZE_OF_REGION) as u64)?;
|
|
|
|
let region_lock = RwLock::new(region);
|
|
|
|
self.write_to_mmap(®ion_lock.write(), index);
|
|
|
|
let region_arc = Arc::new(region_lock);
|
|
|
|
let region_opt = Some(region_arc.clone());
|
|
if index < self.index_to_region.len() {
|
|
self.index_to_region[index] = region_opt
|
|
} else {
|
|
self.index_to_region.push(region_opt);
|
|
self.index_to_region_mmap.flush()?;
|
|
}
|
|
|
|
if self.id_to_index.insert(id, index).is_some() {
|
|
return Err(Error::Str("Already exists"));
|
|
}
|
|
self.flush_id_to_index()?;
|
|
|
|
Ok((index, region_arc))
|
|
}
|
|
|
|
fn flush_id_to_index(&mut self) -> Result<()> {
|
|
fs::write(
|
|
&self.id_to_index_path,
|
|
serialize_hashmap_binary(&self.id_to_index),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_region(&self, identifier: Identifier) -> Option<Arc<RwLock<Region>>> {
|
|
match identifier {
|
|
Identifier::Number(index) => self.get_region_from_index(index),
|
|
Identifier::String(id) => self.get_region_from_id(&id),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_region_from_index(&self, index: usize) -> Option<Arc<RwLock<Region>>> {
|
|
self.index_to_region.get(index).cloned().flatten()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_region_from_id(&self, id: &str) -> Option<Arc<RwLock<Region>>> {
|
|
self.get_region_index_from_id(id)
|
|
.and_then(|index| self.get_region_from_index(index))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_region_index_from_id(&self, id: &str) -> Option<usize> {
|
|
self.id_to_index.get(id).copied()
|
|
}
|
|
|
|
fn find_id_from_index(&self, index: usize) -> Option<&String> {
|
|
Some(
|
|
self.id_to_index
|
|
.iter()
|
|
.find(|(_, v)| **v == index)
|
|
.unwrap()
|
|
.0,
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn index_to_region(&self) -> &[Option<Arc<RwLock<Region>>>] {
|
|
&self.index_to_region
|
|
}
|
|
|
|
#[inline]
|
|
pub fn id_to_index(&self) -> &HashMap<String, usize> {
|
|
&self.id_to_index
|
|
}
|
|
|
|
#[inline]
|
|
pub fn identifier_to_index(&self, identifier: Identifier) -> Option<usize> {
|
|
match identifier {
|
|
Identifier::Number(index) => Some(index),
|
|
Identifier::String(id) => self.get_region_index_from_id(&id),
|
|
}
|
|
}
|
|
|
|
pub fn remove_region(&mut self, identifier: Identifier) -> Result<Option<Arc<RwLock<Region>>>> {
|
|
match identifier {
|
|
Identifier::Number(index) => self.remove_region_from_index(index),
|
|
Identifier::String(id) => self.remove_region_from_id(&id),
|
|
}
|
|
}
|
|
|
|
pub fn remove_region_from_id(&mut self, id: &str) -> Result<Option<Arc<RwLock<Region>>>> {
|
|
let Some(index) = self.get_region_index_from_id(id) else {
|
|
return Ok(None);
|
|
};
|
|
self.remove_region_from_index(index)
|
|
}
|
|
|
|
pub fn remove_region_from_index(
|
|
&mut self,
|
|
index: usize,
|
|
) -> Result<Option<Arc<RwLock<Region>>>> {
|
|
let Some(region) = self.index_to_region.get_mut(index).and_then(Option::take) else {
|
|
return Ok(None);
|
|
};
|
|
|
|
self.index_to_region_mmap.flush()?;
|
|
|
|
self.id_to_index
|
|
.remove(&self.find_id_from_index(index).unwrap().to_owned());
|
|
|
|
self.flush_id_to_index()?;
|
|
|
|
Ok(Some(region))
|
|
}
|
|
|
|
pub fn write_to_mmap(&self, region: &RwLockWriteGuard<Region>, index: usize) {
|
|
let mmap = &self.index_to_region_mmap;
|
|
let start = index * SIZE_OF_REGION;
|
|
let end = start + SIZE_OF_REGION;
|
|
|
|
if end > mmap.len() {
|
|
unreachable!("Trying to write beyond mmap")
|
|
}
|
|
|
|
let slice = unsafe { std::slice::from_raw_parts_mut(mmap.as_ptr() as *mut u8, mmap.len()) };
|
|
|
|
slice[start..end].copy_from_slice(region.as_bytes());
|
|
}
|
|
|
|
pub fn flush(&self) -> Result<()> {
|
|
self.index_to_region_mmap.flush().map_err(|e| e.into())
|
|
}
|
|
}
|
|
|
|
fn serialize_hashmap_binary(map: &HashMap<String, usize>) -> Vec<u8> {
|
|
let mut buffer = Vec::new();
|
|
|
|
buffer.extend_from_slice(&map.len().to_ne_bytes());
|
|
|
|
for (key, value) in map {
|
|
buffer.extend_from_slice(&key.len().to_ne_bytes());
|
|
buffer.extend_from_slice(key.as_bytes());
|
|
buffer.extend_from_slice(&value.to_ne_bytes());
|
|
}
|
|
|
|
buffer
|
|
}
|
|
|
|
fn deserialize_hashmap_binary(data: &[u8]) -> Result<HashMap<String, usize>> {
|
|
let mut cursor = Cursor::new(data);
|
|
let mut buffer = [0u8; 8];
|
|
|
|
cursor
|
|
.read_exact(&mut buffer)
|
|
.map_err(|_| Error::Str("Failed to read entry count"))?;
|
|
let entry_count = usize::from_ne_bytes(buffer);
|
|
|
|
let mut map = HashMap::with_capacity(entry_count);
|
|
|
|
for _ in 0..entry_count {
|
|
cursor
|
|
.read_exact(&mut buffer)
|
|
.map_err(|_| Error::Str("Failed to read key length"))?;
|
|
let key_len = usize::from_ne_bytes(buffer);
|
|
|
|
let mut key_bytes = vec![0u8; key_len];
|
|
cursor
|
|
.read_exact(&mut key_bytes)
|
|
.map_err(|_| Error::Str("Failed to read key"))?;
|
|
let key = String::from_utf8(key_bytes).map_err(|_| Error::Str("Invalid UTF-8 in key"))?;
|
|
|
|
cursor
|
|
.read_exact(&mut buffer)
|
|
.map_err(|_| Error::Str("Failed to read value"))?;
|
|
let value = usize::from_ne_bytes(buffer);
|
|
|
|
map.insert(key, value);
|
|
}
|
|
|
|
Ok(map)
|
|
}
|