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}