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}