use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use wasmtime::Engine;
pub struct EpochTicker {
handle: Option<JoinHandle<()>>,
shutdown: Arc<AtomicBool>,
}
impl EpochTicker {
pub fn start(engine: Engine, interval: Duration) -> Self {
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_flag = Arc::clone(&shutdown);
let handle = thread::Builder::new()
.name("wasm-epoch-ticker".to_string())
.spawn(move || {
while !shutdown_flag.load(Ordering::Acquire) {
thread::sleep(interval);
if !shutdown_flag.load(Ordering::Acquire) {
engine.increment_epoch();
}
}
})
.expect("failed to spawn wasm-epoch-ticker thread");
Self {
handle: Some(handle),
shutdown,
}
}
pub fn is_running(&self) -> bool {
!self.shutdown.load(Ordering::Acquire)
}
}
impl Drop for EpochTicker {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Release);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use wasmtime::Config;
fn create_test_engine() -> Engine {
let mut config = Config::new();
config.epoch_interruption(true);
Engine::new(&config).expect("failed to create test engine")
}
#[test]
fn test_epoch_ticker_starts_and_stops() {
let engine = create_test_engine();
let ticker = EpochTicker::start(engine, Duration::from_millis(10));
assert!(ticker.is_running());
drop(ticker);
}
#[test]
fn test_epoch_ticker_increments_epoch() {
let engine = create_test_engine();
let ticker = EpochTicker::start(engine.clone(), Duration::from_millis(5));
std::thread::sleep(Duration::from_millis(20));
assert!(
ticker.is_running(),
"ticker should still be running after 20ms"
);
drop(ticker);
}
#[test]
fn test_epoch_ticker_stops_cleanly() {
let engine = create_test_engine();
let ticker = EpochTicker::start(engine, Duration::from_millis(10));
assert!(ticker.is_running());
drop(ticker);
let engine2 = create_test_engine();
let ticker2 = EpochTicker::start(engine2, Duration::from_millis(10));
assert!(ticker2.is_running());
drop(ticker2);
}
#[test]
fn test_multiple_tickers_on_different_engines() {
let engine1 = create_test_engine();
let engine2 = create_test_engine();
let ticker1 = EpochTicker::start(engine1, Duration::from_millis(10));
let ticker2 = EpochTicker::start(engine2, Duration::from_millis(10));
assert!(ticker1.is_running());
assert!(ticker2.is_running());
drop(ticker1);
assert!(ticker2.is_running());
drop(ticker2);
}
}