use super::storage::artifact::{ArtifactId, PhysicalRef};
use super::{GraphGeneration, OperationFields, StoreInstanceId};
pub const MAX_ENVELOPE_BYTES: usize = 16 * 1024 * 1024;
pub const STACK_RESERVATION_BYTES: usize = 64 * 1024;
pub const HEADER_BYTES: usize = 64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WalError {
Unsupported,
Malformed,
Checksum,
Store,
Sequence,
HighWater,
Capacity,
WorkLimit,
Cancelled,
MissingArtifact,
Participant,
Failed,
}
impl std::fmt::Display for WalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "graph WAL: {self:?}")
}
}
impl std::error::Error for WalError {}
pub struct WalResources<'a> {
remaining: u64,
consumed: u64,
accounting: Option<super::resources::GraphResources>,
cancelled: &'a mut dyn FnMut() -> bool,
}
impl<'a> WalResources<'a> {
pub fn new(
work: u64,
stack_allowance: usize,
cancelled: &'a mut dyn FnMut() -> bool,
) -> Result<Self, WalError> {
if stack_allowance < STACK_RESERVATION_BYTES {
return Err(WalError::Capacity);
}
let mut value = Self {
remaining: work,
consumed: 0,
accounting: None,
cancelled,
};
value.charge(0)?;
Ok(value)
}
pub(crate) fn with_accounting(mut self, resources: &super::resources::GraphResources) -> Self {
self.accounting = Some(resources.clone());
self
}
pub fn charge(&mut self, units: u64) -> Result<(), WalError> {
if (self.cancelled)() {
return Err(WalError::Cancelled);
}
self.remaining = self
.remaining
.checked_sub(units)
.ok_or(WalError::WorkLimit)?;
self.consumed = self
.consumed
.checked_add(units)
.ok_or(WalError::WorkLimit)?;
if let Some(accounting) = &self.accounting {
accounting.record_work(crate::lifecycle::stats::GraphWorkKind::WalCodecUnits, units);
}
Ok(())
}
pub(crate) const fn remaining(&self) -> u64 {
self.remaining
}
pub const fn consumed(&self) -> u64 {
self.consumed
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BatchId(u128);
impl BatchId {
pub fn new(value: u128) -> Result<Self, WalError> {
if value == 0 {
Err(WalError::Malformed)
} else {
Ok(Self(value))
}
}
pub const fn get(self) -> u128 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EnvelopeKind {
Mutation,
Maintenance,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ArtifactDescriptor {
pub store: StoreInstanceId,
pub artifact: ArtifactId,
pub generation: GraphGeneration,
pub serial: u64,
pub bytes: u32,
pub family: u16,
pub version: u16,
pub checksum: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RequiredRef {
pub object: ArtifactDescriptor,
pub block: PhysicalRef,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct WalGraphRoots {
pub slots: [Option<RequiredRef>; 8],
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct HighWaters {
pub node: u128,
pub relationship: u128,
pub symbols: [u64; 4],
pub creation_serial: u64,
}
#[derive(Clone, Copy, Debug)]
pub enum ReferenceList<'a> {
Values(&'a [RequiredRef]),
Encoded(&'a [u8]),
}
#[derive(Clone, Copy, Debug)]
pub enum DescriptorList<'a> {
Values(&'a [ArtifactDescriptor]),
Encoded(&'a [u8]),
}
#[derive(Clone, Copy, Debug)]
pub struct CommitState<'a> {
pub store: StoreInstanceId,
pub generation: GraphGeneration,
pub sequence: u64,
pub graph: WalGraphRoots,
pub catalog: RequiredRef,
pub vector: Option<RequiredRef>,
pub text: Option<RequiredRef>,
pub reclaim: Option<RequiredRef>,
pub high_waters: HighWaters,
pub prepared_inventories: ReferenceList<'a>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Membership {
pub text_before: bool,
pub text_after: bool,
pub vector_before: bool,
pub vector_after: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct Mutation<'a> {
pub provenance_version: u16,
pub provenance: OperationFields<'a>,
pub live: bool,
pub canonical: Option<RequiredRef>,
pub membership: Membership,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InventoryState {
Prepared,
Retained,
ReclaimPending(BatchId),
Reclaimed(BatchId),
}
#[derive(Clone, Copy, Debug)]
pub struct InventoryChange {
pub object: ArtifactDescriptor,
pub state: InventoryState,
}
#[derive(Clone, Copy, Debug)]
pub struct ReclaimIntent<'a> {
pub id: BatchId,
pub capture_generation: GraphGeneration,
pub capture_sequence: u64,
pub serial_fence: u64,
pub protected_roots: RequiredRef,
pub protected_digest: u64,
pub completed_mark: RequiredRef,
pub mark_digest: u64,
pub candidates: DescriptorList<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct ReclaimComplete<'a> {
pub id: BatchId,
pub intent: RequiredRef,
pub completed: DescriptorList<'a>,
pub remaining: DescriptorList<'a>,
}
#[derive(Clone, Copy, Debug)]
pub enum Change<'a> {
Mutation(Mutation<'a>),
Inventory(InventoryChange),
ReclaimIntent(ReclaimIntent<'a>),
ReclaimComplete(ReclaimComplete<'a>),
}
#[derive(Clone, Copy, Debug)]
pub struct Envelope<'a> {
pub batch: BatchId,
pub kind: EnvelopeKind,
pub changes: &'a [Change<'a>],
pub state: CommitState<'a>,
}
mod codec;
mod framing;
pub(crate) use framing::envelope_size;
pub use framing::{encode_envelope, encode_header};
mod checkpoint;
pub(crate) use checkpoint::{NativeCheckpoint, decode_checkpoint, encode_checkpoint};
pub(crate) use replay::{FramedCaptureStep, same_commit_state};
pub(crate) fn validate_graph_root_reference(
state: CommitState<'_>,
reference: RequiredRef,
) -> Result<(), WalError> {
framing::validate_ref(
reference,
state,
super::storage::artifact::BlockKind::TreePage,
)?;
codec::validate_reference_geometry(reference)
}
mod replay;
pub use replay::{
ChangeReader, ParticipantRole, Replay, ReplayEnd, ReplayStep, ReplayValidator, RequiredRole,
ValidatedEnvelope,
};
mod change;
mod maintenance;
pub use replay::validate_required_block;
#[cfg(all(test, feature = "allocation-audit"))]
mod allocation_tests;