use tokio::sync::watch;
#[derive(Debug, Clone)]
pub struct Stop(watch::Receiver<bool>);
#[derive(Debug)]
pub struct Request(watch::Sender<bool>);
impl Stop {
pub fn new() -> (Self, Request) {
let (sender, receiver) = watch::channel(false);
(Self(receiver), Request(sender))
}
#[cfg(test)]
pub fn never() -> Self {
Self::new().0
}
pub fn on_ctrl_c() -> Self {
let (stop, request) = Self::new();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
request.request();
}
});
stop
}
#[allow(
dead_code,
reason = "the run loop learns a stop happened from wait resolving, not by polling this; only this module's own tests call it directly"
)]
pub fn requested(&self) -> bool {
*self.0.borrow()
}
pub async fn wait(&mut self) {
if self.0.wait_for(|&requested| requested).await.is_err() {
core::future::pending::<()>().await;
}
}
}
impl Request {
pub fn request(&self) {
let _ = self.0.send(true);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interrupted {
No,
Yes,
}
pub async fn wait(interval: core::time::Duration, stop: &mut Stop) -> Interrupted {
tokio::select! {
biased;
() = stop.wait() => Interrupted::Yes,
() = tokio::time::sleep(interval) => Interrupted::No,
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::time::Duration;
use tokio::time::timeout;
#[tokio::test]
async fn a_request_wakes_a_waiter() {
let (mut stop, request) = Stop::new();
assert!(!stop.requested());
let waiter = tokio::spawn(async move {
stop.wait().await;
stop.requested()
});
request.request();
assert!(
timeout(Duration::from_secs(5), waiter)
.await
.expect("woke")
.expect("joined")
);
}
#[tokio::test]
async fn a_request_made_before_the_wait_still_wakes_it() {
let (mut stop, request) = Stop::new();
request.request();
assert!(stop.requested());
timeout(Duration::from_secs(5), stop.wait())
.await
.expect("a request already made resolves the wait at once");
}
#[tokio::test]
async fn no_request_never_wakes() {
let mut stop = Stop::never();
assert!(!stop.requested());
assert!(
timeout(Duration::from_millis(50), stop.wait())
.await
.is_err(),
"nothing requested a stop, so the wait must not resolve"
);
assert!(!stop.requested());
}
}