Skip to main content

kithara_storage/backend/
traits.rs

1#![forbid(unsafe_code)]
2
3use std::{ops::Range, path::Path};
4
5use rangemap::RangeSet;
6
7use crate::StorageResult;
8
9mod kithara {
10    pub(crate) use kithara_test_macros::mock;
11}
12
13/// Backend-specific storage operations.
14///
15/// Drivers handle raw byte I/O and storage lifecycle transitions.
16/// The common state machine (range tracking, committed/failed flags,
17/// condvar coordination, cancellation) is managed by
18/// [`Resource<D>`](crate::backend::Resource).
19///
20/// Each driver manages its own interior mutability (e.g. `Mutex<MmapState>`
21/// for mmap, `Mutex<Vec<u8>>` for memory).
22#[kithara::mock(api = DriverIoMock)]
23pub trait DriverIo: Send + Sync + 'static {
24    /// Finalize backing store (e.g. mmap: resize + reopen read-only; memory: truncate).
25    ///
26    /// # Errors
27    ///
28    /// Returns error if the backing store cannot be finalized (e.g. file
29    /// truncation or read-only reopen fails).
30    fn commit(&self, final_len: Option<u64>) -> StorageResult<()>;
31
32    /// Publish a write range to the fast-path mechanism.
33    ///
34    /// Called by [`Resource<D>`](crate::backend::Resource) after a
35    /// successful driver write, before updating state + condvar.
36    fn notify_write(&self, _range: &Range<u64>) {}
37
38    /// Filesystem path, if any.
39    fn path(&self) -> Option<&Path>;
40
41    /// Reopen for writing (e.g. mmap: reopen as read-write; memory: no-op).
42    ///
43    /// # Errors
44    ///
45    /// Returns error if the backing store cannot be reopened for writing.
46    fn reactivate(&self) -> StorageResult<()>;
47
48    /// Read bytes at offset into `buf`.
49    ///
50    /// `effective_len` is the min of `final_len` and physical storage length,
51    /// pre-computed by [`Resource<D>`](crate::backend::Resource). The driver
52    /// reads up to `effective_len`.
53    ///
54    /// # Errors
55    ///
56    /// Returns error if the underlying storage read fails.
57    fn read_at(&self, offset: u64, buf: &mut [u8], effective_len: u64) -> StorageResult<usize>;
58
59    /// Physical storage length (for `read_at` clamping).
60    fn storage_len(&self) -> u64;
61
62    /// Lock-free fast-path check before state mutex.
63    ///
64    /// Some drivers (mmap) maintain a lock-free queue of ready ranges.
65    /// Called before acquiring the state mutex in `wait_range`.
66    /// Return `true` if the range is definitely covered.
67    fn try_fast_check(&self, _range: &Range<u64>) -> bool {
68        false
69    }
70
71    /// Returns the valid data window for ring buffer drivers.
72    ///
73    /// When `Some(window)`, any ranges before `window.start` have been evicted
74    /// and should be removed from the available set. Returns `None` for
75    /// linear drivers where all written data is retained.
76    fn valid_window(&self) -> Option<Range<u64>> {
77        None
78    }
79
80    /// Write bytes at offset.
81    ///
82    /// `committed` comes from common state — the driver decides whether to
83    /// reject (e.g. memory) or reopen as read-write (e.g. mmap `ReadWrite` mode).
84    ///
85    /// # Errors
86    ///
87    /// Returns error if the write fails or the resource is committed
88    /// and the driver does not support post-commit writes.
89    fn write_at(&self, offset: u64, data: &[u8], committed: bool) -> StorageResult<()>;
90}
91
92/// Initial state returned by a driver's inherent `open` to populate
93/// [`Resource<D>`](crate::backend::Resource).
94///
95/// Field order is (visibility, type, name): `Option` first, then the
96/// `RangeSet`, then the boolean — keeps both this struct and its
97/// initializer sites canonical.
98#[derive(Default)]
99pub struct DriverState {
100    /// Known final length (if committed).
101    pub final_len: Option<u64>,
102    /// Pre-populated available byte ranges.
103    pub available: RangeSet<u64>,
104    /// Whether the resource starts as committed.
105    pub is_committed: bool,
106}
107
108/// Driver factory + I/O contract.
109///
110/// `Driver` adds backend creation on top of the runtime [`DriverIo`]
111/// operations. Public on purpose: the generic
112/// `Resource::<D>::open(cancel, opts: D::Options)` constructor uses
113/// `D::Options` as the call-site type-driver disambiguation knob — every
114/// `Resource::open(token, MmapOptions { .. })` callsite resolves to
115/// `Resource<MmapDriver>::open` because `MmapOptions = <MmapDriver as Driver>::Options`.
116///
117/// The `redundant_reexport` audit lint flags the dual surface
118/// (`pub use mmap::MmapOptions` AND `<MmapDriver as Driver>::Options = MmapOptions`)
119/// — the duplication is intentional: `MmapOptions` is the canonical
120/// constructor type users reach via `kithara_storage::MmapOptions`, while
121/// the trait associated type is the bound that lets `Resource::open`
122/// stay generic. See `crates/kithara-storage/README.md` for the rationale.
123pub trait Driver: DriverIo {
124    /// Configuration needed to open/create a driver instance.
125    type Options: Send;
126
127    /// Open or create a new driver from options.
128    ///
129    /// Returns the driver and initial state to populate
130    /// [`Resource<D>`](crate::backend::Resource).
131    ///
132    /// # Errors
133    ///
134    /// Returns error if the backing storage cannot be opened or created.
135    fn open(opts: Self::Options) -> StorageResult<(Self, DriverState)>
136    where
137        Self: Sized;
138}
139
140/// Observer notified when a [`Resource<D>`](crate::backend::Resource) gains
141/// bytes or finalizes.
142///
143/// Fires after the state lock is released so observer implementations
144/// are free to take their own locks without risking deadlocks against
145/// `wait_range` waiters. Storage knows nothing about resource keys —
146/// implementations capture any attribution context themselves (see
147/// `kithara-assets::index::ScopedAvailabilityObserver`).
148pub trait AvailabilityObserver: Send + Sync {
149    /// Record that the resource has been committed with `final_len`
150    /// bytes. Only fires for `commit(Some(final_len))`; `commit(None)`
151    /// is silent.
152    fn on_commit(&self, final_len: u64);
153
154    /// Record that `range` has just become available.
155    fn on_write(&self, range: Range<u64>);
156}
157
158#[cfg(test)]
159mod tests {
160    mod kithara {
161        pub(crate) use kithara_test_macros::test;
162    }
163
164    use super::*;
165
166    #[kithara::test]
167    fn driver_io_mock_api_is_generated() {
168        let _ = DriverIoMock::read_at;
169    }
170}