distributed/lock/
async_in_memory.rs1use std::collections::{HashMap, VecDeque};
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::{Arc, Mutex};
5use std::task::{Context, Poll, Waker};
6
7use super::{AsyncLock, AsyncLockManager, LockError};
8
9#[derive(Default)]
10struct AsyncLockState {
11 locked: bool,
12 waiters: VecDeque<Waker>,
13}
14
15pub struct InMemoryAsyncLock {
24 state: Mutex<AsyncLockState>,
25}
26
27impl InMemoryAsyncLock {
28 pub fn new() -> Self {
29 InMemoryAsyncLock {
30 state: Mutex::new(AsyncLockState::default()),
31 }
32 }
33
34 fn try_lock_core(&self) -> Result<bool, LockError> {
42 let mut state = self
43 .state
44 .lock()
45 .map_err(|err| LockError::Poisoned(err.to_string()))?;
46 if state.locked {
47 Ok(false)
48 } else {
49 state.locked = true;
50 Ok(true)
51 }
52 }
53
54 pub(crate) fn unlock_core(&self) -> Result<(), LockError> {
66 let woken = {
67 let mut state = self
68 .state
69 .lock()
70 .map_err(|err| LockError::Poisoned(err.to_string()))?;
71 if state.locked {
72 state.locked = false;
73 std::mem::take(&mut state.waiters)
74 } else {
75 VecDeque::new()
76 }
77 };
78 for waker in woken {
80 waker.wake();
81 }
82 Ok(())
83 }
84}
85
86impl Default for InMemoryAsyncLock {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92pub struct InMemoryAsyncLockFuture<'a> {
96 lock: &'a InMemoryAsyncLock,
97}
98
99impl Future for InMemoryAsyncLockFuture<'_> {
100 type Output = Result<(), LockError>;
101
102 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
103 let mut state = match self.lock.state.lock() {
104 Ok(state) => state,
105 Err(err) => return Poll::Ready(Err(LockError::Poisoned(err.to_string()))),
106 };
107 if !state.locked {
108 state.locked = true;
109 Poll::Ready(Ok(()))
110 } else {
111 if !state
115 .waiters
116 .iter()
117 .any(|waker| waker.will_wake(cx.waker()))
118 {
119 state.waiters.push_back(cx.waker().clone());
120 }
121 Poll::Pending
122 }
123 }
124}
125
126impl AsyncLock for InMemoryAsyncLock {
127 fn lock(&self) -> impl Future<Output = Result<(), LockError>> + Send + '_ {
128 InMemoryAsyncLockFuture { lock: self }
129 }
130
131 async fn try_lock(&self) -> Result<bool, LockError> {
136 self.try_lock_core()
137 }
138
139 async fn unlock(&self) -> Result<(), LockError> {
140 self.unlock_core()
141 }
142}
143
144pub struct InMemoryAsyncLockManager {
150 locks: Mutex<HashMap<String, Arc<InMemoryAsyncLock>>>,
151}
152
153impl InMemoryAsyncLockManager {
154 pub fn new() -> Self {
155 InMemoryAsyncLockManager {
156 locks: Mutex::new(HashMap::new()),
157 }
158 }
159}
160
161impl Default for InMemoryAsyncLockManager {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167impl AsyncLockManager for InMemoryAsyncLockManager {
168 type Lock = InMemoryAsyncLock;
169
170 fn get_lock(&self, id: &str) -> Result<Arc<InMemoryAsyncLock>, LockError> {
171 let mut locks = self
172 .locks
173 .lock()
174 .map_err(|_| LockError::Poisoned("async lock manager map poisoned".into()))?;
175 Ok(locks
176 .entry(id.to_string())
177 .or_insert_with(|| Arc::new(InMemoryAsyncLock::new()))
178 .clone())
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use std::sync::atomic::{AtomicUsize, Ordering};
186 use std::sync::mpsc;
187 use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
188 use std::time::Duration;
189
190 fn reentrant_waker(lock: Arc<InMemoryAsyncLock>) -> Waker {
195 unsafe fn clone(data: *const ()) -> RawWaker {
196 let arc = unsafe { Arc::from_raw(data as *const InMemoryAsyncLock) };
197 let cloned = Arc::clone(&arc);
198 std::mem::forget(arc);
199 RawWaker::new(Arc::into_raw(cloned) as *const (), &REENTRANT_VTABLE)
200 }
201 unsafe fn wake(data: *const ()) {
202 let arc = unsafe { Arc::from_raw(data as *const InMemoryAsyncLock) };
203 let _ = arc.try_lock_core(); }
205 unsafe fn wake_by_ref(data: *const ()) {
206 let arc = unsafe { Arc::from_raw(data as *const InMemoryAsyncLock) };
207 let _ = arc.try_lock_core();
208 std::mem::forget(arc);
209 }
210 unsafe fn drop_fn(data: *const ()) {
211 drop(unsafe { Arc::from_raw(data as *const InMemoryAsyncLock) });
212 }
213 static REENTRANT_VTABLE: RawWakerVTable =
214 RawWakerVTable::new(clone, wake, wake_by_ref, drop_fn);
215 let raw = RawWaker::new(Arc::into_raw(lock) as *const (), &REENTRANT_VTABLE);
216 unsafe { Waker::from_raw(raw) }
217 }
218
219 fn panicking_waker() -> Waker {
221 unsafe fn clone(_: *const ()) -> RawWaker {
222 RawWaker::new(std::ptr::null(), &PANIC_VTABLE)
223 }
224 unsafe fn wake(_: *const ()) {
225 panic!("waker panicked in wake()");
226 }
227 unsafe fn wake_by_ref(_: *const ()) {
228 panic!("waker panicked in wake_by_ref()");
229 }
230 unsafe fn drop_fn(_: *const ()) {}
231 static PANIC_VTABLE: RawWakerVTable =
232 RawWakerVTable::new(clone, wake, wake_by_ref, drop_fn);
233 unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &PANIC_VTABLE)) }
234 }
235
236 fn park_waker(lock: &InMemoryAsyncLock, waker: &Waker) {
238 let mut cx = Context::from_waker(waker);
239 let mut fut = std::pin::pin!(lock.lock());
240 assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Pending));
241 }
242
243 #[tokio::test]
244 async fn try_lock_reflects_state() {
245 let lock = InMemoryAsyncLock::new();
246 assert!(lock.try_lock().await.unwrap()); assert!(!lock.try_lock().await.unwrap()); lock.unlock().await.unwrap();
249 assert!(lock.try_lock().await.unwrap()); }
251
252 #[tokio::test]
253 async fn lock_resolves_immediately_when_free() {
254 let lock = InMemoryAsyncLock::new();
255 lock.lock().await.unwrap();
256 assert!(!lock.try_lock().await.unwrap()); lock.unlock().await.unwrap();
258 assert!(lock.try_lock().await.unwrap());
259 }
260
261 #[tokio::test]
262 async fn second_acquire_waits_until_unlock() {
263 let lock = Arc::new(InMemoryAsyncLock::new());
264 lock.lock().await.unwrap();
265
266 let order = Arc::new(AtomicUsize::new(0));
267 let waiter_lock = Arc::clone(&lock);
268 let waiter_order = Arc::clone(&order);
269 let waiter = tokio::spawn(async move {
270 waiter_lock.lock().await.unwrap();
271 waiter_order.fetch_add(1, Ordering::SeqCst)
273 });
274
275 tokio::time::sleep(Duration::from_millis(20)).await;
277 assert_eq!(
278 order.load(Ordering::SeqCst),
279 0,
280 "waiter must still be parked"
281 );
282
283 lock.unlock().await.unwrap();
284 let acquired_at = waiter.await.unwrap();
285 assert_eq!(acquired_at, 0, "waiter acquired exactly once after unlock");
286 assert!(!lock.try_lock().await.unwrap(), "waiter holds the lock");
287 }
288
289 #[test]
290 fn manager_returns_same_arc_per_key() {
291 let manager = InMemoryAsyncLockManager::new();
292 let a1 = manager.get_lock("agg-1").unwrap();
293 let a2 = manager.get_lock("agg-1").unwrap();
294 let b = manager.get_lock("agg-2").unwrap();
295 assert!(Arc::ptr_eq(&a1, &a2));
296 assert!(!Arc::ptr_eq(&a1, &b));
297 }
298
299 #[tokio::test]
300 async fn distinct_keys_do_not_contend() {
301 let manager = InMemoryAsyncLockManager::new();
302 let a = manager.get_lock("agg-1").unwrap();
303 let b = manager.get_lock("agg-2").unwrap();
304 a.lock().await.unwrap();
305 b.lock().await.unwrap();
307 a.unlock().await.unwrap();
308 b.unlock().await.unwrap();
309 }
310
311 #[test]
316 fn unlock_does_not_deadlock_with_reentrant_waker() {
317 let lock = Arc::new(InMemoryAsyncLock::new());
318 assert!(lock.try_lock_core().unwrap()); park_waker(&lock, &reentrant_waker(Arc::clone(&lock)));
320
321 let (tx, rx) = mpsc::channel();
322 let unlock_lock = Arc::clone(&lock);
323 std::thread::spawn(move || {
324 let _ = tx.send(unlock_lock.unlock_core());
325 });
326 let result = rx
327 .recv_timeout(Duration::from_secs(2))
328 .expect("unlock deadlocked while waking a re-entrant waker");
329 result.expect("unlock should succeed");
330 }
331
332 #[test]
336 fn unlock_does_not_poison_when_a_waker_panics() {
337 let lock = Arc::new(InMemoryAsyncLock::new());
338 assert!(lock.try_lock_core().unwrap()); park_waker(&lock, &panicking_waker());
340
341 let unlock_lock = Arc::clone(&lock);
342 let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
343 let _ = unlock_lock.unlock_core();
344 }))
345 .is_err();
346 assert!(panicked, "the panicking waker should unwind out of unlock");
347
348 assert!(
351 lock.try_lock_core().unwrap(),
352 "lock must remain usable after a waker panic"
353 );
354 }
355}