use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct Canceled {}
impl std::error::Error for Canceled {}
impl std::fmt::Display for Canceled {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "Operation was Canceled")
}
}
#[derive(Debug, Default)]
pub struct Token {
canceled: AtomicBool,
deadline: Option<Instant>,
}
impl Token {
pub fn new() -> Self {
Default::default()
}
pub fn with_duration(duration: Duration) -> Self {
Self {
canceled: AtomicBool::new(false),
deadline: Some(Instant::now() + duration),
}
}
pub fn with_deadline(deadline: Instant) -> Self {
Self {
canceled: AtomicBool::new(false),
deadline: Some(deadline),
}
}
pub fn cancel(&self) {
self.canceled.store(true, Ordering::Release);
}
pub fn was_canceled(&self) -> bool {
self.canceled.load(Ordering::Acquire)
}
pub fn is_canceled(&self) -> bool {
if self.was_canceled() {
true
} else if let Some(deadline) = self.deadline.as_ref() {
if Instant::now() > *deadline {
self.cancel();
true
} else {
false
}
} else {
false
}
}
pub fn check_cancel(&self) -> Result<(), Canceled> {
if self.is_canceled() {
Err(Canceled {})
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use failure::Fallible;
use std::sync::Arc;
#[test]
fn it_works() {
let token = Token::new();
assert!(!token.was_canceled());
token.cancel();
assert!(token.was_canceled());
}
fn check(token: &Token) -> Fallible<()> {
token.check_cancel()?;
Ok(())
}
#[test]
fn err() {
let token = Token::new();
token.cancel();
assert_eq!(true, token.check_cancel().is_err());
assert_eq!(true, check(&token).is_err());
}
#[test]
fn deadline() {
let hard_deadline = Instant::now() + Duration::new(2, 0);
let token = Token::with_duration(Duration::new(1, 0));
loop {
if token.is_canceled() {
break;
}
assert!(Instant::now() < hard_deadline);
std::thread::sleep(Duration::from_millis(200));
}
}
#[test]
fn threads() {
let token = Arc::new(Token::with_duration(Duration::new(1, 0)));
let shared = Arc::clone(&token);
let thr = std::thread::spawn(move || {
while !shared.is_canceled() {
std::thread::sleep(Duration::from_millis(200));
}
true
});
assert_eq!(true, thr.join().unwrap());
assert_eq!(true, token.was_canceled());
}
}