starnix_core/task/
thread_lockup_detector.rs1use pin_project::pin_project;
12use starnix_sync::{LockDepRwLock, ThreadLockupDetectorRegistryLock};
13use std::borrow::Borrow;
14use std::cell::{Cell, RefCell};
15use std::collections::{HashMap, HashSet};
16use std::sync::LazyLock;
17use std::sync::atomic::{AtomicU8, AtomicU64, AtomicUsize, Ordering};
18
19#[derive(Default)]
20pub struct ThreadLockupDetector;
21
22struct ThreadState {
25 koid: zx::Koid,
27}
28
29impl ThreadState {
30 fn new() -> Self {
32 let handle = fuchsia_runtime::with_thread_self(|thread| thread.raw_handle());
33 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
34 let ptr = OPERATION_GENERATION.with(|atomic| atomic as *const AtomicU64);
35
36 let mut rcu_nesting_level = std::ptr::null();
37 let mut rcu_counter_index = std::ptr::null();
38 fuchsia_rcu::with_thread_block_counters(|nesting_ptr, counter_ptr| {
39 rcu_nesting_level = nesting_ptr;
40 rcu_counter_index = counter_ptr;
41 });
42
43 let registered = RegisteredThread {
44 thread: unsafe { zx::Unowned::from_raw_handle(handle) },
46 koid,
47 atomic: ptr,
48 rcu_nesting_level,
49 rcu_counter_index,
50 };
51 REGISTRY.write().insert(registered);
52 Self { koid }
53 }
54}
55
56impl Drop for ThreadState {
57 fn drop(&mut self) {
59 REGISTRY.write().remove(&self.koid);
60 IS_REGISTERED.set(false);
61 }
62}
63
64thread_local! {
65 static THREAD_STATE: RefCell<Option<ThreadState>> = const { RefCell::new(None) };
66
67 static OPERATION_GENERATION: AtomicU64 = const { AtomicU64::new(0) };
74
75 static LAST_GENERATION: Cell<u64> = const { Cell::new(0) };
77
78 static IS_REGISTERED: Cell<bool> = const { Cell::new(false) };
80}
81
82#[derive(Clone)]
84struct RegisteredThread {
85 thread: zx::Unowned<'static, zx::Thread>,
87 koid: zx::Koid,
89 atomic: *const AtomicU64,
91 rcu_nesting_level: *const AtomicUsize,
93 rcu_counter_index: *const AtomicU8,
95}
96
97impl std::hash::Hash for RegisteredThread {
100 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101 self.koid.hash(state);
102 }
103}
104
105impl PartialEq for RegisteredThread {
106 fn eq(&self, other: &Self) -> bool {
107 self.koid == other.koid
108 }
109}
110
111impl Eq for RegisteredThread {}
112
113impl Borrow<zx::Koid> for RegisteredThread {
114 fn borrow(&self) -> &zx::Koid {
115 &self.koid
116 }
117}
118
119unsafe impl Send for RegisteredThread {}
122unsafe impl Sync for RegisteredThread {}
124
125#[derive(Clone, Debug)]
126pub struct ThreadLockupInfo {
127 pub thread: zx::Unowned<'static, zx::Thread>,
128 pub koid: zx::Koid,
129 pub start_time: zx::MonotonicInstant,
132}
133
134static FIRST_SEEN: LazyLock<fuchsia_sync::Mutex<HashMap<zx::Koid, (u64, zx::MonotonicInstant)>>> =
137 LazyLock::new(|| Default::default());
138
139static REGISTRY: LazyLock<
141 LockDepRwLock<HashSet<RegisteredThread>, ThreadLockupDetectorRegistryLock>,
142> = LazyLock::new(|| Default::default());
143
144impl ThreadLockupDetector {
145 #[inline]
152 fn start_operation() {
153 if !IS_REGISTERED.get() {
154 Self::register_current_thread();
155 }
156 let next = LAST_GENERATION.get().wrapping_add(1).max(1);
157 LAST_GENERATION.set(next);
158 OPERATION_GENERATION.with(|gen_atomic| gen_atomic.store(next, Ordering::Relaxed));
159 }
160
161 #[cold]
163 #[inline(never)]
164 fn register_current_thread() {
165 let _ = THREAD_STATE.try_with(|state| {
168 state.borrow_mut().get_or_insert_with(ThreadState::new);
169 IS_REGISTERED.set(true);
170 });
171 }
172
173 #[inline]
175 fn stop_operation() {
176 OPERATION_GENERATION.with(|gen_atomic| gen_atomic.store(0, Ordering::Relaxed));
177 }
178
179 pub fn get_long_running_threads(threshold: zx::MonotonicDuration) -> Vec<ThreadLockupInfo> {
186 let now = zx::MonotonicInstant::get();
187 let registry = REGISTRY.read();
188 let mut first_seen = FIRST_SEEN.lock();
189 first_seen.retain(|koid, _| {
191 registry.get(koid).is_some_and(|registered| {
192 unsafe { &*registered.atomic }.load(Ordering::Relaxed) != 0
195 })
196 });
197 registry
198 .iter()
199 .filter_map(|registered| {
200 let atomic = unsafe { &*registered.atomic };
204 let generation = atomic.load(Ordering::Relaxed);
205 if generation == 0 {
206 return None;
207 }
208 let entry = first_seen.entry(registered.koid).or_insert((generation, now));
211 if entry.0 != generation {
212 *entry = (generation, now);
213 }
214 let start_time = entry.1;
215 if now - start_time >= threshold {
216 Some(ThreadLockupInfo {
217 thread: registered.thread.clone(),
218 koid: registered.koid,
219 start_time,
220 })
221 } else {
222 None
223 }
224 })
225 .collect()
226 }
227
228 pub fn track() -> LockupDetectorGuard {
231 LockupDetectorGuard::new()
232 }
233
234 pub fn pause_tracking() -> LockupDetectorWaitingGuard {
237 LockupDetectorWaitingGuard::new()
238 }
239
240 pub fn track_future<F>(inner: F) -> LockupDetectorFuture<F> {
242 LockupDetectorFuture::new(inner)
243 }
244
245 pub fn tracked_channel<T>() -> (std::sync::mpsc::Sender<T>, LockupDetectorReceiver<T>) {
247 let (sender, receiver) = std::sync::mpsc::channel();
248 (sender, LockupDetectorReceiver::new(receiver))
249 }
250
251 pub fn active_rcu_read_locks<F>(mut check: F)
252 where
253 F: FnMut(&zx::Thread, zx::Koid, u8),
254 {
255 let registry = REGISTRY.read();
256 for registered in registry.iter() {
257 if registered.rcu_nesting_level.is_null() || registered.rcu_counter_index.is_null() {
258 continue;
259 }
260 let (nesting_level, counter_index) = unsafe {
266 (
267 (*registered.rcu_nesting_level).load(Ordering::Relaxed),
268 (*registered.rcu_counter_index).load(Ordering::Relaxed),
269 )
270 };
271 if nesting_level > 0 {
272 check(®istered.thread, registered.koid, counter_index);
273 }
274 }
275 }
276}
277
278pub struct LockupDetectorGuard;
279
280impl LockupDetectorGuard {
281 fn new() -> Self {
282 ThreadLockupDetector::start_operation();
283 Self
284 }
285}
286
287impl Drop for LockupDetectorGuard {
288 fn drop(&mut self) {
289 ThreadLockupDetector::stop_operation();
290 }
291}
292
293pub struct LockupDetectorWaitingGuard;
294
295impl LockupDetectorWaitingGuard {
296 fn new() -> Self {
297 ThreadLockupDetector::stop_operation();
298 Self
299 }
300}
301
302impl Drop for LockupDetectorWaitingGuard {
303 fn drop(&mut self) {
304 ThreadLockupDetector::start_operation();
305 }
306}
307
308#[pin_project]
309pub struct LockupDetectorFuture<F> {
310 #[pin]
311 inner: F,
312}
313
314impl<F> LockupDetectorFuture<F> {
315 fn new(inner: F) -> Self {
316 Self { inner }
317 }
318}
319
320impl<F: std::future::Future> std::future::Future for LockupDetectorFuture<F> {
321 type Output = F::Output;
322
323 fn poll(
324 self: std::pin::Pin<&mut Self>,
325 cx: &mut std::task::Context<'_>,
326 ) -> std::task::Poll<Self::Output> {
327 let _guard = LockupDetectorGuard::new();
328 let this = self.project();
329 this.inner.poll(cx)
330 }
331}
332
333pub struct LockupDetectorReceiver<T> {
334 inner: std::sync::mpsc::Receiver<T>,
335}
336
337impl<T> LockupDetectorReceiver<T> {
338 fn new(inner: std::sync::mpsc::Receiver<T>) -> Self {
339 Self { inner }
340 }
341
342 pub fn recv(&self) -> Result<T, std::sync::mpsc::RecvError> {
343 let _guard = LockupDetectorWaitingGuard::new();
344 self.inner.recv()
345 }
346
347 pub fn try_iter(&self) -> std::sync::mpsc::TryIter<'_, T> {
348 self.inner.try_iter()
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 fn get_long_running_koids() -> Vec<zx::Koid> {
357 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
358 .iter()
359 .map(|r| r.koid)
360 .collect()
361 }
362
363 #[test]
364 fn test_lockup_detector() {
365 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
366
367 {
368 let _guard = ThreadLockupDetector::track();
369
370 assert!(get_long_running_koids().contains(&koid));
372
373 assert!(get_long_running_koids().contains(&koid));
375 }
376
377 assert!(get_long_running_koids().is_empty());
379 }
380
381 #[test]
382 fn test_guard() {
383 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
384
385 {
386 let _guard = ThreadLockupDetector::track();
387 assert!(get_long_running_koids().contains(&koid));
388 }
389
390 assert!(get_long_running_koids().is_empty());
392 }
393
394 #[test]
395 fn test_start_time_is_recorded() {
396 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
397
398 let _guard = ThreadLockupDetector::track();
399 let before = zx::MonotonicInstant::get();
400 let first =
401 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
402 .into_iter()
403 .find(|info| info.koid == koid)
404 .expect("current thread is tracked");
405 let after = zx::MonotonicInstant::get();
406 assert!(before <= first.start_time && first.start_time <= after);
407
408 std::thread::sleep(std::time::Duration::from_millis(1));
410 let second =
411 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
412 .into_iter()
413 .find(|info| info.koid == koid)
414 .expect("current thread is still tracked");
415 assert_eq!(first.start_time, second.start_time);
416 }
417
418 #[test]
419 fn test_new_operation_resets_start_time() {
420 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
421 let start_time = || {
422 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
423 .into_iter()
424 .find(|info| info.koid == koid)
425 .expect("current thread is tracked")
426 .start_time
427 };
428
429 let first = {
430 let _guard = ThreadLockupDetector::track();
431 start_time()
432 };
433 std::thread::sleep(std::time::Duration::from_millis(1));
434
435 let _guard = ThreadLockupDetector::track();
437 assert!(start_time() > first);
438 }
439
440 #[test]
441 fn test_thread_exit_unregisters_thread() {
442 let koid = std::thread::spawn(|| {
443 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
444 std::mem::forget(ThreadLockupDetector::track());
446 assert!(REGISTRY.read().contains(&koid));
447 koid
448 })
449 .join()
450 .unwrap();
451
452 assert!(!REGISTRY.read().contains(&koid));
454 assert!(!get_long_running_koids().contains(&koid));
455 }
456
457 #[test]
458 fn test_waiting_guard() {
459 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
460
461 let _guard = ThreadLockupDetector::track();
462
463 {
464 let _waiting_guard = ThreadLockupDetector::pause_tracking();
465 assert!(get_long_running_koids().is_empty());
467 }
468
469 assert!(get_long_running_koids().contains(&koid));
471 }
472
473 #[test]
474 fn test_track_future() {
475 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
476 let (signal_tx, signal_rx) = futures::channel::oneshot::channel::<()>();
477
478 let t = std::thread::spawn(move || {
479 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
480 koid_tx.send(koid).unwrap();
481
482 let fut = ThreadLockupDetector::track_future(async move {
483 signal_rx.await.unwrap();
484 });
485
486 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
487
488 koid
489 });
490
491 let spawned_koid = koid_rx.recv().unwrap();
492
493 std::thread::sleep(std::time::Duration::from_millis(100));
495
496 assert!(!get_long_running_koids().contains(&spawned_koid));
498
499 signal_tx.send(()).unwrap();
501
502 t.join().unwrap();
503 }
504
505 #[test]
506 fn test_track_future_polling() {
507 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
508
509 assert!(!get_long_running_koids().contains(&koid));
511
512 let fut = ThreadLockupDetector::track_future(async {
513 assert!(get_long_running_koids().contains(&koid));
514 });
515
516 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
517
518 assert!(!get_long_running_koids().contains(&koid));
520 }
521
522 #[test]
523 fn test_track_channel() {
524 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
525 let (tx, rx) = ThreadLockupDetector::tracked_channel();
526
527 let t = std::thread::spawn(move || {
528 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
529 koid_tx.send(koid).unwrap();
530
531 let _guard = ThreadLockupDetector::track();
532
533 rx.recv().unwrap();
535
536 koid
537 });
538
539 let spawned_koid = koid_rx.recv().unwrap();
540
541 std::thread::sleep(std::time::Duration::from_millis(100));
543
544 assert!(!get_long_running_koids().contains(&spawned_koid));
546
547 tx.send(()).unwrap();
549
550 t.join().unwrap();
551 }
552}