use async_task::{Builder, Runnable, Task};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn block_on<F: Future>(mut fut: F) -> F::Output {
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
struct ParkWaker(thread::Thread);
impl Wake for ParkWaker {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.unpark();
}
}
let waker: Waker = Arc::new(ParkWaker(thread::current())).into();
let mut cx = Context::from_waker(&waker);
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => thread::park(),
}
}
}
fn spawn_worker() -> (mpsc::Sender<Runnable>, thread::JoinHandle<()>, Arc<AtomicBool>) {
let (tx, rx) = mpsc::channel::<Runnable>();
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_worker = shutdown.clone();
let handle = thread::spawn(move || {
while let Ok(runnable) = rx.recv() {
runnable.run();
if shutdown_worker.load(Ordering::Acquire) {
break;
}
}
});
(tx, handle, shutdown)
}
#[test]
fn runnable_is_send() {
fn assert_send<T: Send>() {}
assert_send::<Runnable>();
assert_send::<Task<u32>>();
}
#[test]
fn basic_spawn_and_await() {
let (tx, worker, _shutdown) = spawn_worker();
let tx2 = tx.clone();
let (runnable, task) = Builder::new()
.spawn(|()| async move { 42u32 }, move |runnable| tx2.send(runnable).unwrap());
runnable.schedule();
let result = block_on(task);
assert_eq!(result, 42);
drop(tx);
worker.join().unwrap();
}
#[test]
fn cancel_before_run_drops_future() {
let dropped = Arc::new(AtomicUsize::new(0));
let dropped2 = dropped.clone();
struct Guard(Arc<AtomicUsize>);
impl Drop for Guard {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Release);
}
}
let (runnable, task) = Builder::new().spawn(
move |()| {
let g = Guard(dropped2);
async move {
let _g = g;
1u32
}
},
|_| {},
);
drop(task);
runnable.run();
assert_eq!(dropped.load(Ordering::Acquire), 1, "future not dropped exactly once");
}
#[test]
fn cancel_await_resolves_after_runnable_stops() {
let (tx, worker, _shutdown) = spawn_worker();
let tx2 = tx.clone();
let started = Arc::new(AtomicBool::new(false));
let started2 = started.clone();
let (runnable, task) = Builder::new().spawn(
move |()| async move {
started2.store(true, Ordering::Release);
thread::sleep(Duration::from_millis(50));
99u32
},
move |r| tx2.send(r).unwrap(),
);
runnable.schedule();
thread::sleep(Duration::from_millis(10));
assert!(started.load(Ordering::Acquire), "worker did not start");
let result = block_on(task.cancel());
let _ = result;
drop(tx);
worker.join().unwrap();
}
#[test]
fn spawn_unchecked_accepts_non_static() {
let stack_value = 7u32;
let stack_ref = &stack_value;
let (tx, worker, _shutdown) = spawn_worker();
let tx2 = tx.clone();
let (runnable, task) = unsafe {
Builder::new()
.spawn_unchecked(|()| async move { *stack_ref + 1 }, move |r| tx2.send(r).unwrap())
};
runnable.schedule();
let result = block_on(task);
assert_eq!(result, 8);
drop(tx);
worker.join().unwrap();
}
#[test]
fn panic_in_future_propagates_to_awaiter() {
let (tx, worker, _shutdown) = spawn_worker();
let tx2 = tx.clone();
let (runnable, task) = Builder::new().spawn(
|()| async move {
panic!("smoke test panic");
},
move |r| tx2.send(r).unwrap(),
);
runnable.schedule();
let join_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
block_on(task);
0u32
}));
assert!(join_result.is_err(), "panic in future did not surface on awaiter");
drop(tx);
let _ = worker.join();
}
#[test]
fn fallible_task_returns_panic_as_result() {
let (tx, worker, _shutdown) = spawn_worker();
let tx2 = tx.clone();
let (runnable, task) = Builder::new().spawn(
|()| async move {
if true {
panic!("fallible panic");
}
42u32
},
move |r| tx2.send(r).unwrap(),
);
runnable.schedule();
let fallible = task.fallible();
let outcome = block_on(fallible);
assert!(outcome.is_none(), "fallible should report None on panic, got {:?}", outcome);
drop(tx);
let _ = worker.join();
}