use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::path::Path;
use super::durable_record::{
CreateExclusive, DurableRecordStore, EntryFence, entry_fence_with_source,
};
use super::native_record_store::NativeDurableRecordStore;
use super::operation_error::RecordOperationError;
use super::persist::CodecError;
use super::policy::BranchKind;
pub use super::refrecord::{BranchRefRecord, BranchShardRef};
use super::snapshot::Timestamp;
use crate::ids::ShardId;
use crate::tree::Hash;
#[path = "refstore_codec.rs"]
pub(crate) mod codec;
#[cfg(test)]
use codec::encode_record;
pub(crate) use codec::{REF_EXTENSION, decode_record, ref_file_name};
pub(crate) const TEMP_PREFIX: &str = ".branch-";
pub(crate) const TEMP_SUFFIX: &str = ".tmp";
#[derive(Debug)]
pub enum BranchRefError {
DuplicateBranch(String),
NameHashCollision { requested: String, existing: String },
StaleSeq {
name: String,
expected: u64,
found: u64,
},
BranchGenerationMismatch {
name: String,
expected_created: Timestamp,
found_created: Timestamp,
},
BranchRemoved(String),
InvalidKindLineage {
name: String,
kind: BranchKind,
},
DuplicateShard { name: String, shard_id: ShardId },
UnknownShard { name: String, shard_id: ShardId },
Corrupt(String),
Io(std::io::Error),
}
impl fmt::Display for BranchRefError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateBranch(name) => write!(f, "branch already exists: {name}"),
Self::NameHashCollision {
requested,
existing,
} => write!(
f,
"branch name hash collision: {requested:?} maps to the same ref file as existing branch {existing:?}"
),
Self::StaleSeq {
name,
expected,
found,
} => write!(
f,
"stale branch commit sequence for {name}: handle expected {expected}, record holds {found}"
),
Self::BranchGenerationMismatch {
name,
expected_created,
found_created,
} => write!(
f,
"branch generation mismatch for {name}: handle was bound to creation {expected_created}, record was created at {found_created}"
),
Self::BranchRemoved(name) => write!(f, "branch has been removed: {name}"),
Self::InvalidKindLineage { name, kind } => write!(
f,
"branch {name} of kind {kind} cannot store a namespace lineage"
),
Self::DuplicateShard { name, shard_id } => {
write!(f, "branch {name} names shard {shard_id} twice")
}
Self::UnknownShard { name, shard_id } => {
write!(f, "branch {name} has no shard {shard_id}")
}
Self::Corrupt(reason) => write!(f, "branch ref store corrupted: {reason}"),
Self::Io(error) => write!(f, "branch ref store I/O error: {error}"),
}
}
}
impl std::error::Error for BranchRefError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
_ => None,
}
}
}
impl From<std::io::Error> for BranchRefError {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
impl From<CodecError> for BranchRefError {
fn from(error: CodecError) -> Self {
match error {
CodecError::Corrupt(reason) => Self::Corrupt(reason),
CodecError::Io(io_error) => Self::Io(io_error),
}
}
}
#[derive(Debug)]
pub struct BranchRefStore {
backend: Box<dyn DurableRecordStore<Error = BranchRefError>>,
records: BTreeMap<String, BranchRefRecord>,
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<BranchRefStore>();
assert_send_sync::<NativeDurableRecordStore>();
};
impl BranchRefStore {
pub fn open<P: AsRef<Path>>(dir: P) -> Result<Self, BranchRefError> {
let backend = NativeDurableRecordStore::open(dir)?;
Self::from_backend(backend)
}
pub(super) fn from_backend(
backend: impl DurableRecordStore<Error = BranchRefError> + 'static,
) -> Result<Self, BranchRefError> {
let mut backend: Box<dyn DurableRecordStore<Error = BranchRefError>> = Box::new(backend);
let records = backend
.list_read_at_open()?
.into_iter()
.map(|record| (record.name.clone(), record))
.collect();
Ok(Self { backend, records })
}
pub fn get(&self, name: &str) -> Option<&BranchRefRecord> {
self.records.get(name)
}
pub fn list(&self) -> impl Iterator<Item = &BranchRefRecord> {
self.records.values()
}
pub fn protected_roots(&self) -> HashSet<Hash> {
self.records
.values()
.flat_map(|record| record.shards.iter())
.flat_map(|shard| [shard.fork_anchor, shard.head])
.collect()
}
pub(crate) fn create(&mut self, record: BranchRefRecord) -> Result<(), BranchRefError> {
self.create_with_source(record)
.map_err(RecordOperationError::into_public)
}
pub(crate) fn create_with_source(
&mut self,
mut record: BranchRefRecord,
) -> Result<(), RecordOperationError<BranchRefError>> {
if record.kind == BranchKind::Namespace && record.namespace_lineage.is_some() {
return Err(BranchRefError::InvalidKindLineage {
name: record.name,
kind: record.kind,
}
.into());
}
if self.records.contains_key(&record.name) {
return Err(BranchRefError::DuplicateBranch(record.name).into());
}
record.shards.sort_by_key(|shard| shard.shard_id);
if let Some(pair) = record
.shards
.windows(2)
.find(|pair| pair[0].shard_id == pair[1].shard_id)
{
return Err(BranchRefError::DuplicateShard {
name: record.name,
shard_id: pair[0].shard_id,
}
.into());
}
match self.backend.create_exclusive(&record)? {
CreateExclusive::Installed => {
self.records.insert(record.name.clone(), record.clone());
entry_fence_with_source(self.backend.as_mut(), EntryFence::Present(&record))
.map_err(RecordOperationError::Fence)
}
CreateExclusive::TargetExists(existing) if existing.name == record.name => {
Err(BranchRefError::DuplicateBranch(record.name).into())
}
CreateExclusive::TargetExists(existing) => Err(BranchRefError::NameHashCollision {
requested: record.name,
existing: existing.name,
}
.into()),
}
}
pub(crate) fn cas_check(
&self,
name: &str,
expected_created: Timestamp,
expected_seq: u64,
) -> Result<&BranchRefRecord, BranchRefError> {
let Some(record) = self.records.get(name) else {
return Err(BranchRefError::BranchRemoved(name.to_owned()));
};
if record.created != expected_created {
return Err(BranchRefError::BranchGenerationMismatch {
name: name.to_owned(),
expected_created,
found_created: record.created,
});
}
if record.seq != expected_seq {
return Err(BranchRefError::StaleSeq {
name: name.to_owned(),
expected: expected_seq,
found: record.seq,
});
}
Ok(record)
}
pub(crate) fn advance(
&mut self,
name: &str,
expected_created: Timestamp,
expected_seq: u64,
heads: &[(ShardId, Hash)],
parents: Vec<Hash>,
timestamp: Timestamp,
) -> Result<u64, BranchRefError> {
self.advance_with_source(
name,
expected_created,
expected_seq,
heads,
parents,
timestamp,
)
.map_err(RecordOperationError::into_public)
}
fn advance_with_source(
&mut self,
name: &str,
expected_created: Timestamp,
expected_seq: u64,
heads: &[(ShardId, Hash)],
parents: Vec<Hash>,
timestamp: Timestamp,
) -> Result<u64, RecordOperationError<BranchRefError>> {
let Some(record) = self.records.get(name) else {
return Err(BranchRefError::BranchRemoved(name.to_owned()).into());
};
let mut replacement = record.clone();
replacement.seq = expected_seq + 1;
replacement.timestamp = timestamp;
replacement.parents = parents;
for &(shard_id, head) in heads {
let Some(shard) = replacement
.shards
.iter_mut()
.find(|shard| shard.shard_id == shard_id)
else {
return Err(BranchRefError::UnknownShard {
name: name.to_owned(),
shard_id,
}
.into());
};
shard.head = head;
}
let installed =
self.backend
.cas_replace_install(name, expected_created, expected_seq, &replacement)?;
let seq = installed.seq;
self.records.insert(name.to_owned(), installed.clone());
entry_fence_with_source(self.backend.as_mut(), EntryFence::Present(&installed))
.map_err(RecordOperationError::Fence)?;
Ok(seq)
}
pub fn remove(&mut self, name: &str) -> Result<Option<BranchRefRecord>, BranchRefError> {
self.remove_with_source(name)
.map_err(RecordOperationError::into_public)
}
fn remove_with_source(
&mut self,
name: &str,
) -> Result<Option<BranchRefRecord>, RecordOperationError<BranchRefError>> {
if !self.records.contains_key(name) {
return Ok(None);
}
self.backend.unlink(name)?;
entry_fence_with_source(self.backend.as_mut(), EntryFence::Absent(name))
.map_err(RecordOperationError::Fence)?;
Ok(self.records.remove(name))
}
}
#[cfg(test)]
#[path = "refstore_kind_tests.rs"]
mod kind_tests;
#[cfg(test)]
#[path = "refstore_tests.rs"]
mod tests;