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