common/singleton.rs
1//! This file implements a Reentrant Synchronization Singleton, which is used to guard
2//! a critical path such that only a single thread may execute it at a time. The
3//! general use is:
4//!
5//! ```rust
6//! // Take control of the Singleton. This is a blocking operation.
7//! let lock = common::singleton::Singleton::new();
8//! // ...
9//! if let Some(lock) = lock {
10//! drop(lock)
11//! }
12//! ```
13//!
14//! The primitive is Reentrant, meaning that once a thread owns the object, subsequent
15//! calls do not cause recursive deadlock. The intializer will simply return None,
16//! and the original MutexGuard acquired by the thread further up the call-stack
17//! will remain. This means that if you have multiple critical paths which may
18//! overlap, you do not need to worry about causing deadlock--the Singleton will
19//! remain owned by the thread for the scope highest in the call-chain:
20//!
21//! ```rust
22//! fn critical_write() {
23//! // Acquire a lock
24//! let _lock = common::singleton::Singleton::new();
25//! println!("Rust already ensures only a single thread can write here, but we're being safe ;)");
26//!
27//! // Because we already have the Singleton in this thread, this instance will be none. The MutexGuard
28//! // is held by the parent.
29//! assert!(_lock.is_none())
30//! }
31//!
32//! // Acquire a lock for our critical section.
33//! let _lock = common::singleton::Singleton::new();
34//! let x = 1;
35//!
36//! // Write. Though we already hold an instance of the Singleton, we can safely call this from this thread.
37//! critical_write();
38//!
39//! // The lock will drop here, allowing the entire critical path to execute without multiple acquisitions.
40//! ```
41
42use parking_lot::{Condvar, Mutex, MutexGuard, ReentrantMutex, ReentrantMutexGuard};
43use std::sync::{Arc, LazyLock};
44
45/// The global semaphore controls which thread is allowed to change users.
46static SEMAPHORE: LazyLock<Semaphore> =
47 LazyLock::new(|| Arc::new((ReentrantMutex::new(()), Mutex::new(false), Condvar::new())));
48
49/// A Semaphore implementation. Includes A ReentrantMutex to check if the current thread owns
50/// the Singleton, a regular Mutex that holds a boolean we can modify to save whether the current
51/// mutex is held, and a condition variable to alert waiting threads when the Singleton is available.
52type Semaphore = Arc<(ReentrantMutex<()>, Mutex<bool>, Condvar)>;
53
54/// More concise Mutex Guard types.
55type Guard = MutexGuard<'static, bool>;
56type ThreadGuard = ReentrantMutexGuard<'static, ()>;
57
58/// The Singleton is a Reentrant Synchronization Type that can only be held by a single thread.
59pub struct Singleton {
60 sem: Semaphore,
61 guard: Guard,
62 _thread_guard: ThreadGuard,
63}
64impl Singleton {
65 /// Take ownership of the Singleton, blocking until it becomes available.
66 /// If the current thread already owns the Singleton, this function will
67 /// return None. Otherwise, it will return an instance that, when dropped,
68 /// will free the Singleton for another thread.
69 pub fn new() -> Option<Self> {
70 // Get the semaphore.
71 let sem = Arc::clone(&SEMAPHORE);
72 let (thread_lock, mutex, cvar) = &*sem;
73
74 // If we already own it, just return
75 if thread_lock.is_owned_by_current_thread() {
76 return None;
77 }
78
79 // Otherwise, get a guard
80 let mut guard: Guard = unsafe {
81 let tmp_guard = mutex.lock();
82 std::mem::transmute::<MutexGuard<'_, bool>, Guard>(tmp_guard)
83 };
84 while *guard {
85 cvar.wait(&mut guard);
86 }
87
88 // Get the thread guard as well.
89 let _thread_guard: ThreadGuard = unsafe {
90 let tmp_guard = thread_lock.lock();
91 std::mem::transmute::<ReentrantMutexGuard<'_, ()>, ThreadGuard>(tmp_guard)
92 };
93
94 // Notify that the Singleton is owned.
95 *guard = true;
96 Some(Self {
97 sem,
98 guard,
99 _thread_guard,
100 })
101 }
102}
103impl Drop for Singleton {
104 fn drop(&mut self) {
105 *self.guard = false;
106 let (_, _, cvar) = &*self.sem;
107 cvar.notify_one();
108 }
109}