#![forbid(unsafe_code)]
use std::collections::HashMap;
use crate::core::extent::ChunkId;
use crate::core::representation::Representation;
use crate::format::version::RecordTag;
use crate::store::StoreError;
use crate::store::index::{BTreeError, ObjectProvider};
use crate::store::root::Root;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CrashPoint {
AfterRecordAppend,
AfterSegmentFdatasync,
AfterSegmentDirFsync,
AfterRootWrite,
AfterSuperblockWrite,
AfterSuperblockFsync,
BeforeOldSegmentDelete,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CrashHooks {
pub armed: Option<CrashPoint>,
}
impl CrashHooks {
pub fn none() -> Self {
Self { armed: None }
}
pub fn crash_at(point: CrashPoint) -> Self {
Self { armed: Some(point) }
}
pub fn hit(&self, point: CrashPoint) -> Result<(), StoreError> {
if self.armed == Some(point) {
Err(StoreError::CrashSimulated(format!("{point:?}")))
} else {
Ok(())
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct PendingRecord {
pub(crate) tag: RecordTag,
pub(crate) payload: Vec<u8>,
pub(crate) materialized_len: Option<u64>,
}
pub struct Tx<'a> {
pending: HashMap<ChunkId, Vec<u8>>,
records: Vec<PendingRecord>,
root: Root,
pub(crate) store: &'a mut crate::store::Store,
}
impl<'a> Tx<'a> {
pub(crate) fn begin(store: &'a mut crate::store::Store) -> Self {
Self {
pending: HashMap::new(),
records: Vec::new(),
root: store.current_root().clone(),
store,
}
}
pub fn root(&self) -> &Root {
&self.root
}
pub fn root_mut(&mut self) -> &mut Root {
&mut self.root
}
pub fn resolve_descriptor(&self, cid: &ChunkId) -> Result<Option<Representation>, StoreError> {
let bytes = self.fetch_pending_or_store(cid)?;
match bytes {
Some(b) => {
let rep = crate::format::descriptor::decode(
&b,
self.store.limits().max_descriptor_bytes,
self.store.limits().max_inline_bytes,
self.store.limits().max_palette,
self.store.limits().max_period,
self.store.limits().max_chunk_size,
)?;
Ok(Some(rep))
}
None => Ok(None),
}
}
pub(crate) fn fetch_pending_or_store(
&self,
id: &ChunkId,
) -> Result<Option<Vec<u8>>, StoreError> {
if let Some(b) = self.pending.get(id) {
return Ok(Some(b.clone()));
}
self.store.fetch_object(id)
}
pub fn commit(self, hooks: &CrashHooks) -> Result<(), StoreError> {
let store = self.commit_deferred(hooks)?;
store.durability_barrier(hooks)?;
Ok(())
}
pub fn commit_deferred(
mut self,
hooks: &CrashHooks,
) -> Result<&'a mut crate::store::Store, StoreError> {
self.store.ensure_commit_space(&self.records)?;
self.root.generation = self.store.generation() + 1;
self.root.segment_seq = self.store.current_segment_seq();
let root_bytes = self.root.encode();
let root_id = ChunkId::of(&root_bytes);
self.records.push(PendingRecord {
tag: RecordTag::Root,
payload: root_bytes,
materialized_len: None,
});
hooks.hit(CrashPoint::AfterRootWrite)?;
self.store.append_records(&mut self.records)?;
self.store.flush_segment()?;
self.store.write_superblock(root_id, &self.root)?;
self.store.publish_commit(&self.root, root_id)?;
Ok(self.store)
}
}
impl<'a> ObjectProvider for Tx<'a> {
fn get(&self, id: &ChunkId) -> Result<Option<Vec<u8>>, BTreeError> {
self.fetch_pending_or_store(id)
.map_err(|e| BTreeError::Provider(e.to_string()))
}
fn put(&mut self, id: ChunkId, bytes: Vec<u8>) {
self.pending.insert(id, bytes.clone());
self.records.push(PendingRecord {
tag: RecordTag::BtreeNode,
payload: bytes,
materialized_len: None,
});
}
}
pub fn put_object(
tx: &mut Tx<'_>,
tag: RecordTag,
payload: Vec<u8>,
materialized_len: Option<u64>,
) -> ChunkId {
let id = ChunkId::of(&payload);
tx.pending.insert(id, payload.clone());
tx.records.push(PendingRecord {
tag,
payload,
materialized_len,
});
id
}
pub fn put_inode(tx: &mut Tx<'_>, inode: &crate::store::inode::Inode) -> ChunkId {
put_object(tx, RecordTag::Inode, inode.encode(), None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crash_points_are_distinct() {
let points = [
CrashPoint::AfterRecordAppend,
CrashPoint::AfterSegmentFdatasync,
CrashPoint::AfterSegmentDirFsync,
CrashPoint::AfterRootWrite,
CrashPoint::AfterSuperblockWrite,
CrashPoint::AfterSuperblockFsync,
CrashPoint::BeforeOldSegmentDelete,
];
let mut seen = std::collections::HashSet::new();
for p in points {
assert!(seen.insert(p));
}
assert_eq!(points.len(), 7);
}
}