#![allow(unknown_lints, unexpected_cfgs)] #![cfg(all( tokio_unstable, tokio_taskdump, target_os = "linux", any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") ))] use std::future::Future; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use tokio::runtime::dump::{Root, Trace}; pin_project_lite::pin_project! { pub struct PrettyFuture { #[pin] f: Root, t_last: State, logs: Arc>>, } } enum State { NotStarted, Running { since: Instant }, Alerted, } impl PrettyFuture { pub fn pretty(f: F, logs: Arc>>) -> Self { PrettyFuture { f: Trace::root(f), t_last: State::NotStarted, logs, } } } impl Future for PrettyFuture { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); let now = Instant::now(); let t_last = match this.t_last { State::Running { since } => Some(*since), State::NotStarted => { *this.t_last = State::Running { since: now }; None } State::Alerted => { // don't double-alert for the same future None } }; if t_last.is_some_and(|t_last| now.duration_since(t_last) > Duration::from_millis(500)) { let (res, trace) = tokio::runtime::dump::Trace::capture(|| this.f.as_mut().poll(cx)); this.logs.lock().unwrap().push(trace); *this.t_last = State::Alerted; return res; } this.f.poll(cx) } } #[tokio::test] async fn task_trace_self() { let log = Arc::new(Mutex::new(vec![])); let log2 = Arc::new(Mutex::new(vec![])); let mut good_line = vec![]; let mut bad_line = vec![]; PrettyFuture::pretty( PrettyFuture::pretty( async { bad_line.push(line!() + 1); tokio::task::yield_now().await; bad_line.push(line!() + 1); tokio::time::sleep(Duration::from_millis(1)).await; for _ in 0..100 { good_line.push(line!() + 1); tokio::time::sleep(Duration::from_millis(10)).await; } }, log.clone(), ), log2.clone(), ) .await; for line in good_line { let s = format!("{}:{}:", file!(), line); assert!(log.lock().unwrap().iter().any(|x| { eprintln!("{}", x); format!("{}", x).contains(&s) })); } for line in bad_line { let s = format!("{}:{}:", file!(), line); assert!(!log .lock() .unwrap() .iter() .any(|x| format!("{}", x).contains(&s))); } }