Skip to main content

SharedDb

Struct SharedDb 

Source
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§

Source§

impl SharedDb

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 Batch frame.
  • 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 the Relaxed window 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).

Source

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.

Source

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: NoneGraphError::RoleWriteDenied (endpoint not permitted) — maps to HTTP 403.
  • Scope / visibility violations inside the batch → GraphError::RoleWriteDenied with 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§

Source§

impl Clone for SharedDb

Source§

fn clone(&self) -> SharedDb

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.