core_api/shared.rs
1//! Concurrent access to a [`GraphDb`] via a process-wide reader-writer lock.
2//!
3//! This is v1 of the spec's single-writer / epoch-reader model: many concurrent
4//! readers **or** one writer. The same `read` / `write` API is the upgrade
5//! path — lock-free epoch snapshot readers (Plan 8) replace the `RwLock`
6//! without changing callers.
7
8use crate::GraphDb;
9use core_storage::RealFs;
10use core_storage::Result;
11use std::ops::{Deref, DerefMut};
12use std::path::Path;
13use std::sync::{Arc, RwLock};
14
15/// Shared handle to an on-disk [`GraphDb`]. [`Clone`] is cheap and shares state.
16///
17/// # Event-sink deadlock
18///
19/// [`GraphDb::set_event_sink`] runs the hook inside `log_then_apply` while
20/// this write guard is still held. A sink must never call [`SharedDb::read`]
21/// or [`SharedDb::write`] on the same handle (the `RwLock` is not
22/// re-entrant). The sink is `Send + Sync`; `std::sync::mpsc::Sender`
23/// is not `Sync`. Intended examples: `std::sync::mpsc::SyncSender`,
24/// `tokio::sync::mpsc::Sender`, `tokio::sync::broadcast::Sender`, or
25/// `Arc<Mutex<Vec<_>>>`.
26#[derive(Clone)]
27pub struct SharedDb {
28 inner: Arc<RwLock<GraphDb<RealFs>>>,
29}
30
31const _: () = {
32 fn assert_send_sync<T: Send + Sync>() {}
33 let _ = assert_send_sync::<SharedDb>;
34};
35
36impl SharedDb {
37 pub fn open(dir: &Path) -> Result<Self> {
38 Ok(Self {
39 inner: Arc::new(RwLock::new(GraphDb::open(dir)?)),
40 })
41 }
42
43 /// Shared read access. Many readers may hold this concurrently.
44 ///
45 /// # Deadlock warning
46 ///
47 /// Do not hold a returned guard while calling any method on the same
48 /// [`SharedDb`]; the [`RwLock`] is not re-entrant; doing so deadlocks.
49 pub fn read(&self) -> impl Deref<Target = GraphDb<RealFs>> + '_ {
50 // Recover a poisoned lock. A panicked reader cannot corrupt state;
51 // this just unblocks the process instead of propagating the poison
52 // panic. WAL replay on reopen is the real recovery.
53 self.inner.read().unwrap_or_else(|e| e.into_inner())
54 }
55
56 /// Exclusive write access. Blocks until no other readers or writers hold
57 /// the lock.
58 ///
59 /// # Deadlock warning
60 ///
61 /// Do not hold a returned guard while calling any method on the same
62 /// [`SharedDb`]; the [`RwLock`] is not re-entrant; doing so deadlocks.
63 pub fn write(&self) -> impl DerefMut<Target = GraphDb<RealFs>> + '_ {
64 // Recover a poisoned lock. A panicked writer mid-apply can leave
65 // partial in-memory state; pre-alpha accepts that. WAL replay on
66 // reopen is the real recovery — this just unblocks the process
67 // instead of propagating the poison panic.
68 self.inner.write().unwrap_or_else(|e| e.into_inner())
69 }
70}