use std::io;
use std::sync::{Arc, Mutex, Weak};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{Builder, JoinHandle};
use std::time::{Duration, Instant};
struct Canary {
alive: AtomicBool,
thread: Mutex<Option<JoinHandle<()>>>,
}
impl Canary {
fn new() -> Self {
Self {
alive: AtomicBool::new(true),
thread: Mutex::new(None),
}
}
}
pub struct RunLoop {
flag: Weak<Canary>,
}
impl RunLoop {
pub fn new<F, T>(fun: F) -> io::Result<Self>
where
F: FnOnce(&Fn() -> bool) -> T,
F: Send + 'static,
{
Self::new_with_timeout(fun, 0 )
}
pub fn new_with_timeout<F, T>(fun: F, timeout_ms: u64) -> io::Result<Self>
where
F: FnOnce(&Fn() -> bool) -> T,
F: Send + 'static,
{
let flag = Arc::new(Canary::new());
let flag_ = flag.clone();
let thread = Builder::new().spawn(move || {
let timeout = Duration::from_millis(timeout_ms);
let start = Instant::now();
let still_alive = || {
flag.alive.load(Ordering::Relaxed) &&
(timeout_ms == 0 || start.elapsed() < timeout)
};
let _ = fun(&still_alive);
})?;
let mut guard = (*flag_).thread.lock().map_err(|_| {
io::Error::new(io::ErrorKind::Other, "failed to lock")
})?;
*guard = Some(thread);
Ok(Self { flag: Arc::downgrade(&flag_) })
}
pub fn cancel(&self) {
if let Some(flag) = self.flag.upgrade() {
flag.alive.store(false, Ordering::Relaxed);
if let Ok(mut guard) = flag.thread.lock() {
if let Some(handle) = (*guard).take() {
let _ = handle.join();
}
}
}
}
pub fn alive(&self) -> bool {
if let Some(flag) = self.flag.upgrade() {
flag.alive.load(Ordering::Relaxed)
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Barrier};
use std::sync::mpsc::channel;
use super::RunLoop;
#[test]
fn test_empty() {
let rloop = RunLoop::new(|_| {}).unwrap();
while rloop.alive() { }
rloop.cancel(); }
#[test]
fn test_cancel_early() {
RunLoop::new(|alive| assert!(!alive())).unwrap().cancel();
}
#[test]
fn test_cancel_endless_loop() {
let barrier = Arc::new(Barrier::new(2));
let b = barrier.clone();
let rloop = RunLoop::new(move |alive| {
b.wait();
while alive() { }
}).unwrap();
barrier.wait();
assert!(rloop.alive());
rloop.cancel();
assert!(!rloop.alive());
}
#[test]
fn test_timeout() {
let rloop = RunLoop::new_with_timeout(|alive| while alive() {}, 1).unwrap();
while rloop.alive() { }
assert!(!rloop.alive());
rloop.cancel(); }
#[test]
fn test_channel() {
let (tx, rx) = channel();
let rloop = RunLoop::new(move |alive| while alive() {
tx.send(0u8).unwrap();
}).unwrap();
assert_eq!(rx.recv().unwrap(), 0u8);
assert!(rloop.alive());
rloop.cancel();
assert!(!rloop.alive());
}
}