use std::collections::BTreeMap;
use std::io::{Error, ErrorKind, Result};
use std::sync::{Arc, RwLock};
use btree_ondisk::MemoryBlockLoader;
use btree_ondisk::node::{BtreeNode, BTREE_NODE_LEVEL_DATA};
use log::debug;
use crate::{BlockPtr, BlockIndex, SegmentId};
use crate::config::{HyperFileMetaConfig, HyperFileRuntimeConfig};
use crate::file::ReadTiming;
use crate::inode::{FlushInodeFlag, OnDiskState};
use crate::ondisk::{SegmentHeader, InodeRaw};
use crate::meta_format::BlockPtrFormat;
use crate::segment::{self, SegmentSum, SegmentReadWrite};
use crate::segment_body::SegmentBody;
use crate::staging::config::StagingConfig;
use crate::staging::{Staging, StagingIntercept};
#[derive(Default, Debug)]
struct MemoryStore {
segments: BTreeMap<SegmentId, Vec<u8>>,
inode: Option<Vec<u8>>,
timestamps: BTreeMap<SegmentId, i64>,
meta_keys: std::collections::HashSet<BlockPtr>,
}
impl MemoryStore {
fn read_at(&self, segid: SegmentId, off: usize, buf: &mut [u8]) -> Result<()> {
let Some(seg) = self.segments.get(&segid) else {
return Err(Error::new(ErrorKind::NotFound,
format!("segment {} does not exist in memory staging", segid)));
};
let end = off + buf.len();
if end > seg.len() {
return Err(Error::new(ErrorKind::InvalidData,
format!("read of {} bytes at offset {} is past the end of segment {} ({} bytes)",
buf.len(), off, segid, seg.len())));
}
buf.copy_from_slice(&seg[off..end]);
Ok(())
}
}
pub struct MemoryStaging {
store: Arc<RwLock<MemoryStore>>,
loader: MemoryBlockLoader<BlockPtr>,
block_ptr_format: BlockPtrFormat,
meta_block_size: usize,
root_path: String,
config: StagingConfig,
runtime_config: HyperFileRuntimeConfig,
interceptor: Option<Arc<dyn StagingIntercept<Self>>>,
read_timing: Arc<ReadTiming>,
}
impl Clone for MemoryStaging {
fn clone(&self) -> Self {
Self {
store: self.store.clone(),
loader: self.loader.clone(),
block_ptr_format: self.block_ptr_format,
meta_block_size: self.meta_block_size,
root_path: self.root_path.clone(),
config: self.config.clone(),
runtime_config: self.runtime_config.clone(),
interceptor: self.interceptor.clone(),
read_timing: self.read_timing.clone(),
}
}
}
impl MemoryStaging {
pub fn new(config: StagingConfig, runtime_config: HyperFileRuntimeConfig) -> Self {
Self::with_meta_block_size(config, runtime_config, HyperFileMetaConfig::default().meta_block_size)
}
pub fn with_meta_block_size(config: StagingConfig, runtime_config: HyperFileRuntimeConfig, meta_block_size: usize) -> Self {
let root_path = config.root_uri.clone();
Self {
store: Arc::new(RwLock::new(MemoryStore::default())),
loader: MemoryBlockLoader::new(meta_block_size),
block_ptr_format: BlockPtrFormat::MicroGroup,
meta_block_size,
root_path,
config,
runtime_config,
interceptor: None,
read_timing: Arc::new(ReadTiming::default()),
}
}
pub fn with_name(name: &str) -> Self {
let config = StagingConfig::new_memory(name);
Self::new(config, HyperFileRuntimeConfig::default())
}
pub fn with_block_ptr_format(mut self, fmt: BlockPtrFormat) -> Self {
self.block_ptr_format = fmt;
self
}
fn writing_as(&self, config: &HyperFileMetaConfig) -> Self {
let mut s = self.clone();
s.block_ptr_format = config.block_ptr_format;
s
}
pub fn segment_count(&self) -> usize {
self.store.read().unwrap().segments.len()
}
pub fn bytes_stored(&self) -> usize {
let store = self.store.read().unwrap();
store.segments.values().map(|s| s.len()).sum::<usize>()
+ store.inode.as_ref().map_or(0, |i| i.len())
}
pub fn put_raw_segment(&self, segid: SegmentId, bytes: Vec<u8>) {
let mut store = self.store.write().unwrap();
store.segments.insert(segid, bytes);
store.timestamps.insert(segid, Self::now());
}
pub fn segment_bytes(&self, segid: SegmentId) -> Option<Vec<u8>> {
self.store.read().unwrap().segments.get(&segid).cloned()
}
pub fn backdate_objects(&self, secs: i64) {
for ts in self.store.write().unwrap().timestamps.values_mut() {
*ts -= secs;
}
}
pub fn inode_bytes(&self) -> Option<Vec<u8>> {
self.store.read().unwrap().inode.clone()
}
pub fn put_raw_inode(&self, bytes: Vec<u8>) {
self.store.write().unwrap().inode = Some(bytes);
}
pub fn has_segment(&self, segid: SegmentId) -> bool {
self.store.read().unwrap().segments.contains_key(&segid)
}
pub fn has_inode(&self) -> bool {
self.store.read().unwrap().inode.is_some()
}
fn now() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
fn publish_meta_nodes(&self, segid: SegmentId, seg: &[u8]) -> Result<()> {
if seg.len() < SegmentHeader::size() {
return Err(Error::new(ErrorKind::InvalidData, "incorrect segment header size"));
}
let hdr = SegmentHeader::from_slice(&seg[..SegmentHeader::size()]);
let nmetablk = hdr.s_nmetablk as usize;
if nmetablk == 0 {
return Ok(());
}
let node_size = 1usize << hdr.s_meta_blk_shift;
let base = hdr.aligned_ss_bytes();
if node_size != self.meta_block_size {
return Err(Error::new(ErrorKind::InvalidInput, format!(
"segment {} has {} byte meta nodes but this staging was built for {}",
segid, node_size, self.meta_block_size)));
}
if base + nmetablk * node_size > seg.len() {
return Err(Error::new(ErrorKind::InvalidData, format!(
"segment {} claims {} meta nodes at offset {} but is only {} bytes",
segid, nmetablk, base, seg.len())));
}
let fmt = self.block_ptr_format;
for i in 0..nmetablk {
let off = base + i * node_size;
let key = BlockPtrFormat::encode(segid, off, i, &fmt);
let mut store = self.store.write().unwrap();
if !store.meta_keys.insert(key) {
debug!("memory staging: meta key {} already published, skipping", key);
continue;
}
drop(store);
self.loader.write(key, &seg[off..off + node_size]);
}
Ok(())
}
fn segment_sum(&self, segid: SegmentId) -> Result<SegmentSum> {
let store = self.store.read().unwrap();
let Some(seg) = store.segments.get(&segid) else {
return Err(Error::new(ErrorKind::NotFound,
format!("segment {} does not exist in memory staging", segid)));
};
let hdr_size = SegmentHeader::size();
if seg.len() < hdr_size {
return Err(Error::new(ErrorKind::InvalidData, "incorrect segment header size"));
}
let hdr = SegmentHeader::from_slice(&seg[..hdr_size]);
let ss_bytes = hdr.s_bytes as usize;
if seg.len() < ss_bytes {
return Err(Error::new(ErrorKind::InvalidData,
format!("segment summary truncated in segment {}: header claims {} bytes, segment has {}",
segid, ss_bytes, seg.len())));
}
Ok(SegmentSum::from_slice(&seg[..ss_bytes]))
}
}
impl Staging<MemoryBlockLoader<BlockPtr>> for MemoryStaging {
fn to_block_loader(&self) -> MemoryBlockLoader<BlockPtr> {
self.loader.clone()
}
fn read_timing(&self) -> &ReadTiming {
&self.read_timing
}
async fn load_inode(&self, buf: &mut [u8]) -> Result<Option<OnDiskState>> {
let start = std::time::Instant::now();
let store = self.store.read().unwrap();
let Some(inode) = store.inode.as_ref() else {
self.read_timing.add_inode_get(start.elapsed().as_nanos() as u64);
return Err(Error::new(ErrorKind::NotFound, "inode does not exist in memory staging"));
};
if buf.len() > inode.len() {
return Err(Error::new(ErrorKind::InvalidData,
format!("inode read of {} bytes but only {} stored", buf.len(), inode.len())));
}
buf.copy_from_slice(&inode[..buf.len()]);
drop(store);
self.read_timing.add_inode_get(start.elapsed().as_nanos() as u64);
Ok(None)
}
async fn load_inode_from_segment(&self, buf: &mut [u8], segid: SegmentId) -> Result<Option<OnDiskState>> {
if segid.as_cno() == 0 {
return self.load_inode(buf).await;
}
let inode_off = std::mem::offset_of!(SegmentHeader, s_inode);
let inode_bytes = std::mem::size_of::<InodeRaw>();
if buf.len() > inode_bytes {
return Err(Error::new(ErrorKind::InvalidData,
format!("inode read of {} bytes exceeds the {} an inode occupies", buf.len(), inode_bytes)));
}
let start = std::time::Instant::now();
let res = {
let store = self.store.read().unwrap();
let mut r = store.read_at(segid.at_part(crate::segment::Segment::SUMMARY_PART), inode_off, buf);
if r.is_err() {
r = store.read_at(segid.checkpoint(), inode_off, buf);
}
r
};
self.read_timing.add_inode_get(start.elapsed().as_nanos() as u64);
res?;
Ok(None)
}
async fn segment_exists(&self, segid: SegmentId) -> Result<bool> {
Ok(self.store.read().unwrap().segments.contains_key(&segid))
}
async fn load_segment_timestamp(&self, segid: SegmentId) -> Result<(i64, i64)> {
let store = self.store.read().unwrap();
let Some(ts) = store.timestamps.get(&segid.at_part(crate::segment::Segment::SUMMARY_PART))
.or_else(|| store.timestamps.get(&segid.checkpoint())) else {
return Err(Error::new(ErrorKind::NotFound,
format!("checkpoint {} does not exist in memory staging", segid)));
};
Ok((Self::now(), *ts))
}
async fn flush_inode(&self, buf: &[u8], inode_state: &Option<OnDiskState>, flag: FlushInodeFlag) -> Result<Option<OnDiskState>> {
if let Some(i) = &self.interceptor {
let _ = i.before_flush_inode(&self, buf, flag.clone()).await?;
}
self.store.write().unwrap().inode = Some(buf.to_vec());
if let Some(i) = &self.interceptor {
let _ = i.after_flush_inode(&self, buf, flag).await;
}
Ok(inode_state.clone())
}
async fn remove_inode(&self, _inode_state: &Option<OnDiskState>) -> Result<()> {
self.store.write().unwrap().inode = None;
if let Some(i) = &self.interceptor {
let _ = i.after_remove_inode(&self).await;
}
Ok(())
}
async fn load_data_block(&self, segid: SegmentId, staging_off: usize, offset: usize, block_size: usize, buf: &mut [u8]) -> Result<()> {
let start_off = staging_off + offset;
debug!("mem staging: load data block from segment {} at offset {} size {}", segid, start_off, block_size);
let start = std::time::Instant::now();
let res = self.store.read().unwrap().read_at(segid, start_off, buf);
self.read_timing.add_data_get(block_size, start.elapsed().as_nanos() as u64);
res
}
async fn load_range(&self, segid: SegmentId, s3_off: usize, buf: &mut [u8]) -> Result<()> {
let len = buf.len();
debug_assert!(len > 0, "load_range with zero length");
debug!("mem staging: load range from segment {} at offset {} len {}", segid, s3_off, len);
let start = std::time::Instant::now();
let res = self.store.read().unwrap().read_at(segid, s3_off, buf);
self.read_timing.add_data_get(len, start.elapsed().as_nanos() as u64);
res
}
fn new_segwr(&self, segid: SegmentId, hyper_file_config: &HyperFileMetaConfig) -> segment::Writer<MemoryStaging> {
segment::Writer::<MemoryStaging>::new(self.writing_as(hyper_file_config), self.runtime_config.segment_buffer_size, segid, hyper_file_config)
}
fn new_segwr_at(&self, segid: SegmentId, hyper_file_config: &HyperFileMetaConfig) -> segment::Writer<MemoryStaging> {
segment::Writer::<MemoryStaging>::new_at(self.writing_as(hyper_file_config), self.runtime_config.segment_buffer_size, segid, hyper_file_config)
}
fn dir_filename(&self) -> (&str, &str) {
if let Some((dir, filename)) = self.root_path.rsplit_once('/') {
return (dir, filename);
}
("", &self.root_path)
}
fn root_path(&self) -> &str {
&self.root_path
}
fn config(&self) -> &StagingConfig {
&self.config
}
async fn unlink(&self) -> Result<()> {
self.remove_inode(&None).await?;
let mut store = self.store.write().unwrap();
store.segments.clear();
store.timestamps.clear();
Ok(())
}
fn interceptor(&mut self, i: impl StagingIntercept<Self> + 'static) {
self.interceptor = Some(Arc::new(i));
}
}
impl SegmentReadWrite for MemoryStaging {
fn append(&self, segid: SegmentId, buf: &[u8]) -> Result<()> {
let _ = (segid, buf);
Ok(())
}
async fn done(&self, segid: SegmentId, buf: &[u8], len: usize) -> Result<()> {
if let Some(i) = &self.interceptor {
let _ = i.before_segment_done(&self, segid, buf, len).await?;
}
let (data, _) = buf.split_at(len);
debug!("mem staging: store segment {} of {} bytes", segid, len);
{
let mut store = self.store.write().unwrap();
store.segments.insert(segid, data.to_vec());
store.timestamps.insert(segid, Self::now());
}
self.publish_meta_nodes(segid, data)
}
async fn done_pieces(&self, segid: SegmentId, body: SegmentBody) -> Result<()> {
let body = body.materialize().await?;
let mut data = Vec::with_capacity(body.len());
for piece in body.byte_pieces() {
data.extend_from_slice(piece);
}
if let Some(i) = &self.interceptor {
let _ = i.before_segment_done(&self, segid, &data, data.len()).await?;
}
debug!("mem staging: store segment {} of {} bytes (pieces)", segid, data.len());
{
let mut store = self.store.write().unwrap();
store.segments.insert(segid, data.clone());
store.timestamps.insert(segid, Self::now());
}
self.publish_meta_nodes(segid, &data)
}
async fn remove(&self, segid: SegmentId) -> Result<()> {
let mut store = self.store.write().unwrap();
store.segments.remove(&segid);
store.timestamps.remove(&segid);
Ok(())
}
async fn open(&self, segid: SegmentId) -> Result<SegmentSum> {
match self.segment_sum(segid) {
Err(e) if e.kind() == ErrorKind::NotFound && segid.part_id().is_none() =>
self.segment_sum(segid.at_part(crate::segment::Segment::SUMMARY_PART)),
other => other,
}
}
async fn list(&self, segid: SegmentId) -> Result<Vec<SegmentId>> {
let store = self.store.read().unwrap();
{
let mut v: Vec<SegmentId> = store.segments.keys()
.map(|o| o.checkpoint()).filter(|s| *s >= segid.checkpoint()).collect();
v.dedup();
Ok(v)
}
}
async fn build_block_map(&self, segid: SegmentId) -> Result<Vec<(BlockIndex, BlockPtr)>> {
let (meta_block_off, meta_block_size, nmetablk) = {
let store = self.store.read().unwrap();
let Some(seg) = store.segments.get(&segid) else {
return Err(Error::new(ErrorKind::NotFound,
format!("segment {} does not exist in memory staging", segid)));
};
if seg.len() < SegmentHeader::size() {
return Err(Error::new(ErrorKind::InvalidData, "incorrect segment header size"));
}
let hdr = SegmentHeader::from_slice(&seg[..SegmentHeader::size()]);
(hdr.aligned_ss_bytes(), (1usize << hdr.s_meta_blk_shift), hdr.s_nmetablk as usize)
};
if nmetablk == 0 {
return Ok(Vec::new());
}
let mut buf = vec![0u8; meta_block_size * nmetablk];
self.store.read().unwrap().read_at(segid, meta_block_off, &mut buf)?;
let mut v = Vec::new();
for meta_block_slice in buf.chunks(meta_block_size) {
let node = BtreeNode::<BlockIndex, BlockPtr, BlockPtr>::from_slice_ref(meta_block_slice)?;
if node.get_level() == BTREE_NODE_LEVEL_DATA + 1 {
for idx in 0..node.get_nchild() {
v.push((*node.get_key(idx), *node.get_val(idx)));
}
}
}
Ok(v)
}
}
impl std::fmt::Debug for MemoryStaging {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let store = self.store.read().unwrap();
f.debug_struct("MemoryStaging")
.field("root_path", &self.root_path)
.field("segments", &store.segments.len())
.field("has_inode", &store.inode.is_some())
.finish()
}
}