starnix_core/task/
thread_lockup_detector.rs1use pin_project::pin_project;
12use starnix_sync::{LockDepRwLock, ThreadLockupDetectorRegistryLock};
13use std::borrow::Borrow;
14use std::cell::RefCell;
15use std::collections::HashSet;
16use std::sync::LazyLock;
17use std::sync::atomic::{AtomicU8, AtomicU64, AtomicUsize, Ordering};
18
19#[derive(Default)]
20pub struct ThreadLockupDetector;
21
22struct ThreadState {
25 atomic: Box<AtomicU64>,
28 koid: zx::Koid,
30}
31
32impl ThreadState {
33 fn new() -> Self {
35 let handle = fuchsia_runtime::with_thread_self(|thread| thread.raw_handle());
36 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
37 let atomic = Box::new(AtomicU64::new(0));
38 let ptr = &*atomic as *const AtomicU64;
39
40 let mut rcu_nesting_level = std::ptr::null();
41 let mut rcu_counter_index = std::ptr::null();
42 fuchsia_rcu::with_thread_block_counters(|nesting_ptr, counter_ptr| {
43 rcu_nesting_level = nesting_ptr;
44 rcu_counter_index = counter_ptr;
45 });
46
47 let registered = RegisteredThread {
48 thread: unsafe { zx::Unowned::from_raw_handle(handle) },
50 koid,
51 atomic: ptr,
52 rcu_nesting_level,
53 rcu_counter_index,
54 };
55 REGISTRY.write().insert(registered);
56 Self { atomic, koid }
57 }
58}
59
60impl Drop for ThreadState {
61 fn drop(&mut self) {
63 REGISTRY.write().remove(&self.koid);
64 }
65}
66
67thread_local! {
68 static THREAD_STATE: RefCell<Option<ThreadState>> = const { RefCell::new(None) };
69}
70
71#[derive(Clone)]
73struct RegisteredThread {
74 thread: zx::Unowned<'static, zx::Thread>,
76 koid: zx::Koid,
78 atomic: *const AtomicU64,
80 rcu_nesting_level: *const AtomicUsize,
82 rcu_counter_index: *const AtomicU8,
84}
85
86impl std::hash::Hash for RegisteredThread {
89 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
90 self.koid.hash(state);
91 }
92}
93
94impl PartialEq for RegisteredThread {
95 fn eq(&self, other: &Self) -> bool {
96 self.koid == other.koid
97 }
98}
99
100impl Eq for RegisteredThread {}
101
102impl Borrow<zx::Koid> for RegisteredThread {
103 fn borrow(&self) -> &zx::Koid {
104 &self.koid
105 }
106}
107
108unsafe impl Send for RegisteredThread {}
111unsafe impl Sync for RegisteredThread {}
113
114#[derive(Clone)]
115pub struct ThreadLockupInfo {
116 pub thread: zx::Unowned<'static, zx::Thread>,
117 pub koid: zx::Koid,
118}
119
120static REGISTRY: LazyLock<
122 LockDepRwLock<HashSet<RegisteredThread>, ThreadLockupDetectorRegistryLock>,
123> = LazyLock::new(|| Default::default());
124
125impl ThreadLockupDetector {
126 fn start_operation() {
128 THREAD_STATE.with(|state| {
129 let mut state = state.borrow_mut();
130 let state = state.get_or_insert_with(|| ThreadState::new());
131 state.atomic.store(zx::MonotonicInstant::get().into_nanos() as u64, Ordering::Relaxed);
132 });
133 }
134
135 fn stop_operation() {
137 THREAD_STATE.with(|state| {
138 if let Some(state) = state.borrow().as_ref() {
139 state.atomic.store(0, Ordering::Relaxed);
140 }
141 });
142 }
143
144 pub fn get_long_running_threads(threshold: zx::MonotonicDuration) -> Vec<ThreadLockupInfo> {
147 let now = zx::MonotonicInstant::get();
148 let registry = REGISTRY.read();
149 registry
150 .iter()
151 .filter_map(|registered| {
152 let atomic = unsafe { &*registered.atomic };
156 let start_nanos = atomic.load(Ordering::Relaxed);
157 if start_nanos == 0 {
158 return None;
159 }
160 let start_time = zx::MonotonicInstant::from_nanos(start_nanos as i64);
161 if now - start_time > threshold {
162 Some(ThreadLockupInfo {
163 thread: registered.thread.clone(),
164 koid: registered.koid,
165 })
166 } else {
167 None
168 }
169 })
170 .collect()
171 }
172
173 pub fn track() -> LockupDetectorGuard {
176 LockupDetectorGuard::new()
177 }
178
179 pub fn pause_tracking() -> LockupDetectorWaitingGuard {
182 LockupDetectorWaitingGuard::new()
183 }
184
185 pub fn track_future<F>(inner: F) -> LockupDetectorFuture<F> {
187 LockupDetectorFuture::new(inner)
188 }
189
190 pub fn tracked_channel<T>() -> (std::sync::mpsc::Sender<T>, LockupDetectorReceiver<T>) {
192 let (sender, receiver) = std::sync::mpsc::channel();
193 (sender, LockupDetectorReceiver::new(receiver))
194 }
195
196 pub fn active_rcu_read_locks<F>(mut check: F)
197 where
198 F: FnMut(&zx::Thread, zx::Koid, u8),
199 {
200 let registry = REGISTRY.read();
201 for registered in registry.iter() {
202 if registered.rcu_nesting_level.is_null() || registered.rcu_counter_index.is_null() {
203 continue;
204 }
205 let (nesting_level, counter_index) = unsafe {
211 (
212 (*registered.rcu_nesting_level).load(Ordering::Relaxed),
213 (*registered.rcu_counter_index).load(Ordering::Relaxed),
214 )
215 };
216 if nesting_level > 0 {
217 check(®istered.thread, registered.koid, counter_index);
218 }
219 }
220 }
221}
222
223pub struct LockupDetectorGuard;
224
225impl LockupDetectorGuard {
226 fn new() -> Self {
227 ThreadLockupDetector::start_operation();
228 Self
229 }
230}
231
232impl Drop for LockupDetectorGuard {
233 fn drop(&mut self) {
234 ThreadLockupDetector::stop_operation();
235 }
236}
237
238pub struct LockupDetectorWaitingGuard;
239
240impl LockupDetectorWaitingGuard {
241 fn new() -> Self {
242 ThreadLockupDetector::stop_operation();
243 Self
244 }
245}
246
247impl Drop for LockupDetectorWaitingGuard {
248 fn drop(&mut self) {
249 ThreadLockupDetector::start_operation();
250 }
251}
252
253#[pin_project]
254pub struct LockupDetectorFuture<F> {
255 #[pin]
256 inner: F,
257}
258
259impl<F> LockupDetectorFuture<F> {
260 fn new(inner: F) -> Self {
261 Self { inner }
262 }
263}
264
265impl<F: std::future::Future> std::future::Future for LockupDetectorFuture<F> {
266 type Output = F::Output;
267
268 fn poll(
269 self: std::pin::Pin<&mut Self>,
270 cx: &mut std::task::Context<'_>,
271 ) -> std::task::Poll<Self::Output> {
272 let _guard = LockupDetectorGuard::new();
273 let this = self.project();
274 this.inner.poll(cx)
275 }
276}
277
278pub struct LockupDetectorReceiver<T> {
279 inner: std::sync::mpsc::Receiver<T>,
280}
281
282impl<T> LockupDetectorReceiver<T> {
283 fn new(inner: std::sync::mpsc::Receiver<T>) -> Self {
284 Self { inner }
285 }
286
287 pub fn recv(&self) -> Result<T, std::sync::mpsc::RecvError> {
288 let _guard = LockupDetectorWaitingGuard::new();
289 self.inner.recv()
290 }
291
292 pub fn try_iter(&self) -> std::sync::mpsc::TryIter<'_, T> {
293 self.inner.try_iter()
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 fn get_long_running_koids() -> Vec<zx::Koid> {
302 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
303 .iter()
304 .map(|r| r.koid)
305 .collect()
306 }
307
308 #[test]
309 fn test_lockup_detector() {
310 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
311
312 {
313 let _guard = ThreadLockupDetector::track();
314
315 assert!(get_long_running_koids().contains(&koid));
317
318 assert!(get_long_running_koids().contains(&koid));
320 }
321
322 assert!(get_long_running_koids().is_empty());
324 }
325
326 #[test]
327 fn test_guard() {
328 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
329
330 {
331 let _guard = ThreadLockupDetector::track();
332 assert!(get_long_running_koids().contains(&koid));
333 }
334
335 assert!(get_long_running_koids().is_empty());
337 }
338
339 #[test]
340 fn test_waiting_guard() {
341 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
342
343 let _guard = ThreadLockupDetector::track();
344
345 {
346 let _waiting_guard = ThreadLockupDetector::pause_tracking();
347 assert!(get_long_running_koids().is_empty());
349 }
350
351 assert!(get_long_running_koids().contains(&koid));
353 }
354
355 #[test]
356 fn test_track_future() {
357 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
358 let (signal_tx, signal_rx) = futures::channel::oneshot::channel::<()>();
359
360 let t = std::thread::spawn(move || {
361 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
362 koid_tx.send(koid).unwrap();
363
364 let fut = ThreadLockupDetector::track_future(async move {
365 signal_rx.await.unwrap();
366 });
367
368 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
369
370 koid
371 });
372
373 let spawned_koid = koid_rx.recv().unwrap();
374
375 std::thread::sleep(std::time::Duration::from_millis(100));
377
378 assert!(!get_long_running_koids().contains(&spawned_koid));
380
381 signal_tx.send(()).unwrap();
383
384 t.join().unwrap();
385 }
386
387 #[test]
388 fn test_track_future_polling() {
389 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
390
391 assert!(!get_long_running_koids().contains(&koid));
393
394 let fut = ThreadLockupDetector::track_future(async {
395 assert!(get_long_running_koids().contains(&koid));
396 });
397
398 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
399
400 assert!(!get_long_running_koids().contains(&koid));
402 }
403
404 #[test]
405 fn test_track_channel() {
406 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
407 let (tx, rx) = ThreadLockupDetector::tracked_channel();
408
409 let t = std::thread::spawn(move || {
410 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
411 koid_tx.send(koid).unwrap();
412
413 let _guard = ThreadLockupDetector::track();
414
415 rx.recv().unwrap();
417
418 koid
419 });
420
421 let spawned_koid = koid_rx.recv().unwrap();
422
423 std::thread::sleep(std::time::Duration::from_millis(100));
425
426 assert!(!get_long_running_koids().contains(&spawned_koid));
428
429 tx.send(()).unwrap();
431
432 t.join().unwrap();
433 }
434}