use std::future::Future;
use std::sync::OnceLock;
use async_executor::{Executor, Task};
const WORKERS: usize = 4;
static EXECUTOR: OnceLock<&'static Executor<'static>> = OnceLock::new();
fn executor() -> &'static Executor<'static> {
EXECUTOR.get_or_init(|| {
let shared: &'static Executor<'static> = Box::leak(Box::new(Executor::new()));
for index in 0..WORKERS {
std::thread::Builder::new()
.name(format!("frus-async-{index}"))
.spawn(move || async_io::block_on(shared.run(std::future::pending::<()>())))
.expect("spawning a frus async worker");
}
shared
})
}
pub(crate) fn spawn<T: Send + 'static>(
future: impl Future<Output = T> + Send + 'static,
) -> Task<T> {
executor().spawn(future)
}
pub(crate) async fn sleep(duration: std::time::Duration) {
async_io::Timer::after(duration).await;
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn a_task_runs_on_the_pool_and_gives_its_value_back() {
let task = spawn(async { 6 * 7 });
assert_eq!(futures_lite::future::block_on(task), 42);
}
#[test]
fn a_task_that_waits_on_a_timer_actually_wakes_up() {
let started = Instant::now();
let task = spawn(async {
sleep(Duration::from_millis(120)).await;
"awake"
});
assert_eq!(futures_lite::future::block_on(task), "awake");
assert!(
started.elapsed() >= Duration::from_millis(100),
"it returned in {:?}, which means it never actually waited",
started.elapsed()
);
}
#[test]
fn more_waiting_tasks_than_there_are_threads() {
let started = Instant::now();
let tasks: Vec<_> = (0..64)
.map(|i| {
spawn(async move {
sleep(Duration::from_millis(60)).await;
i
})
})
.collect();
let sum: usize = tasks.into_iter().map(futures_lite::future::block_on).sum();
assert_eq!(sum, (0..64).sum::<usize>());
assert!(
started.elapsed() < Duration::from_millis(600),
"{:?} — the waits did not overlap",
started.elapsed()
);
}
#[test]
fn dropping_a_handle_cancels_the_task() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let ran = Arc::new(AtomicBool::new(false));
let flag = ran.clone();
let task = spawn(async move {
sleep(Duration::from_millis(250)).await;
flag.store(true, Ordering::SeqCst);
});
drop(task);
std::thread::sleep(Duration::from_millis(400));
assert!(
!ran.load(Ordering::SeqCst),
"the task carried on after its handle was dropped"
);
}
}