Skip to content

Commit

Permalink
feat(console): add warning for tasks that never yield
Browse files Browse the repository at this point in the history
  • Loading branch information
jefftt committed Jun 22, 2023
1 parent 2de5b68 commit e2fc234
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 2 deletions.
31 changes: 31 additions & 0 deletions console-subscriber/examples/busy_loop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::sync::atomic::Ordering;
use std::sync::{atomic::AtomicBool, Arc};
use std::time::Duration;
use tokio::task;
use tokio::time::sleep;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
console_subscriber::init();
let stop = Arc::new(AtomicBool::new(false));

let t = task::Builder::default()
.name("busy-loop")
.spawn({
let stop = Arc::clone(&stop);
async move {
loop {
if stop.load(Ordering::Acquire) {
break;
}
}
}
})
.unwrap();

sleep(Duration::from_secs(300)).await;
stop.store(true, Ordering::Release);
t.await?;

Ok(())
}
1 change: 1 addition & 0 deletions tokio-console/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async fn main() -> color_eyre::Result<()> {
.with_task_linters(vec![
warnings::Linter::new(warnings::SelfWakePercent::default()),
warnings::Linter::new(warnings::LostWaker),
warnings::Linter::new(warnings::NeverYielded::default()),
])
.with_retain_for(retain_for);
let mut input = input::EventStream::new();
Expand Down
4 changes: 4 additions & 0 deletions tokio-console/src/state/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ impl Task {
.unwrap_or_default()
}

pub(crate) fn yielded(&self) -> bool {
self.total_polls() > 1
}

/// Returns the total number of times the task has been polled.
pub(crate) fn total_polls(&self) -> u64 {
self.stats.polls
Expand Down
61 changes: 59 additions & 2 deletions tokio-console/src/warnings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::state::tasks::Task;
use std::{fmt::Debug, rc::Rc};
use crate::state::tasks::{Task, TaskState};
use std::{
fmt::Debug,
rc::Rc,
time::{Duration, SystemTime},
};

/// A warning for a particular type of monitored entity (e.g. task or resource).
///
Expand Down Expand Up @@ -150,3 +154,56 @@ impl Warn<Task> for LostWaker {
"This task has lost its waker, and will never be woken again.".into()
}
}

/// Warning for if a task has never yielded
#[derive(Clone, Debug)]
pub(crate) struct NeverYielded {
min_duration: Duration,
description: String,
}

impl NeverYielded {
pub(crate) const DEFAULT_DURATION: Duration = Duration::from_millis(10);
pub(crate) fn new(min_duration: Duration) -> Self {
Self {
min_duration,
description: format!(
"tasks have never yielded (threshold {}ms)",
min_duration.as_millis()
),
}
}
}

impl Default for NeverYielded {
fn default() -> Self {
Self::new(Self::DEFAULT_DURATION)
}
}

impl Warn<Task> for NeverYielded {
fn summary(&self) -> &str {
self.description.as_str()
}

fn check(&self, task: &Task) -> bool {
// Don't fire warning for tasks that are waiting to run
if task.state() != TaskState::Running {
return false;
}

if task.yielded() {
return false;
}

// Avoid short-lived task false positives
task.busy(SystemTime::now()) >= self.min_duration
}

fn format(&self, task: &Task) -> String {
format!(
"This task has never yielded ({:?})",
task.busy(SystemTime::now()),
)
}
}

0 comments on commit e2fc234

Please sign in to comment.