1use parking_lot::{Mutex, MutexGuard};
26#[allow(unused_imports)]
27use std::sync::TryLockError;
28use std::time::Duration;
29
30#[allow(dead_code)]
31const PANIC_MESSAGE: &str = "lock timeout";
32
33pub trait SafeLock {
36 const TIMEOUT: Duration = Duration::from_secs(10);
37 type Output<'a>
38 where
39 Self: 'a;
40 fn safe_lock(&self) -> Self::Output<'_>;
43}
44
45impl<T: ?Sized> SafeLock for Mutex<T> {
46 type Output<'a>
47 = MutexGuard<'a, T>
48 where
49 T: 'a;
50 #[cfg(target_arch = "wasm32")]
51 fn safe_lock(&self) -> Self::Output<'_> {
52 self.lock()
53 }
54 #[cfg(not(target_arch = "wasm32"))]
55 fn safe_lock(&self) -> Self::Output<'_> {
56 self.try_lock_for(Self::TIMEOUT).expect(PANIC_MESSAGE)
57 }
58}
59
60impl<T: ?Sized> SafeLock for std::sync::Mutex<T> {
61 type Output<'a>
62 = std::sync::LockResult<std::sync::MutexGuard<'a, T>>
63 where
64 T: 'a;
65
66 #[cfg(target_arch = "wasm32")]
67 fn safe_lock(&self) -> Self::Output<'_> {
68 self.lock()
69 }
70 #[cfg(not(target_arch = "wasm32"))]
71 fn safe_lock(&self) -> Self::Output<'_> {
72 let start = std::time::Instant::now();
73 loop {
74 match self.try_lock() {
75 Ok(guard) => return Ok(guard),
76 Err(TryLockError::WouldBlock) => (),
77 Err(TryLockError::Poisoned(err)) => return Err(err),
78 }
79 std::thread::yield_now();
80 if start.elapsed() > Self::TIMEOUT {
81 std::panic::panic_any(PANIC_MESSAGE);
82 }
83 }
84 }
85}
86
87#[cfg(test)]
88mod test {
89 use super::*;
90 use std::{
91 any::Any,
92 panic::{catch_unwind, AssertUnwindSafe},
93 };
94 fn panic_to_string(message: Box<dyn Any>) -> Option<String> {
95 match message.downcast_ref::<&str>() {
96 Some(&str) => Some(str.into()),
97 None => message.downcast::<String>().ok().map(|s| *s),
98 }
99 }
100 #[test]
101 fn successful_lock_parking_lot() {
102 let mutex = Mutex::new(());
103 drop(mutex.safe_lock());
104 }
105 #[test]
106 fn successful_lock_std() {
107 let mutex = std::sync::Mutex::new(());
108 drop(mutex.safe_lock());
109 }
110 #[test]
111 fn failed_lock_parking_lot() {
112 let mutex = Mutex::new(());
113 let _guard = mutex.safe_lock();
114 let panic_message = catch_unwind(AssertUnwindSafe(|| mutex.safe_lock()))
115 .expect_err("safe_lock did not panic");
116 let Some(message) = panic_to_string(panic_message) else {
117 panic!("safe_lock panicked with wrong type");
118 };
119 assert_eq!(message, PANIC_MESSAGE);
120 }
121 #[test]
122 fn failed_lock_std() {
123 let mutex = std::sync::Mutex::new(());
124 let _guard = mutex.safe_lock();
125 let panic_message =
126 catch_unwind(|| mutex.safe_lock()).expect_err("safe_lock did not panic");
127 let Some(message) = panic_to_string(panic_message) else {
128 panic!("safe_lock panicked with wrong type");
129 };
130 assert_eq!(message, PANIC_MESSAGE);
131 }
132}