use crate::foundation::{
AuthorityId, Digest, DurableCommit, Epoch, Head, OperationId, ProposedCommit, Sequence,
};
use crate::performance::{MeasuredResult, OperationFailure, WorkBudget, WorkCounters};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;
pub const OBJECT_DIGEST_ENVELOPE_BYTES: u64 = b"acyclic-fs-object-v1\0".len() as u64 + 1 + 8;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReplayLimit {
pub records: u32,
pub payload_bytes: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CreateAuthorityOutcome {
Created(Head),
Existing(Head),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AppendOutcome {
Committed(DurableCommit),
AlreadyCommitted(DurableCommit),
Conflict {
actual: Head,
},
Fenced {
actual_epoch: Epoch,
},
IdempotencyConflict {
committed_fingerprint: Digest,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublicationPermit {
Unrestricted,
Lease {
authority_id: [u8; 16],
workspace_id: [u8; 16],
lease_id: [u8; 16],
expires_at_millis: u64,
},
Reservation {
operation_id: [u8; 16],
gate_tail: u64,
expected: Head,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublicationReservation {
pub authority_id: AuthorityId,
pub operation_id: OperationId,
pub expected: Head,
pub gate_tail: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReservationOutcome {
Reserved(PublicationReservation),
AlreadyReserved(PublicationReservation),
Conflict {
actual: Head,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GuardedAppend {
pub authority_id: AuthorityId,
pub epoch: Epoch,
pub expected: Head,
pub commit: ProposedCommit,
pub permit: PublicationPermit,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FenceOutcome {
Advanced(Head),
Conflict {
actual: Head,
},
}
pub trait AuthorityStore: Send + Sync {
fn create_authority(
&self,
authority_id: AuthorityId,
genesis_epoch: Epoch,
budget: WorkBudget,
) -> AuthorityResult<CreateAuthorityOutcome>;
fn head(&self, authority_id: AuthorityId, budget: WorkBudget) -> AuthorityResult<Head>;
fn compare_and_append(
&self,
authority_id: AuthorityId,
epoch: Epoch,
expected: Head,
commit: ProposedCommit,
budget: WorkBudget,
) -> AuthorityResult<AppendOutcome>;
fn replay(
&self,
authority_id: AuthorityId,
after: Sequence,
limit: ReplayLimit,
budget: WorkBudget,
) -> AuthorityResult<Vec<DurableCommit>>;
fn fence(
&self,
authority_id: AuthorityId,
expected: Head,
budget: WorkBudget,
) -> AuthorityResult<FenceOutcome>;
fn find_operation(
&self,
authority_id: AuthorityId,
operation_id: OperationId,
budget: WorkBudget,
) -> AuthorityResult<Option<DurableCommit>>;
}
impl<T: AuthorityStore + ?Sized> AuthorityStore for Arc<T> {
fn create_authority(
&self,
authority_id: AuthorityId,
genesis_epoch: Epoch,
budget: WorkBudget,
) -> AuthorityResult<CreateAuthorityOutcome> {
(**self).create_authority(authority_id, genesis_epoch, budget)
}
fn head(&self, authority_id: AuthorityId, budget: WorkBudget) -> AuthorityResult<Head> {
(**self).head(authority_id, budget)
}
fn compare_and_append(
&self,
authority_id: AuthorityId,
epoch: Epoch,
expected: Head,
commit: ProposedCommit,
budget: WorkBudget,
) -> AuthorityResult<AppendOutcome> {
(**self).compare_and_append(authority_id, epoch, expected, commit, budget)
}
fn replay(
&self,
authority_id: AuthorityId,
after: Sequence,
limit: ReplayLimit,
budget: WorkBudget,
) -> AuthorityResult<Vec<DurableCommit>> {
(**self).replay(authority_id, after, limit, budget)
}
fn fence(
&self,
authority_id: AuthorityId,
expected: Head,
budget: WorkBudget,
) -> AuthorityResult<FenceOutcome> {
(**self).fence(authority_id, expected, budget)
}
fn find_operation(
&self,
authority_id: AuthorityId,
operation_id: OperationId,
budget: WorkBudget,
) -> AuthorityResult<Option<DurableCommit>> {
(**self).find_operation(authority_id, operation_id, budget)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthorityReceipt<T> {
pub value: T,
pub work: WorkCounters,
}
pub type AuthorityFailure = OperationFailure<AuthorityStoreError>;
pub type AuthorityResult<T> = MeasuredResult<AuthorityReceipt<T>, AuthorityStoreError>;
#[derive(Debug, Error)]
pub enum AuthorityStoreError {
#[error("authority operation was cancelled")]
Cancelled,
#[error("authority does not exist")]
Missing,
#[error("replay limit must have non-zero record and payload bounds")]
InvalidReplayLimit,
#[error("next authority record has {observed} payload bytes; replay maximum is {maximum}")]
ReplayRecordTooLarge {
observed: u64,
maximum: u64,
},
#[error("authority payload has {observed} bytes; maximum is {maximum}")]
PayloadTooLarge {
observed: u64,
maximum: u64,
},
#[error("authority epoch exhausted")]
EpochExhausted,
#[error("authority sequence exhausted")]
SequenceExhausted,
#[error("authority storage is corrupt: {0}")]
Corrupt(String),
#[error("authority storage I/O failed: {0}")]
Io(#[from] std::io::Error),
#[error("authority {operation} outcome is indeterminate: {source}")]
Indeterminate {
operation: &'static str,
#[source]
source: std::io::Error,
},
#[error("authority backend rejected operation: {0}")]
Rejected(String),
#[error(transparent)]
Work(#[from] crate::performance::WorkError),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ObjectKind {
Blob,
BlobChunk,
TreePage,
ExtentPage,
FileTablePage,
GenerationRoot,
Metadata,
AttributePage,
}
impl ObjectKind {
#[must_use]
pub const fn canonical_tag(self) -> u8 {
match self {
Self::Blob => 1,
Self::BlobChunk => 2,
Self::TreePage => 3,
Self::ExtentPage => 4,
Self::FileTablePage => 5,
Self::GenerationRoot => 6,
Self::Metadata => 7,
Self::AttributePage => 8,
}
}
pub const fn from_canonical_tag(tag: u8) -> Result<Self, ObjectKindError> {
match tag {
1 => Ok(Self::Blob),
2 => Ok(Self::BlobChunk),
3 => Ok(Self::TreePage),
4 => Ok(Self::ExtentPage),
5 => Ok(Self::FileTablePage),
6 => Ok(Self::GenerationRoot),
7 => Ok(Self::Metadata),
8 => Ok(Self::AttributePage),
value => Err(ObjectKindError::Unknown(value)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum ObjectKindError {
#[error("unknown object kind tag {0}")]
Unknown(u8),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ObjectId {
pub kind: ObjectKind,
pub digest: Digest,
}
#[must_use]
pub fn object_digest(kind: ObjectKind, bytes: &[u8]) -> Digest {
let mut hasher = blake3::Hasher::new();
hasher.update(b"acyclic-fs-object-v1\0");
hasher.update(&[kind.canonical_tag()]);
hasher.update(&u64::try_from(bytes.len()).unwrap_or(u64::MAX).to_le_bytes());
hasher.update(bytes);
Digest::from_bytes(*hasher.finalize().as_bytes())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ByteRange {
pub offset: u64,
pub length: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObjectReceipt<T> {
pub value: T,
pub work: WorkCounters,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ObjectReadRetention {
Shared,
Owned {
logical_bytes: u64,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObjectRead {
pub bytes: Bytes,
pub retention: ObjectReadRetention,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ObjectReadRequest {
pub object_id: ObjectId,
pub maximum_bytes: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ObjectWrite {
pub object_id: ObjectId,
pub bytes: Bytes,
}
impl std::ops::Deref for ObjectRead {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.bytes
}
}
pub type ObjectFailure = OperationFailure<ObjectStoreError>;
pub type ObjectResult<T> = MeasuredResult<ObjectReceipt<T>, ObjectStoreError>;
pub trait ObjectStore: Send + Sync {
fn put(&self, object_id: ObjectId, bytes: Bytes, budget: WorkBudget) -> ObjectResult<()>;
fn put_many(&self, writes: &[ObjectWrite], budget: WorkBudget) -> ObjectResult<()>;
fn read(
&self,
object_id: ObjectId,
maximum_bytes: u64,
budget: WorkBudget,
) -> ObjectResult<ObjectRead>;
fn read_many(
&self,
requests: &[ObjectReadRequest],
budget: WorkBudget,
) -> ObjectResult<Vec<ObjectRead>>;
fn contains(&self, object_id: ObjectId, budget: WorkBudget) -> ObjectResult<bool>;
}
impl<T: ObjectStore + ?Sized> ObjectStore for Arc<T> {
fn put(&self, object_id: ObjectId, bytes: Bytes, budget: WorkBudget) -> ObjectResult<()> {
(**self).put(object_id, bytes, budget)
}
fn put_many(&self, writes: &[ObjectWrite], budget: WorkBudget) -> ObjectResult<()> {
(**self).put_many(writes, budget)
}
fn read(
&self,
object_id: ObjectId,
maximum_bytes: u64,
budget: WorkBudget,
) -> ObjectResult<ObjectRead> {
(**self).read(object_id, maximum_bytes, budget)
}
fn read_many(
&self,
requests: &[ObjectReadRequest],
budget: WorkBudget,
) -> ObjectResult<Vec<ObjectRead>> {
(**self).read_many(requests, budget)
}
fn contains(&self, object_id: ObjectId, budget: WorkBudget) -> ObjectResult<bool> {
(**self).contains(object_id, budget)
}
}
#[derive(Debug, Error)]
pub enum ObjectStoreError {
#[error("object operation was cancelled")]
Cancelled,
#[error("object is missing")]
Missing,
#[error("object digest does not match canonical bytes")]
DigestMismatch,
#[error("object is {observed} bytes; maximum is {maximum}")]
TooLarge {
observed: u64,
maximum: u64,
},
#[error("object range is outside the admitted object")]
InvalidRange,
#[error("stored object is corrupt")]
Corrupt,
#[error("corrupt object quarantine failed: {0}")]
QuarantineFailed(#[source] std::io::Error),
#[error("object storage I/O failed: {0}")]
Io(#[from] std::io::Error),
#[error("object backend rejected operation: {0}")]
Rejected(String),
#[error(transparent)]
Work(#[from] crate::performance::WorkError),
}
#[cfg(test)]
#[path = "tests/storage.rs"]
mod tests;