use std::future::Future;
use std::pin::Pin;
use std::time::{Duration, Instant, SystemTime};
mod tokio_runtime;
pub use tokio_runtime::TokioRuntime;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm_runtime;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub use wasm_runtime::WasmRuntime;
#[cfg(all(feature = "embedded", feature = "std"))]
pub mod embedded_runtime;
#[cfg(all(feature = "embedded", feature = "std"))]
pub use embedded_runtime::EmbeddedRuntime;
#[cfg(all(feature = "wasi-leg", target_os = "wasi"))]
pub mod wasi_runtime;
#[cfg(all(feature = "wasi-leg", target_os = "wasi"))]
pub use wasi_runtime::WasiRuntime;
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
pub trait Runtime: Send + Sync + 'static {
fn spawn(&self, fut: BoxFuture<()>) -> SpawnHandle;
fn sleep(&self, duration: Duration) -> BoxFuture<()>;
fn now_monotonic(&self) -> Instant;
fn now_wall_clock(&self) -> SystemTime;
}
pub struct SpawnHandle {
inner: Box<dyn SpawnHandleInner>,
}
impl SpawnHandle {
pub fn from_inner<T: SpawnHandleInner>(inner: T) -> Self {
Self {
inner: Box::new(inner),
}
}
pub fn abort(&self) {
self.inner.abort();
}
pub fn is_finished(&self) -> bool {
self.inner.is_finished()
}
}
impl std::fmt::Debug for SpawnHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpawnHandle")
.field("is_finished", &self.is_finished())
.finish()
}
}
pub trait SpawnHandleInner: Send + 'static {
fn abort(&self);
fn is_finished(&self) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[tokio::test]
async fn tokio_runtime_spawn_and_sleep() {
let rt: Arc<dyn Runtime> = Arc::new(TokioRuntime);
let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
let c = counter.clone();
let rt_for_task = rt.clone();
let handle = rt.spawn(Box::pin(async move {
rt_for_task.sleep(Duration::from_millis(10)).await;
c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}));
rt.sleep(Duration::from_millis(100)).await;
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
assert!(handle.is_finished());
}
#[tokio::test]
async fn tokio_runtime_abort_cancels_task() {
let rt: Arc<dyn Runtime> = Arc::new(TokioRuntime);
let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
let c = counter.clone();
let rt_for_task = rt.clone();
let handle = rt.spawn(Box::pin(async move {
rt_for_task.sleep(Duration::from_secs(60)).await;
c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}));
rt.sleep(Duration::from_millis(20)).await;
handle.abort();
rt.sleep(Duration::from_millis(20)).await;
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 0);
}
#[test]
fn monotonic_clock_does_not_go_backwards() {
let rt = TokioRuntime;
let a = rt.now_monotonic();
for _ in 0..1000 {
std::hint::black_box(a);
}
let b = rt.now_monotonic();
assert!(b >= a, "monotonic clock went backwards: {:?} → {:?}", a, b);
}
#[test]
fn wall_clock_is_after_unix_epoch() {
let rt = TokioRuntime;
let now = rt.now_wall_clock();
assert!(now > SystemTime::UNIX_EPOCH);
}
#[test]
fn runtime_is_object_safe() {
fn assert_runtime_obj_safe(_: &dyn Runtime) {}
let rt = TokioRuntime;
assert_runtime_obj_safe(&rt);
}
}