mod ancestry;
mod append;
pub mod backup_envelope;
pub(crate) mod byte_cursor;
mod chain_walk;
pub mod cold_start;
mod compaction_report;
mod config;
#[cfg(feature = "payload-encryption")]
mod crypto_shred_api;
pub mod delivery;
mod diagnostics_api;
mod dir_lock;
mod error;
#[cfg(feature = "dangerous-test-hooks")]
#[cfg_attr(
all(docsrs, not(batpak_stable_docs)),
doc(cfg(feature = "dangerous-test-hooks"))
)]
pub mod fault;
mod file_classification;
mod fork_report;
mod frontier_api;
mod gate;
pub(crate) mod hidden_ranges;
mod import;
mod import_api;
pub mod index;
#[cfg(feature = "payload-encryption")]
#[cfg_attr(
all(docsrs, not(batpak_stable_docs)),
doc(cfg(feature = "payload-encryption"))
)]
pub mod keyscope;
mod lifecycle;
mod lifecycle_api;
mod lifecycle_close;
mod open;
mod platform;
pub mod projection;
mod projection_run;
pub mod reaction;
mod reactor_delivery;
pub mod reactor_typed;
mod read_api;
mod read_walk;
mod receipt_verification;
pub(crate) mod receipt_verify;
#[cfg(test)]
mod runtime_contracts;
pub mod segment;
mod signing;
#[cfg(feature = "dangerous-test-hooks")]
pub(crate) mod sim;
mod snapshot_report;
pub mod stats;
mod store_resource_report;
mod subscriber_frontier;
#[cfg(feature = "dangerous-test-hooks")]
mod test_support;
mod watch_api;
pub(crate) mod wire_header;
pub(crate) mod write;
mod write_api;
pub use ancestry::{AncestorWalk, AncestryBoundary};
pub use append::{
AppendOptions, AppendPositionHint, AppendReceipt, BatchAppendItem, CausationRef,
CompactionConfig, CompactionStrategy, DenialReceipt, DenialRequest, EncodedBytes, ExtensionKey,
ExtensionKeyError, ReceiptExtensionKey, ReceiptExtensionNamespace, ReceiptExtensionValue,
RetentionPredicate, SigningDowngradeBody, SigningDowngradeReason, SigningExtensionNamespace,
SIGNING_DOWNGRADE_SCHEMA_VERSION,
};
pub use chain_walk::{
ChainWalkEvidenceReport, ChainWalkFinding, ChainWalkHash, ChainWalkMode, ChainWalkReportBody,
ChainWalkReportError, ChainWalkRequest, ChainWalkStartRef, CHAIN_WALK_REPORT_SCHEMA_VERSION,
};
pub use compaction_report::{
compaction_strategy_shape, report_for_run, report_skipped, CompactionEvidenceHash,
CompactionEvidenceReport, CompactionReportBody, CompactionReportFinding,
CompactionStrategyShape, COMPACTION_REPORT_SCHEMA_VERSION,
};
pub use config::{
BatchConfig, ChainVerification, IdempotencyRetention, IndexConfig, IndexTopology,
OpenReportObserver, OverflowPolicy, SigningPolicy, StoreConfig, SyncConfig, SyncMode,
WriterConfig,
};
pub use delivery::canal::{Canal, CanalBatch, CanalClosed, CanalHandle, CanalItem, ReactorCanal};
pub use delivery::cursor::{
Cursor, CursorGapConfig, CursorWorkerAction, CursorWorkerConfig, CursorWorkerHandle,
GapObservation,
};
pub use delivery::observation::{
AtLeastOnce, CheckpointId, CheckpointIdError, IdempotencyKey, ObservedOnce,
MAX_CHECKPOINT_ID_LEN,
};
pub use delivery::subscription::Subscription;
pub use error::{
HiddenRangesCorruption, ProfileInvalidKind, StoreError, StoreInvariant, StoreLockMode,
};
#[cfg(feature = "dangerous-test-hooks")]
#[cfg_attr(
all(docsrs, not(batpak_stable_docs)),
doc(cfg(feature = "dangerous-test-hooks"))
)]
pub use fault::{
CountdownAction, CountdownInjector, FaultInjector, InjectionPoint, ProbabilisticInjector,
};
pub use fork_report::{
decode_fork_evidence_wire, encode_fork_evidence_wire, fork_report_body_hash, CopyPreference,
ForkCopyStrategy, ForkEvidenceHash, ForkFinding, ForkOptions, ForkReport, ForkReportBody,
ForkStrategyCounts, KeysetPolicy, FORK_EVIDENCE_REPORT_SCHEMA_VERSION,
};
pub use gate::DurabilityGate;
pub use import::{
decode_import_provenance_wire, encode_import_provenance_wire, provenance,
provenance_from_extensions, ImportFilter, ImportOptions, ImportProvenance, ImportReport,
ImportSelector, SourceNamespace, IMPORT_PROVENANCE_SCHEMA_VERSION,
};
pub use index::IndexEntry;
pub use cold_start::checkpoint::format::CHECKPOINT_VERSION;
pub use cold_start::mmap::format::MMAP_INDEX_VERSION;
pub use hidden_ranges::VISIBILITY_RANGES_VERSION;
pub use index::idemp::IDEMP_VERSION;
#[cfg(feature = "payload-encryption")]
#[cfg_attr(
all(docsrs, not(batpak_stable_docs)),
doc(cfg(feature = "payload-encryption"))
)]
pub use keyscope::backend::{FileKeysetBackend, KeysetBackend};
#[cfg(feature = "payload-encryption")]
#[cfg_attr(
all(docsrs, not(batpak_stable_docs)),
doc(cfg(feature = "payload-encryption"))
)]
pub use keyscope::{
scope_for, KeyScope, KeyScopeGranularity, KeyStore, KeyStoreError, PayloadKey, ShredScope,
};
#[cfg(any(feature = "alloc-count", feature = "fault-alloc"))]
pub use platform::alloc;
pub use platform::clock::{Clock, SystemClock};
pub use platform::fs::{
CowStrategyUsed, DirEntryInfo, FileKind, FileStat, PositionedReadError, RealFs, StagedFile,
StoreDirLockGuard, StoreFile, StoreFs,
};
pub use platform::mem_fs::MemFs;
pub use platform::spawn::{JobHandle, JobStatus, JoinError, Spawn, SpawnError, ThreadSpawn};
pub use platform::sync::ParentDirSyncAdmission;
pub use projection::flow::ReplayInput;
pub use projection::watch::{CursorWatcherError, ProjectionWatcher, WatcherError};
pub use projection::{
CacheCapabilities, CacheMeta, Freshness, NativeCache, NoCache, ProjectionCache,
};
pub type ProjectionFusion3<First, Second, Third> = (Option<First>, Option<Second>, Option<Third>);
pub use projection_run::{
ProjectionEvidenceRegistry, ProjectionRunCacheStatus, ProjectionRunCheckpointRef,
ProjectionRunEvidenceReport, ProjectionRunFinding, ProjectionRunFreshnessStatus,
ProjectionRunFrontierKind, ProjectionRunHash, ProjectionRunInputFrontier,
ProjectionRunOutputHash, ProjectionRunReplayMode, ProjectionRunReportBody,
ProjectionRunReportError, ProjectionRunRequestedFreshness, ProjectionSourceRef,
PROJECTION_RUN_REPORT_SCHEMA_VERSION,
};
pub use reaction::ReactionBatch;
pub use reactor_typed::{ReactorConfig, ReactorError, TypedReactorHandle};
pub use read_api::ChainVerificationReport;
#[cfg(feature = "payload-encryption")]
pub use read_api::{DeliveryPayload, ReadDisposition};
pub use read_walk::{
ReadWalkDroppedCount, ReadWalkEvidenceReport, ReadWalkFinding, ReadWalkFreshnessIntent,
ReadWalkFrontierKind, ReadWalkHash, ReadWalkInputFrontier, ReadWalkProofRef, ReadWalkProofRefs,
ReadWalkReplayMode, ReadWalkReportBody, ReadWalkReportError, ReadWalkRequest,
ReadWalkSourceRef, READ_WALK_REPORT_SCHEMA_VERSION,
};
pub use receipt_verification::{ReceiptVerification, ReceiptVerificationError};
pub use receipt_verify::{verify_receipt_claim, ReceiptClaim, ReceiptVerifyingKeys};
pub use signing::SigningKey;
#[cfg(feature = "dangerous-test-hooks")]
pub use sim::SimClock;
pub use snapshot_report::{
snapshot_report_body_hash, SnapshotEvidenceHash, SnapshotEvidenceReport, SnapshotFenceTokenRef,
SnapshotFileKind, SnapshotFinding, SnapshotOptions, SnapshotReportBody, SnapshotWatermarkRef,
SNAPSHOT_EVIDENCE_REPORT_SCHEMA_VERSION,
};
pub use stats::{
ActiveSegmentReadEvidence, ClockEvidence, FrontierView, HlcPoint, HostEvidenceSummary,
LaneFrontierView, LockLeafSymlinkProtection, MmapAdmissionSummary, MmapEvidence,
ParentDirSyncAdmissionSummary, ParentDirSyncEvidence, PlatformAdmissionSummary,
PlatformEvidenceSummary, StoreDiagnostics, StoreLockAdmissionSummary, StorePathEvidenceSummary,
StorePathStatusEvidence, StoreStats, WatermarkKind, WriterPressure,
};
pub use store_resource_report::{
store_data_dir_identity_hash, store_resource_evidence_report_from_diagnostics,
store_resource_report_body_from_diagnostics, store_resource_report_body_hash,
StoreResourceEvidenceReport, StoreResourceFrontierBody, StoreResourceHash,
StoreResourceReportBody, StoreResourceReportError, StoreResourceRestartPolicyShape,
STORE_RESOURCE_REPORT_SCHEMA_VERSION,
};
pub use subscriber_frontier::{
LossPrecision, SubscriberDeliveryState, SubscriberFrontierEvidenceReport,
SubscriberFrontierFinding, SubscriberFrontierHash, SubscriberFrontierReportBody,
SubscriberFrontierReportError, SubscriberFrontierRequest, SubscriberFrontierSource,
SUBSCRIBER_FRONTIER_REPORT_SCHEMA_VERSION,
};
pub use watch_api::ReactLoopHandle;
pub use write::control::{AppendTicket, BatchAppendTicket, Outbox, VisibilityFence};
pub use write::writer::{Notification, RestartPolicy};
use crate::coordinate::{Coordinate, KindFilter, Region};
use crate::event::{
self, EventKind, EventPayload, EventPayloadValidation, EventSourced, StoredEvent,
};
use index::StoreIndex;
use open::timestamp_us_for_hlc;
use parking_lot::Mutex;
use projection::registry::ProjectionRegistry;
use segment::scan::Reader;
use serde::Serialize;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
use write::control::AppendSubmission;
use write::fanout::{ReactorSubscriberList, SubscriberList};
use write::writer::{WatermarkAdvanceHandle, WatermarkState, WriterCommand, WriterHandle};
#[cfg(test)]
const TEST_WRITER_REPLY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
pub(crate) fn recv_writer_reply<T>(
rx: &flume::Receiver<Result<T, StoreError>>,
) -> Result<T, StoreError> {
#[cfg(test)]
let received = rx
.recv_timeout(TEST_WRITER_REPLY_TIMEOUT)
.map_err(|_| StoreError::WriterCrashed)?;
#[cfg(not(test))]
let received = rx.recv().map_err(|_| StoreError::WriterCrashed)?;
received
}
#[cfg(feature = "async-store")]
compile_error!("INVARIANT 2: Store API is sync. Use spawn_blocking or flume recv_async.");
#[doc(hidden)]
pub mod sealed {
pub trait Sealed {}
}
pub trait StoreState: sealed::Sealed {
fn shutdown_writer(&mut self, should_shutdown: bool);
fn writer_queue_len(&self) -> Option<usize>;
}
pub struct Open(pub(crate) WriterHandle);
pub struct Closed;
pub struct ReadOnly;
impl sealed::Sealed for Open {}
impl sealed::Sealed for Closed {}
impl sealed::Sealed for ReadOnly {}
impl StoreState for Open {
fn shutdown_writer(&mut self, should_shutdown: bool) {
if !should_shutdown {
return;
}
let writer = &mut self.0;
tracing::warn!(
"Store dropped without explicit close(); draining writer before releasing store lock"
);
let (tx, rx) = flume::bounded(1);
if writer
.tx
.send(WriterCommand::Shutdown { respond: tx })
.is_ok()
{
writer.pump();
wait_for_drop_shutdown_ack(&rx);
}
join_drop_shutdown_writer(writer);
}
fn writer_queue_len(&self) -> Option<usize> {
Some(self.0.tx.len())
}
}
impl StoreState for Closed {
fn shutdown_writer(&mut self, _should_shutdown: bool) {}
fn writer_queue_len(&self) -> Option<usize> {
None
}
}
impl StoreState for ReadOnly {
fn shutdown_writer(&mut self, _should_shutdown: bool) {}
fn writer_queue_len(&self) -> Option<usize> {
None
}
}
pub struct Store<State: StoreState = Open> {
pub(crate) index: Arc<StoreIndex>,
pub(crate) reader: Arc<Reader>,
pub(crate) cache: Box<dyn ProjectionCache>,
pub(crate) watermark_handle: WatermarkAdvanceHandle,
pub(crate) projection_registry: ProjectionRegistry,
pub(crate) lifecycle_gate: Mutex<()>,
pub(crate) config: Arc<StoreConfig>,
pub(crate) runtime: Arc<config::ValidatedStoreConfig>,
pub(crate) should_shutdown_on_drop: bool,
pub(crate) open_report: Option<cold_start::rebuild::OpenIndexReport>,
pub(crate) cumulative_reserved_kind_fallbacks: segment::sidx::ReservedKindFallbackStats,
#[cfg(feature = "payload-encryption")]
pub(crate) key_store: Option<Arc<Mutex<keyscope::KeyStore>>>,
pub(crate) state: State,
pub(crate) _store_lock: dir_lock::StoreDirLock,
}
impl<State: StoreState> Drop for Store<State> {
fn drop(&mut self) {
self.state.shutdown_writer(self.should_shutdown_on_drop);
}
}
fn wait_for_drop_shutdown_ack(rx: &flume::Receiver<Result<(), StoreError>>) {
let _ack = rx.recv();
}
fn join_drop_shutdown_writer(writer: &mut WriterHandle) {
let _join_result = writer.join();
}
#[cfg(feature = "dangerous-test-hooks")]
impl Store<Open> {
pub(crate) fn abandon_without_shutdown(mut self) {
self.should_shutdown_on_drop = false;
self.state.0.close_channel_and_join();
}
}
#[cfg(all(test, feature = "dangerous-test-hooks"))]
mod writer_queue_len_tests {
use crate::coordinate::Coordinate;
use crate::event::EventKind;
use crate::store::{Store, StoreConfig};
#[test]
fn open_diagnostics_reports_the_live_writer_backlog_and_capacity() {
let dir = tempfile::tempdir().expect("tempdir");
let config = StoreConfig::new(dir.path()).with_writer_channel_capacity(32);
let store = Store::open_cooperative(config).expect("open cooperative store");
let coord = Coordinate::new("entity:queue-len", "scope:mutation").expect("coord");
let mut tickets = Vec::new();
for n in 0..4u32 {
tickets.push(
store
.submit(&coord, EventKind::DATA, &serde_json::json!({ "n": n }))
.expect("submit"),
);
}
let pressure = store.diagnostics().writer_pressure;
let mut failures: Vec<String> = Vec::new();
if pressure.queue_len < 2 {
failures.push(format!(
"Open writer_queue_len must reflect the real backlog (>=2 after 4 un-awaited \
submits), got {}",
pressure.queue_len
));
}
if pressure.capacity != 32 {
failures.push(format!(
"Open writer pressure capacity must be the configured 32 (None mutant would \
report 0), got {}",
pressure.capacity
));
}
assert!(
failures.is_empty(),
"writer pressure mismatches: {failures:?}"
);
drop(tickets);
store.abandon_without_shutdown();
}
}
#[cfg(test)]
mod open_writer_queue_len_direct_tests {
use super::{Open, StoreError, StoreState, SubscriberList, WriterCommand, WriterHandle};
use std::sync::Arc;
#[test]
fn open_writer_queue_len_reports_the_undrained_command_backlog() {
let (tx, _command_rx) = flume::bounded::<WriterCommand>(16);
let open = Open(WriterHandle::from_parts_for_test(
tx,
Arc::new(SubscriberList::new()),
));
let (ack_tx, _ack_rx) = flume::bounded::<Result<(), StoreError>>(1);
let mut setup_failures: Vec<String> = Vec::new();
for n in 0..3u32 {
if open
.0
.tx
.send(WriterCommand::Shutdown {
respond: ack_tx.clone(),
})
.is_err()
{
setup_failures.push(format!("could not enqueue writer command {n}"));
}
}
let observed = open.writer_queue_len();
assert!(
setup_failures.is_empty(),
"writer-command enqueue setup failed: {setup_failures:?}"
);
assert_eq!(
observed,
Some(3),
"Open::writer_queue_len must report the live writer command-channel \
backlog (3 un-drained commands); the Some(0) mutant fabricates an \
empty queue and the None mutant claims there is no writer at all"
);
}
}