tuwunel_core/utils/
mutex_map.rs1use std::{
8 fmt::Debug,
9 hash::Hash,
10 sync::{Arc, TryLockError::WouldBlock},
11};
12
13use tokio::sync::OwnedMutexGuard as Omg;
14
15use crate::{Result, err};
16
17#[derive(Debug)]
23pub struct MutexMap<Key, Val> {
24 map: Map<Key, Val>,
25}
26
27#[derive(Debug)]
33#[clippy::has_significant_drop]
34pub struct Guard<Key, Val> {
35 map: Map<Key, Val>,
36 entry: Option<Value<Val>>,
37 val: Option<Omg<Val>>,
38}
39
40type Map<Key, Val> = Arc<MapMutex<Key, Val>>;
41type MapMutex<Key, Val> = std::sync::Mutex<HashMap<Key, Val>>;
42type HashMap<Key, Val> = std::collections::HashMap<Key, Value<Val>>;
43type Value<Val> = Arc<tokio::sync::Mutex<Val>>;
44
45impl<Key, Val> MutexMap<Key, Val>
46where
47 Key: Clone + Eq + Hash + Send,
48 Val: Default + Send,
49{
50 #[must_use]
55 pub fn new() -> Self {
56 Self {
57 map: Map::new(MapMutex::new(HashMap::new())),
58 }
59 }
60
61 #[tracing::instrument(level = "trace", skip(self))]
67 pub async fn lock<K>(&self, k: &K) -> Guard<Key, Val>
68 where
69 K: Debug + Send + ?Sized + Sync + ToOwned<Owned = Key>,
70 {
71 self.entry(k).lock().await
72 }
73
74 #[tracing::instrument(level = "trace", skip(self))]
80 pub fn try_lock<K>(&self, k: &K) -> Result<Guard<Key, Val>>
81 where
82 K: Debug + Send + ?Sized + Sync + ToOwned<Owned = Key>,
83 {
84 self.entry(k).try_lock()
85 }
86
87 #[tracing::instrument(level = "trace", skip(self))]
95 pub fn try_try_lock<K>(&self, k: &K) -> Result<Guard<Key, Val>>
96 where
97 K: Debug + Send + ?Sized + Sync + ToOwned<Owned = Key>,
98 {
99 self.try_entry(k)?.try_lock()
100 }
101
102 #[must_use]
107 pub fn contains(&self, k: &Key) -> bool { self.map.lock().expect("locked").contains_key(k) }
108
109 #[must_use]
114 pub fn is_empty(&self) -> bool { self.map.lock().expect("locked").is_empty() }
115
116 #[must_use]
121 pub fn len(&self) -> usize { self.map.lock().expect("locked").len() }
122
123 fn entry<K>(&self, k: &K) -> Guard<Key, Val>
124 where
125 K: ?Sized + ToOwned<Owned = Key>,
126 {
127 let val = self
128 .map
129 .lock()
130 .expect("locked")
131 .entry(k.to_owned())
132 .or_default()
133 .clone();
134
135 self.pending(val)
136 }
137
138 fn try_entry<K>(&self, k: &K) -> Result<Guard<Key, Val>>
139 where
140 K: ?Sized + ToOwned<Owned = Key>,
141 {
142 let val = self
143 .map
144 .try_lock()
145 .map_err(|e| match e {
146 | WouldBlock => err!("would block"),
147 | _ => panic!("{e:?}"),
148 })?
149 .entry(k.to_owned())
150 .or_default()
151 .clone();
152
153 Ok(self.pending(val))
154 }
155
156 fn pending(&self, val: Value<Val>) -> Guard<Key, Val> {
157 Guard {
158 map: Arc::clone(&self.map),
159 entry: Some(val),
160 val: None,
161 }
162 }
163}
164
165impl<Key, Val> Default for MutexMap<Key, Val>
166where
167 Key: Clone + Eq + Hash + Send,
168 Val: Default + Send,
169{
170 fn default() -> Self { Self::new() }
171}
172
173impl<Key, Val> Guard<Key, Val> {
174 async fn lock(mut self) -> Self {
175 let val = self.claim();
178
179 self.val = Some(val.lock_owned().await);
180 self
181 }
182
183 fn try_lock(mut self) -> Result<Self> {
184 self.val = self
185 .claim()
186 .try_lock_owned()
187 .map_err(|_| err!("would yield"))
188 .map(Some)?;
189
190 Ok(self)
191 }
192
193 fn claim(&self) -> Value<Val> { Arc::clone(self.entry.as_ref().expect("claimed")) }
194}
195
196impl<Key, Val> Drop for Guard<Key, Val> {
197 #[tracing::instrument(name = "unlock", level = "trace", skip_all)]
198 fn drop(&mut self) {
199 self.val.take();
200
201 let mut map = self.map.lock().expect("locked");
203
204 if self
205 .entry
206 .take()
207 .is_some_and(|val| Arc::strong_count(&val) <= 2)
208 {
209 map.retain(|_, val| Arc::strong_count(val) > 1);
210 }
211 }
212}