use std::sync::mpsc::{self, RecvTimeoutError};
use std::time::Duration;
use futures::channel::oneshot;
use scheduler::Instant;
use super::{HangDetector, HangIncident};
#[derive(Debug, Clone, Copy)]
pub struct HangMonitorConfig {
pub threshold: Duration,
pub frame_budget: Duration,
pub interval: Duration,
}
#[derive(Debug, thiserror::Error)]
pub enum HangMonitorError {
#[error("the hang monitor was already started")]
AlreadyStarted,
#[error("failed to spawn the hang monitor thread: {0}")]
Spawn(#[source] std::io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HangMonitorPollReason {
Interval,
Flush,
}
pub struct HangMonitorPoll {
pub incidents: Vec<HangIncident>,
pub first_present_at: Option<Instant>,
pub reason: HangMonitorPollReason,
}
enum Request {
Flush { done: oneshot::Sender<()> },
}
pub(crate) struct HangMonitor {
requests: mpsc::Sender<Request>,
}
impl HangMonitor {
pub(crate) fn spawn<F>(
mut detector: HangDetector,
interval: Duration,
mut on_poll: F,
) -> std::io::Result<Self>
where
F: FnMut(HangMonitorPoll) + Send + 'static,
{
let (requests, receiver) = mpsc::channel();
std::thread::Builder::new()
.name("HangDetection".to_string())
.spawn(move || {
loop {
let (reason, done) = match receiver.recv_timeout(interval) {
Ok(Request::Flush { done }) => (HangMonitorPollReason::Flush, Some(done)),
Err(RecvTimeoutError::Timeout) => (HangMonitorPollReason::Interval, None),
Err(RecvTimeoutError::Disconnected) => break,
};
let incidents = detector.poll();
on_poll(HangMonitorPoll {
incidents,
first_present_at: detector.first_present_at(),
reason,
});
if let Some(done) = done {
done.send(()).ok();
}
}
})?;
Ok(Self { requests })
}
pub(crate) fn request_flush(&self) -> Option<oneshot::Receiver<()>> {
let (done, finished) = oneshot::channel();
self.requests.send(Request::Flush { done }).ok()?;
Some(finished)
}
}