Skip to main content

bark/lock_manager/
memory.rs

1//! In-process named locks with a process-wide shared keyspace.
2//!
3//! All [`MemoryLockManager`] instances within a process share a single
4//! global key map: two `MemoryLockManager::new()` calls produce handles
5//! into the same lock universe. Two instances cannot accidentally end
6//! up with disjoint lock universes the way direct
7//! [`InternalMemoryLockManager`](super::internal_memory::InternalMemoryLockManager)
8//! instances would.
9//!
10//! Compare with
11//! [`InternalMemoryLockManager`](super::internal_memory::InternalMemoryLockManager),
12//! whose keyspace is per-instance and exists for composition by
13//! file-based backends — each backend needs its own private in-process
14//! map so two unrelated lock directories don't falsely contend on the
15//! same key. That type is crate-private; this one is the public
16//! in-memory backend.
17//!
18//! Gives no cross-process, cross-machine, or cross-tab guarantees —
19//! coordination is only within the current OS process.
20//!
21//! # Platform support
22//!
23//! All platforms. Pure Rust over `tokio::sync::Mutex`; no I/O, no
24//! syscalls.
25//!
26//! # When to use
27//!
28//! - You've already enforced that exactly one bark instance opens this
29//!   dataset at a time (single-process service, container exclusivity,
30//!   external pid lock).
31//! - Unit and integration tests.
32
33use std::sync::OnceLock;
34use std::time::Duration;
35
36use super::{LockGuard, LockManager};
37use super::internal_memory::InternalMemoryLockManager;
38
39/// In-process named locks with a process-wide shared keyspace. See the
40/// [module docs](self) for the comparison with
41/// [`InternalMemoryLockManager`].
42pub struct MemoryLockManager;
43
44impl MemoryLockManager {
45	pub fn new() -> Self {
46		// Touch the static so initialization happens at construction
47		// time rather than on first use.
48		let _ = Self::shared();
49		Self
50	}
51
52	fn shared() -> &'static InternalMemoryLockManager {
53		static SHARED: OnceLock<InternalMemoryLockManager> = OnceLock::new();
54		SHARED.get_or_init(InternalMemoryLockManager::new)
55	}
56}
57
58impl Default for MemoryLockManager {
59	fn default() -> Self { Self::new() }
60}
61
62impl std::fmt::Debug for MemoryLockManager {
63	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64		f.debug_struct("MemoryLockManager").finish()
65	}
66}
67
68#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
69#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
70impl LockManager for MemoryLockManager {
71	async fn try_lock(&self, key: &str) -> Option<Box<dyn LockGuard>> {
72		Self::shared().try_lock(key).await
73	}
74
75	async fn lock(&self, key: &str, timeout: Duration) -> anyhow::Result<Box<dyn LockGuard>> {
76		Self::shared().lock(key, timeout).await
77	}
78}
79
80// Uses `tokio::test` (tokio rt feature, desktop-only).
81#[cfg(all(test, not(target_arch = "wasm32")))]
82mod test {
83	use super::*;
84
85	#[tokio::test]
86	async fn two_instances_share_keys() {
87		let a = MemoryLockManager::new();
88		let b = MemoryLockManager::new();
89		let g = a.try_lock("bark.shared.test").await.unwrap();
90		let busy = b.try_lock("bark.shared.test").await;
91		assert!(busy.is_none(), "second instance should observe the lock");
92		drop(g);
93		let g2 = b.try_lock("bark.shared.test").await;
94		assert!(g2.is_some(), "second instance can acquire after release");
95	}
96}