use std::{
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
thread,
};
use crate::{
bus::{Bus, BusEvent, BusReceiver},
element::Element,
error::{Result, ThreadSpawnError},
};
#[derive(Clone)]
pub struct StopReceiver {
flag: Arc<AtomicBool>,
}
impl StopReceiver {
pub fn is_stopped(&self) -> bool {
self.flag.load(Ordering::Acquire)
}
}
pub trait Driver: Element {
fn run(&mut self, stop: &StopReceiver, bus: &Bus) -> Result<()>;
}
pub struct DriverRunner {
driver: Mutex<Option<Box<dyn Driver>>>,
bus: Mutex<Option<Bus>>,
stop_flag: Arc<AtomicBool>,
bus_rx: BusReceiver,
running: AtomicBool,
}
impl DriverRunner {
pub fn new(driver: impl Driver + 'static) -> Arc<Self> {
let (bus, bus_rx) = Bus::new();
Arc::new(DriverRunner {
driver: Mutex::new(Some(Box::new(driver))),
bus: Mutex::new(Some(bus)),
stop_flag: Arc::new(AtomicBool::new(false)),
bus_rx,
running: AtomicBool::new(false),
})
}
pub fn bus(&self) -> &BusReceiver {
&self.bus_rx
}
pub fn run(self: &Arc<Self>) -> Result<()> {
self.run_with_spawner(|thread_name, task| {
thread::Builder::new().name(thread_name).spawn(task)
})
}
fn run_with_spawner(
self: &Arc<Self>,
spawn: impl FnOnce(
String,
Box<dyn FnOnce() + Send + 'static>,
) -> std::io::Result<thread::JoinHandle<()>>,
) -> Result<()> {
let Some(mut driver) = self.driver.lock().unwrap().take() else {
return Ok(());
};
let Some(bus) = self.bus.lock().unwrap().take() else {
return Ok(());
};
self.running.store(true, Ordering::Release);
let stop = StopReceiver {
flag: self.stop_flag.clone(),
};
let this = Arc::downgrade(self);
let thread_name = "driver".to_owned();
let spawn_result = spawn(
thread_name.clone(),
Box::new(move || {
let name = driver.name();
let element_type = driver.element_type();
if let Err(error) = driver.run(&stop, &bus) {
bus.post(
driver.pp_log(),
BusEvent::Error {
element_type,
name,
error,
},
);
}
if let Some(this) = this.upgrade() {
this.running.store(false, Ordering::Release);
}
}),
);
if let Err(source) = spawn_result {
self.running.store(false, Ordering::Release);
return Err(ThreadSpawnError::new(thread_name, source).into());
}
Ok(())
}
pub fn stop(&self) {
if !self.running.load(Ordering::Acquire) {
return;
}
self.stop_flag.store(true, Ordering::Release);
}
}
impl Drop for DriverRunner {
fn drop(&mut self) {
self.stop_flag.store(true, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use std::{sync::mpsc, time::Duration};
use crate::pp_log::PpLog;
use super::*;
use crate::element::{Element, ElementType, element_pp_log};
struct LoopingDriver {
pp_log: PpLog,
started: mpsc::Sender<()>,
stopped: mpsc::Sender<()>,
}
impl Element for LoopingDriver {
fn name(&self) -> Arc<str> {
"looping".into()
}
fn element_type(&self) -> ElementType {
ElementType::Other
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Driver for LoopingDriver {
fn run(&mut self, stop: &StopReceiver, _bus: &Bus) -> Result<()> {
let _ = self.started.send(());
while !stop.is_stopped() {
thread::sleep(Duration::from_millis(5));
}
let _ = self.stopped.send(());
Ok(())
}
}
#[test]
fn dropping_the_last_handle_stops_the_background_thread() {
let (started_tx, started_rx) = mpsc::channel();
let (stopped_tx, stopped_rx) = mpsc::channel();
let runner = DriverRunner::new(LoopingDriver {
started: started_tx,
stopped: stopped_tx,
pp_log: element_pp_log(ElementType::Other, "looping", None),
});
runner.run().unwrap();
started_rx
.recv_timeout(Duration::from_secs(1))
.expect("driver should start");
drop(runner);
stopped_rx
.recv_timeout(Duration::from_secs(1))
.expect("dropping the last DriverRunner handle should stop the background thread");
}
#[test]
fn thread_spawn_failure_is_returned_and_runner_is_not_left_running() {
let (started_tx, started_rx) = mpsc::channel();
let (stopped_tx, _stopped_rx) = mpsc::channel();
let runner = DriverRunner::new(LoopingDriver {
started: started_tx,
stopped: stopped_tx,
pp_log: element_pp_log(ElementType::Other, "looping", None),
});
let error = runner
.run_with_spawner(|_thread_name, _task| {
Err(std::io::Error::other("injected spawn failure"))
})
.expect_err("the injected spawn failure must be returned");
assert!(matches!(error, crate::Error::ThreadSpawnError(_)));
assert!(!runner.running.load(Ordering::Acquire));
assert!(started_rx.try_recv().is_err());
}
}