Skip to main content

veilid_tools/
tag_lock.rs

1use super::*;
2
3use core::fmt::Debug;
4use core::hash::Hash;
5
6/// Holds the lock for one tag in a `TagLockTable`. The tag is released and its
7/// table entry reclaimed when the last clone of the guard drops.
8#[derive(Clone, Debug)]
9pub struct TagLockGuard<T>
10where
11    T: Hash + Eq + Clone + Debug,
12{
13    inner: Arc<TagLockGuardInner<T>>,
14}
15
16impl<T> TagLockGuard<T>
17where
18    T: Hash + Eq + Clone + Debug,
19{
20    /// The tag this guard holds.
21    #[must_use]
22    pub fn tag(&self) -> T {
23        self.inner.tag()
24    }
25}
26
27struct TagLockGuardInner<T>
28where
29    T: Hash + Eq + Clone + Debug,
30{
31    table: TagLockTable<T>,
32    tag: T,
33    guard: Option<ArcMutexGuard<RawMutex, ()>>,
34}
35
36impl<T> fmt::Debug for TagLockGuardInner<T>
37where
38    T: Hash + Eq + Clone + Debug,
39{
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.debug_struct("TagLockGuardInner")
42            .field("tag", &self.tag)
43            .finish()
44    }
45}
46
47impl<T> TagLockGuardInner<T>
48where
49    T: Hash + Eq + Clone + Debug,
50{
51    fn new(table: TagLockTable<T>, tag: T, guard: ArcMutexGuard<RawMutex, ()>) -> Self {
52        Self {
53            table,
54            tag,
55            guard: Some(guard),
56        }
57    }
58
59    fn tag(&self) -> T {
60        self.tag.clone()
61    }
62}
63
64impl<T> Drop for TagLockGuardInner<T>
65where
66    T: Hash + Eq + Clone + Debug,
67{
68    fn drop(&mut self) {
69        let mut inner = self.table.inner.lock();
70        // Inform the table we're dropping this guard
71        let guards = {
72            // Get the table entry, it must exist since we have a guard locked
73            let entry = inner.table.get_mut(&self.tag).unwrap_or_log();
74            // Decrement the number of guards
75            entry.guards -= 1;
76            // Return the number of guards left
77            entry.guards
78        };
79        // If there are no guards left, we remove the tag from the table
80        if guards == 0 {
81            inner.table.remove(&self.tag).unwrap_or_log();
82        }
83        // Proceed with releasing guard, which may cause some concurrent tag lock to acquire
84        drop(self.guard.take());
85    }
86}
87
88#[derive(Clone, Debug)]
89struct TagLockTableEntry {
90    mutex: Arc<Mutex<()>>,
91    guards: usize,
92}
93
94struct TagLockTableInner<T>
95where
96    T: Hash + Eq + Clone + Debug,
97{
98    table: HashMap<T, TagLockTableEntry>,
99}
100
101/// A table of mutexes keyed by tag. Locking a tag blocks only other lockers of
102/// the same tag; distinct tags never contend. Entries are created on first lock
103/// and removed when their last guard drops.
104#[derive(Clone)]
105pub struct TagLockTable<T>
106where
107    T: Hash + Eq + Clone + Debug,
108{
109    inner: Arc<Mutex<TagLockTableInner<T>>>,
110}
111
112impl<T> fmt::Debug for TagLockTable<T>
113where
114    T: Hash + Eq + Clone + Debug,
115{
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("TagLockTable").finish()
118    }
119}
120
121impl<T> TagLockTable<T>
122where
123    T: Hash + Eq + Clone + Debug,
124{
125    /// Create an empty table.
126    #[must_use]
127    pub fn new() -> Self {
128        Self {
129            inner: Arc::new(Mutex::new(TagLockTableInner {
130                table: HashMap::new(),
131            })),
132        }
133    }
134
135    /// Whether no tags are currently locked.
136    #[must_use]
137    pub fn is_empty(&self) -> bool {
138        let inner = self.inner.lock();
139        inner.table.is_empty()
140    }
141
142    /// Number of tags currently locked.
143    #[must_use]
144    pub fn len(&self) -> usize {
145        let inner = self.inner.lock();
146        inner.table.len()
147    }
148
149    /// Lock `tag`, blocking until it is available, and return a guard holding it.
150    /// Blocks on the per-tag mutex; the tag stays locked until the returned guard
151    /// (and all its clones) drop.
152    pub fn lock_tag(&self, tag: T) -> TagLockGuard<T> {
153        // Get or create a tag lock entry
154        let mutex = {
155            let mut inner = self.inner.lock();
156
157            // See if this tag is in the table
158            // and if not, add a new mutex for this tag
159            let entry = inner
160                .table
161                .entry(tag.clone())
162                .or_insert_with(|| TagLockTableEntry {
163                    mutex: Arc::new(Mutex::new(())),
164                    guards: 0,
165                });
166
167            // Increment the number of guards
168            entry.guards += 1;
169
170            // Return the mutex associated with the tag
171            entry.mutex.clone()
172
173            // Drop the table guard
174        };
175
176        // Lock the tag lock
177        let guard = mutex.lock_arc();
178
179        // Return the locked guard
180        TagLockGuard {
181            inner: Arc::new(TagLockGuardInner::new(self.clone(), tag, guard)),
182        }
183    }
184
185    /// Lock `tag` without blocking, returning `None` if it is already held.
186    /// On success the tag stays locked until the returned guard (and all its
187    /// clones) drop.
188    pub fn try_lock_tag(&self, tag: T) -> Option<TagLockGuard<T>> {
189        // Get or create a tag lock entry
190        let mut inner = self.inner.lock();
191
192        // See if this tag is in the table
193        // and if not, add a new mutex for this tag
194        let entry = inner.table.entry(tag.clone());
195
196        // Lock the tag lock
197        let guard = match entry {
198            std::collections::hash_map::Entry::Occupied(mut o) => {
199                let e = o.get_mut();
200                let guard = e.mutex.try_lock_arc()?;
201                e.guards += 1;
202                guard
203            }
204            std::collections::hash_map::Entry::Vacant(v) => {
205                let mutex = Arc::new(Mutex::new(()));
206                let guard = mutex.try_lock_arc().unwrap_or_log();
207                v.insert(TagLockTableEntry { mutex, guards: 1 });
208                guard
209            }
210        };
211        // Return guard
212        Some(TagLockGuard {
213            inner: Arc::new(TagLockGuardInner::new(self.clone(), tag, guard)),
214        })
215    }
216}
217
218impl<T> Default for TagLockTable<T>
219where
220    T: Hash + Eq + Clone + Debug,
221{
222    fn default() -> Self {
223        Self::new()
224    }
225}