pub struct SharedDb { /* private fields */ }Expand description
Shared handle to an on-disk GraphDb. Clone is cheap and shares state.
§Group-commit write path
SharedDb::submit_batch routes mutations through a group-commit queue.
A background drain thread batches concurrent submissions under a single WAL
fsync, yielding throughput that scales with concurrency.
§Direct write path
SharedDb::write gives exclusive &mut GraphDb access for callers that
need complex multi-step mutations (e.g. Cypher write queries). It acquires
the WAL mutex first, then the RwLock write guard, satisfying the lock-order
discipline described in the module doc.
§Event-sink deadlock
GraphDb::set_event_sink runs inside log_then_apply while the write
guard is held. A sink must never call SharedDb::read or
SharedDb::write on the same handle.
Implementations§
Sourcepub fn open(dir: &Path) -> Result<Self>
pub fn open(dir: &Path) -> Result<Self>
Open the store at dir as a shared, multi-reader handle.
Unlike a plain GraphDb, this does not hold the store’s cross-process
write lock for the handle’s lifetime — a server would otherwise lock out
every other process for as long as it runs. The lock is taken per write
instead, and reads follow other processes’ commits automatically.
Sourcepub fn open_with_test_sync(
dir: &Path,
sync: impl Fn(&Path) -> Result<()> + Send + Sync + 'static,
) -> Result<Self>
pub fn open_with_test_sync( dir: &Path, sync: impl Fn(&Path) -> Result<()> + Send + Sync + 'static, ) -> Result<Self>
Open with an injectable WAL sync function.
Allows tests to inject fsync failures through the live drain thread
without requiring real filesystem manipulation. Not intended for
production use; the test_sync name signals its purpose.
Sourcepub fn read(&self) -> impl Deref<Target = GraphDb<RealFs>> + '_
pub fn read(&self) -> impl Deref<Target = GraphDb<RealFs>> + '_
Shared read access. Many readers may hold this concurrently.
Readers never acquire the WAL mutex and never wait on the cross-process write lock, so neither a concurrent fsync nor a peer process writing the store lengthens a read. A reader can still wait on another thread’s RwLock write guard, including the refresh below.
§Following other processes
Every read checks whether another process has committed and, if so,
applies its commits before handing out the guard. A handle therefore
stays current without reopening, and the guarantee is not a timing
accident: a read started after a peer’s commit completed sees that
commit. The check is metadata-only — one or two stat calls, no file
contents — so a read loop pays a syscall, not a reload.
§Deadlock warning
Do not hold a returned guard while calling any method on the same
SharedDb; the RwLock is not re-entrant; doing so deadlocks.
Sourcepub fn write(&self) -> WriteGuard<'_>
pub fn write(&self) -> WriteGuard<'_>
Exclusive write access, waiting up to WRITE_LOCK_WAIT for the
store’s cross-process write lock.
Acquires the WAL mutex first, then the cross-process lock, then the
RwLock write guard — the order required by the fsync-failure contract
(see module doc). Waiting for the cross-process lock happens with no
RwLock write guard held, which is what keeps a busy peer process off
this process’s read path. The returned WriteGuard releases the
cross-process lock, then the RwLock, then the WAL mutex on drop.
§When another process holds the lock
The guard is still returned, but every mutation through it fails with
GraphError::Busy and writes nothing. Call
write_with_wait when you would rather see
that up front, or choose your own wait budget.
§Deadlock warning
Do not hold a returned guard while calling any method on the same
SharedDb; the RwLock is not re-entrant; doing so deadlocks.
Sourcepub fn write_with_wait(&self, wait: Duration) -> Result<WriteGuard<'_>>
pub fn write_with_wait(&self, wait: Duration) -> Result<WriteGuard<'_>>
Like write but with an explicit wait budget, and
GraphError::Busy returned up front when the cross-process write lock
is not free within it.
A zero wait makes exactly one attempt. Nothing is written on failure, so retrying later is always safe.
Sourcepub fn reader(&self) -> ReaderSnapshot
pub fn reader(&self) -> ReaderSnapshot
Capture a lock-free ReaderSnapshot of the current db state.
Acquires the read lock only long enough to clone a handful of Arc
handles. Subsequent reads on the returned snapshot are lock-free.
Sourcepub fn submit_batch(&self, ops: Vec<BatchOp>) -> Result<(usize, usize)>
pub fn submit_batch(&self, ops: Vec<BatchOp>) -> Result<(usize, usize)>
Enqueue a mutation batch for the group-committing writer.
Blocks until the containing group is durably committed (one WAL
fsync per group under Strict policy). Submissions from concurrent
callers are coalesced into groups of up to 256 items.
§Durability semantics
Under Strict policy (the default):
- Each submission becomes a separate WAL
Batchframe. - All frames in a group share one fsync — the caller unblocks only after that fsync.
- Fsync failure: the drain thread truncates the WAL back to the
pre-group offset and marks the database degraded. All submitters in
the failed group and all subsequent callers receive
Err. Data that was already in readers’ snapshots (observed between write-lock release and truncation) is not rolled back — equivalent to theRelaxedwindow for in-flight readers. Reopen the database to recover. - A crash between group fsyncs loses the entire unfsynced group, but never tears an individual submission (CRC-protected frame boundaries).
Under Relaxed policy (set via db.write().set_fsync_policy):
- WAL frames are appended but NOT synced; caller unblocks after apply.
§Event ordering
Under Strict / Batched policy, subscription events fire AFTER the
group fsync (durability before notification). Under Relaxed, events
fire immediately after apply.
§FIFO ordering
Submissions from the same caller arrive FIFO at the queue. Across concurrent callers, drain order within a group is arbitrary, but each submission’s commit sequence is monotonically increasing.
§Returns
(nodes_inserted, edges_inserted) on success. An all-noop batch
returns (0, 0).
Sourcepub fn submit_batch_cas(
&self,
preconds: Vec<Precondition>,
ops: Vec<BatchOp>,
) -> Result<(usize, usize)>
pub fn submit_batch_cas( &self, preconds: Vec<Precondition>, ops: Vec<BatchOp>, ) -> Result<(usize, usize)>
Like [submit_batch] but with compare-and-set preconditions.
The preconditions are evaluated by the drain thread under the same
write guard as the batch apply — there is no TOCTOU window. If any
precondition fails, the entire batch is rejected with
core_storage::GraphError::CasConflict and no WAL frame is written.
See crate::Precondition for the full semantics.
Sourcepub fn submit_batch_authz(
&self,
role: String,
ops: Vec<BatchOp>,
) -> Result<(usize, usize)>
pub fn submit_batch_authz( &self, role: String, ops: Vec<BatchOp>, ) -> Result<(usize, usize)>
Like [submit_batch] but with role-scoped write authorization.
The drain thread resolves mask_for_role + scope under the same write
guard as the mutation (§5 lock discipline: authz BEFORE any CAS
preconditions, BEFORE the WAL write).
- Role with
write: None→GraphError::RoleWriteDenied(endpoint not permitted) — maps to HTTP 403. - Scope / visibility violations inside the batch →
GraphError::RoleWriteDeniedwith the appropriate §4.3 reason string.
All-or-nothing semantics: a single denied op rejects the entire batch with no WAL frame written.
Trait Implementations§
Auto Trait Implementations§
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.