1use std::sync::Arc;
24use std::time::{Duration, Instant};
25
26use dev_report::{CheckResult, Evidence, Severity};
27use tokio::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
28
29pub async fn try_mutex_lock_with_timeout<'a, T>(
59 name: impl Into<String>,
60 lock: &'a Arc<Mutex<T>>,
61 timeout: Duration,
62) -> Result<(CheckResult, MutexGuard<'a, T>), CheckResult> {
63 let name = name.into();
64 let started = Instant::now();
65 match tokio::time::timeout(timeout, lock.lock()).await {
66 Ok(guard) => {
67 let elapsed = started.elapsed();
68 let mut c = CheckResult::pass(format!("async::lock::{name}"))
69 .with_duration_ms(elapsed.as_millis() as u64);
70 c.tags = vec!["async".to_string(), "lock".to_string()];
71 c.evidence = vec![
72 Evidence::numeric("elapsed_ms", elapsed.as_millis() as f64),
73 Evidence::numeric("timeout_ms", timeout.as_millis() as f64),
74 ];
75 Ok((c, guard))
76 }
77 Err(_) => {
78 let mut c = CheckResult::fail(format!("async::lock::{name}"), Severity::Error)
79 .with_detail(format!("could not acquire lock within {timeout:?}"));
80 c.tags = vec![
81 "async".to_string(),
82 "lock".to_string(),
83 "deadlock_suspected".to_string(),
84 "regression".to_string(),
85 ];
86 c.evidence = vec![Evidence::numeric("timeout_ms", timeout.as_millis() as f64)];
87 Err(c)
88 }
89 }
90}
91
92pub async fn try_rwlock_read_with_timeout<'a, T>(
94 name: impl Into<String>,
95 lock: &'a Arc<RwLock<T>>,
96 timeout: Duration,
97) -> Result<(CheckResult, RwLockReadGuard<'a, T>), CheckResult> {
98 let name = name.into();
99 let started = Instant::now();
100 match tokio::time::timeout(timeout, lock.read()).await {
101 Ok(guard) => {
102 let elapsed = started.elapsed();
103 let mut c = CheckResult::pass(format!("async::lock::{name}::read"))
104 .with_duration_ms(elapsed.as_millis() as u64);
105 c.tags = vec!["async".to_string(), "lock".to_string()];
106 c.evidence = vec![
107 Evidence::numeric("elapsed_ms", elapsed.as_millis() as f64),
108 Evidence::numeric("timeout_ms", timeout.as_millis() as f64),
109 ];
110 Ok((c, guard))
111 }
112 Err(_) => {
113 let mut c = CheckResult::fail(format!("async::lock::{name}::read"), Severity::Error)
114 .with_detail(format!("could not acquire read lock within {timeout:?}"));
115 c.tags = vec![
116 "async".to_string(),
117 "lock".to_string(),
118 "deadlock_suspected".to_string(),
119 "regression".to_string(),
120 ];
121 c.evidence = vec![Evidence::numeric("timeout_ms", timeout.as_millis() as f64)];
122 Err(c)
123 }
124 }
125}
126
127pub async fn try_rwlock_write_with_timeout<'a, T>(
129 name: impl Into<String>,
130 lock: &'a Arc<RwLock<T>>,
131 timeout: Duration,
132) -> Result<(CheckResult, RwLockWriteGuard<'a, T>), CheckResult> {
133 let name = name.into();
134 let started = Instant::now();
135 match tokio::time::timeout(timeout, lock.write()).await {
136 Ok(guard) => {
137 let elapsed = started.elapsed();
138 let mut c = CheckResult::pass(format!("async::lock::{name}::write"))
139 .with_duration_ms(elapsed.as_millis() as u64);
140 c.tags = vec!["async".to_string(), "lock".to_string()];
141 c.evidence = vec![
142 Evidence::numeric("elapsed_ms", elapsed.as_millis() as f64),
143 Evidence::numeric("timeout_ms", timeout.as_millis() as f64),
144 ];
145 Ok((c, guard))
146 }
147 Err(_) => {
148 let mut c = CheckResult::fail(format!("async::lock::{name}::write"), Severity::Error)
149 .with_detail(format!("could not acquire write lock within {timeout:?}"));
150 c.tags = vec![
151 "async".to_string(),
152 "lock".to_string(),
153 "deadlock_suspected".to_string(),
154 "regression".to_string(),
155 ];
156 c.evidence = vec![Evidence::numeric("timeout_ms", timeout.as_millis() as f64)];
157 Err(c)
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use dev_report::Verdict;
166
167 #[tokio::test]
168 async fn mutex_lock_acquires_under_timeout() {
169 let m = Arc::new(Mutex::new(0));
170 let (check, _g) = try_mutex_lock_with_timeout("a", &m, Duration::from_millis(50))
171 .await
172 .unwrap();
173 assert_eq!(check.verdict, Verdict::Pass);
174 assert!(check.has_tag("lock"));
175 }
176
177 #[tokio::test]
178 async fn mutex_lock_times_out_when_held() {
179 let m = Arc::new(Mutex::new(0));
180 let _held = m.lock().await;
181 let err = try_mutex_lock_with_timeout("a", &m, Duration::from_millis(20))
182 .await
183 .unwrap_err();
184 assert_eq!(err.verdict, Verdict::Fail);
185 assert!(err.has_tag("deadlock_suspected"));
186 assert!(err.has_tag("regression"));
187 }
188
189 #[tokio::test]
190 async fn rwlock_read_under_timeout() {
191 let l = Arc::new(RwLock::new(0));
192 let (check, _g) = try_rwlock_read_with_timeout("a", &l, Duration::from_millis(50))
193 .await
194 .unwrap();
195 assert_eq!(check.verdict, Verdict::Pass);
196 }
197
198 #[tokio::test]
199 async fn rwlock_write_times_out_when_held() {
200 let l = Arc::new(RwLock::new(0));
201 let _held = l.write().await;
202 let err = try_rwlock_write_with_timeout("a", &l, Duration::from_millis(20))
203 .await
204 .unwrap_err();
205 assert_eq!(err.verdict, Verdict::Fail);
206 }
207}