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, Debug)]
115pub struct ThreadLockupInfo {
116 pub thread: zx::Unowned<'static, zx::Thread>,
117 pub koid: zx::Koid,
118 pub start_time: zx::MonotonicInstant,
119}
120
121static REGISTRY: LazyLock<
123 LockDepRwLock<HashSet<RegisteredThread>, ThreadLockupDetectorRegistryLock>,
124> = LazyLock::new(|| Default::default());
125
126impl ThreadLockupDetector {
127 fn start_operation() {
129 THREAD_STATE.with(|state| {
130 let mut state = state.borrow_mut();
131 let state = state.get_or_insert_with(|| ThreadState::new());
132 state.atomic.store(zx::MonotonicInstant::get().into_nanos() as u64, Ordering::Relaxed);
133 });
134 }
135
136 fn stop_operation() {
138 THREAD_STATE.with(|state| {
139 if let Some(state) = state.borrow().as_ref() {
140 state.atomic.store(0, Ordering::Relaxed);
141 }
142 });
143 }
144
145 pub fn get_long_running_threads(threshold: zx::MonotonicDuration) -> Vec<ThreadLockupInfo> {
148 let now = zx::MonotonicInstant::get();
149 let registry = REGISTRY.read();
150 registry
151 .iter()
152 .filter_map(|registered| {
153 let atomic = unsafe { &*registered.atomic };
157 let start_nanos = atomic.load(Ordering::Relaxed);
158 if start_nanos == 0 {
159 return None;
160 }
161 let start_time = zx::MonotonicInstant::from_nanos(start_nanos as i64);
162 if now - start_time > threshold {
163 Some(ThreadLockupInfo {
164 thread: registered.thread.clone(),
165 koid: registered.koid,
166 start_time,
167 })
168 } else {
169 None
170 }
171 })
172 .collect()
173 }
174
175 pub fn track() -> LockupDetectorGuard {
178 LockupDetectorGuard::new()
179 }
180
181 pub fn pause_tracking() -> LockupDetectorWaitingGuard {
184 LockupDetectorWaitingGuard::new()
185 }
186
187 pub fn track_future<F>(inner: F) -> LockupDetectorFuture<F> {
189 LockupDetectorFuture::new(inner)
190 }
191
192 pub fn tracked_channel<T>() -> (std::sync::mpsc::Sender<T>, LockupDetectorReceiver<T>) {
194 let (sender, receiver) = std::sync::mpsc::channel();
195 (sender, LockupDetectorReceiver::new(receiver))
196 }
197
198 pub fn active_rcu_read_locks<F>(mut check: F)
199 where
200 F: FnMut(&zx::Thread, zx::Koid, u8),
201 {
202 let registry = REGISTRY.read();
203 for registered in registry.iter() {
204 if registered.rcu_nesting_level.is_null() || registered.rcu_counter_index.is_null() {
205 continue;
206 }
207 let (nesting_level, counter_index) = unsafe {
213 (
214 (*registered.rcu_nesting_level).load(Ordering::Relaxed),
215 (*registered.rcu_counter_index).load(Ordering::Relaxed),
216 )
217 };
218 if nesting_level > 0 {
219 check(®istered.thread, registered.koid, counter_index);
220 }
221 }
222 }
223}
224
225pub struct LockupDetectorGuard;
226
227impl LockupDetectorGuard {
228 fn new() -> Self {
229 ThreadLockupDetector::start_operation();
230 Self
231 }
232}
233
234impl Drop for LockupDetectorGuard {
235 fn drop(&mut self) {
236 ThreadLockupDetector::stop_operation();
237 }
238}
239
240pub struct LockupDetectorWaitingGuard;
241
242impl LockupDetectorWaitingGuard {
243 fn new() -> Self {
244 ThreadLockupDetector::stop_operation();
245 Self
246 }
247}
248
249impl Drop for LockupDetectorWaitingGuard {
250 fn drop(&mut self) {
251 ThreadLockupDetector::start_operation();
252 }
253}
254
255#[pin_project]
256pub struct LockupDetectorFuture<F> {
257 #[pin]
258 inner: F,
259}
260
261impl<F> LockupDetectorFuture<F> {
262 fn new(inner: F) -> Self {
263 Self { inner }
264 }
265}
266
267impl<F: std::future::Future> std::future::Future for LockupDetectorFuture<F> {
268 type Output = F::Output;
269
270 fn poll(
271 self: std::pin::Pin<&mut Self>,
272 cx: &mut std::task::Context<'_>,
273 ) -> std::task::Poll<Self::Output> {
274 let _guard = LockupDetectorGuard::new();
275 let this = self.project();
276 this.inner.poll(cx)
277 }
278}
279
280pub struct LockupDetectorReceiver<T> {
281 inner: std::sync::mpsc::Receiver<T>,
282}
283
284impl<T> LockupDetectorReceiver<T> {
285 fn new(inner: std::sync::mpsc::Receiver<T>) -> Self {
286 Self { inner }
287 }
288
289 pub fn recv(&self) -> Result<T, std::sync::mpsc::RecvError> {
290 let _guard = LockupDetectorWaitingGuard::new();
291 self.inner.recv()
292 }
293
294 pub fn try_iter(&self) -> std::sync::mpsc::TryIter<'_, T> {
295 self.inner.try_iter()
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 fn get_long_running_koids() -> Vec<zx::Koid> {
304 ThreadLockupDetector::get_long_running_threads(zx::MonotonicDuration::from_nanos(0))
305 .iter()
306 .map(|r| r.koid)
307 .collect()
308 }
309
310 #[test]
311 fn test_lockup_detector() {
312 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
313
314 {
315 let _guard = ThreadLockupDetector::track();
316
317 assert!(get_long_running_koids().contains(&koid));
319
320 assert!(get_long_running_koids().contains(&koid));
322 }
323
324 assert!(get_long_running_koids().is_empty());
326 }
327
328 #[test]
329 fn test_guard() {
330 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
331
332 {
333 let _guard = ThreadLockupDetector::track();
334 assert!(get_long_running_koids().contains(&koid));
335 }
336
337 assert!(get_long_running_koids().is_empty());
339 }
340
341 #[test]
342 fn test_waiting_guard() {
343 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
344
345 let _guard = ThreadLockupDetector::track();
346
347 {
348 let _waiting_guard = ThreadLockupDetector::pause_tracking();
349 assert!(get_long_running_koids().is_empty());
351 }
352
353 assert!(get_long_running_koids().contains(&koid));
355 }
356
357 #[test]
358 fn test_track_future() {
359 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
360 let (signal_tx, signal_rx) = futures::channel::oneshot::channel::<()>();
361
362 let t = std::thread::spawn(move || {
363 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
364 koid_tx.send(koid).unwrap();
365
366 let fut = ThreadLockupDetector::track_future(async move {
367 signal_rx.await.unwrap();
368 });
369
370 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
371
372 koid
373 });
374
375 let spawned_koid = koid_rx.recv().unwrap();
376
377 std::thread::sleep(std::time::Duration::from_millis(100));
379
380 assert!(!get_long_running_koids().contains(&spawned_koid));
382
383 signal_tx.send(()).unwrap();
385
386 t.join().unwrap();
387 }
388
389 #[test]
390 fn test_track_future_polling() {
391 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
392
393 assert!(!get_long_running_koids().contains(&koid));
395
396 let fut = ThreadLockupDetector::track_future(async {
397 assert!(get_long_running_koids().contains(&koid));
398 });
399
400 fuchsia_async::LocalExecutor::default().run_singlethreaded(fut);
401
402 assert!(!get_long_running_koids().contains(&koid));
404 }
405
406 #[test]
407 fn test_track_channel() {
408 let (koid_tx, koid_rx) = std::sync::mpsc::channel();
409 let (tx, rx) = ThreadLockupDetector::tracked_channel();
410
411 let t = std::thread::spawn(move || {
412 let koid = fuchsia_runtime::with_thread_self(|thread| thread.koid()).unwrap();
413 koid_tx.send(koid).unwrap();
414
415 let _guard = ThreadLockupDetector::track();
416
417 rx.recv().unwrap();
419
420 koid
421 });
422
423 let spawned_koid = koid_rx.recv().unwrap();
424
425 std::thread::sleep(std::time::Duration::from_millis(100));
427
428 assert!(!get_long_running_koids().contains(&spawned_koid));
430
431 tx.send(()).unwrap();
433
434 t.join().unwrap();
435 }
436}