use std::{
cell::RefCell,
future::{Future, Pending, pending, poll_fn},
io::{self, Write},
mem::MaybeUninit,
pin::{Pin, pin},
sync::{
Arc, Mutex, Weak,
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc,
},
task::{Context, Poll, Wake, Waker},
thread,
time::{Duration, Instant},
};
use futures::{executor::block_on, future::poll_immediate};
use ntest::timeout;
use socket2::SockRef;
use super::{core::pair, yield_now};
#[cfg(feature = "event")]
use crate::{Event, EventListener};
use crate::{
Interest, Interval, LocalRuntime, Registration, Shared, SharedRuntime, Sleep, Task, Timeout,
driver,
mode::sealed::Sealed as _,
runtime::{Core, lock},
};
#[test]
#[timeout(15000)]
fn handles_cross_threads() {
fn shared<T>()
where
T: Send + Sync + Unpin,
{
}
fn sent<T>()
where
T: Send,
{
}
shared::<SharedRuntime>();
shared::<Task<(), Shared>>();
shared::<Registration<Shared>>();
sent::<Sleep<Shared>>();
sent::<Timeout<Pending<()>, Shared>>();
sent::<Interval<Shared>>();
}
#[test]
#[timeout(15000)]
fn a_spawned_task_runs_to_completion() {
let runtime = runtime();
let ran = Arc::new(AtomicBool::new(false));
let task = {
let ran = ran.clone();
runtime.spawn("a task that sets a flag", async move {
ran.store(true, Ordering::Release);
})
};
block_on(task).unwrap();
assert!(ran.load(Ordering::Acquire));
}
#[test]
#[timeout(15000)]
fn a_spawned_task_hands_its_output_back() {
let runtime = runtime();
let task = runtime.spawn("an answer", async { 42 });
assert_eq!(block_on(task).unwrap(), 42);
}
#[test]
#[timeout(15000)]
fn a_finished_task_whose_handle_is_dropped_leaves_nothing_behind() {
let runtime = runtime();
let task = runtime.spawn("a task that finishes at once", async {});
drive(&runtime, async {
std::future::poll_fn(|cx| {
cx.waker().wake_by_ref();
std::task::Poll::<()>::Ready(())
})
.await
});
assert!(within_a_second(|| !runtime.inner().is_busy()));
drop(task);
assert!(!runtime.inner().is_busy());
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn the_helper_starts_on_the_first_spawn() {
let runtime = runtime();
let task = runtime.spawn("a task that names the thread it runs on", async {
thread::current().name().map(str::to_owned)
});
let name = block_on(task).unwrap();
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[test]
#[timeout(15000)]
fn the_helper_exits_when_nothing_is_left() {
let runtime = runtime();
let sleep = runtime.sleep(Duration::from_millis(50));
let task = runtime.spawn("a task that sleeps", sleep);
block_on(task).unwrap();
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn cancelling_the_last_task_from_another_thread_lets_the_helper_exit() {
let runtime = runtime();
let task = runtime.spawn("a task that never finishes", pending::<()>());
assert!(runtime.helper_running());
drop(task);
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn cancelling_the_last_task_from_another_thread_and_waiting_lets_the_helper_exit() {
let runtime = runtime();
let task = runtime.spawn("a task that never finishes", pending::<u32>());
assert!(runtime.helper_running());
assert_eq!(block_on(task.cancel()), None);
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn cancelling_a_task_the_helper_polls_waits_for_that_poll() {
let runtime = runtime();
let held = Arc::new(());
let (announce, announced) = mpsc::channel();
let (release, released) = mpsc::channel::<()>();
let task = {
let held = held.clone();
runtime.spawn("a task held in its poll", async move {
let _held = held;
announce.send(()).unwrap();
let _ = released.recv();
pending::<u32>().await
})
};
announced.recv().unwrap();
let mut cancelling = pin!(task.cancel());
assert!(block_on(poll_immediate(cancelling.as_mut())).is_none());
assert_eq!(Arc::strong_count(&held), 2);
release.send(()).unwrap();
assert_eq!(block_on(cancelling), None);
assert_eq!(Arc::strong_count(&held), 1);
assert!(helper_gone(&runtime));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_task_on_one_runtime_wakes_a_task_on_another() {
let waiting_runtime = runtime();
let waking_runtime = runtime();
let listening = Arc::new(Event::new());
let woken = Arc::new(Event::new());
let announced = listening.listen();
let waiting = {
let woken = woken.clone();
waiting_runtime.spawn("a task waiting to be woken", async move {
let wake = woken.listen();
listening.notify(1);
wake.await;
42
})
};
block_on(announced);
thread::sleep(Duration::from_millis(100));
let _waking = waking_runtime.spawn("a task on another runtime", async move {
woken.notify(1);
});
assert_eq!(block_on(waiting).unwrap(), 42);
}
#[test]
#[timeout(15000)]
fn a_finished_detached_task_lets_the_helper_exit() {
let runtime = runtime();
let sleep = runtime.sleep(Duration::from_millis(50));
runtime.spawn("a detached task", sleep).detach();
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn a_registration_reports_readiness() {
let runtime = runtime();
let (source, mut peer) = pair();
let registration = runtime.register(source.clone()).unwrap();
let writer = thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
peer.write_all(&[7]).unwrap();
});
let read = block_on(poll_fn(|cx| {
let mut byte = [MaybeUninit::<u8>::uninit(); 1];
registration.poll_io(cx, Interest::Readable, || {
SockRef::from(&source).recv(&mut byte)
})
}));
assert_eq!(read.unwrap(), 1);
writer.join().unwrap();
}
#[cfg(windows)]
#[test]
#[timeout(15000)]
fn an_af_unix_socket_is_watched_beside_the_tcp_wake_socket() {
use std::{
env, fs,
os::windows::io::{AsSocket, OwnedSocket},
process,
time::SystemTime,
};
use uds_windows::{UnixListener, UnixStream};
let runtime = runtime();
let since_epoch = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let path = env::temp_dir().join(format!(
"zruntime-{}-{}.sock",
process::id(),
since_epoch.as_nanos()
));
let listener = UnixListener::bind(&path).unwrap();
let client = UnixStream::connect(&path).unwrap();
let (mut server, _) = listener.accept().unwrap();
client.set_nonblocking(true).unwrap();
server.set_nonblocking(true).unwrap();
let owned: OwnedSocket = client.as_socket().try_clone_to_owned().unwrap();
let source: Arc<OwnedSocket> = Arc::new(owned);
let registration = runtime.register(source.clone()).unwrap();
let writer = thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
server.write_all(&[7]).unwrap();
});
let read = block_on(poll_fn(|cx| {
let mut byte = [MaybeUninit::<u8>::uninit(); 1];
registration.poll_io(cx, Interest::Readable, || {
SockRef::from(&source).recv(&mut byte)
})
}));
assert_eq!(read.unwrap(), 1);
writer.join().unwrap();
drop(listener);
fs::remove_file(&path).unwrap();
}
#[test]
#[timeout(15000)]
fn sleep_resolves_once_the_duration_has_passed() {
let runtime = runtime();
let started = Instant::now();
block_on(runtime.sleep(Duration::from_millis(50)));
assert!(started.elapsed() >= Duration::from_millis(50));
}
#[test]
#[timeout(15000)]
fn a_sleep_polled_after_the_helper_retired_is_fired() {
let runtime = runtime();
let sleep = runtime.sleep(Duration::from_millis(20));
assert!(!runtime.helper_running());
let started = Instant::now();
block_on(sleep);
assert!(started.elapsed() >= Duration::from_millis(20));
assert!(started.elapsed() < Duration::from_secs(1));
}
#[test]
#[timeout(15000)]
fn a_sleep_armed_from_another_thread_bounds_the_wait() {
let runtime = runtime();
let (source, _peer) = pair();
let _registration = runtime.register(source).unwrap();
let started = Instant::now();
block_on(runtime.sleep(Duration::from_millis(20)));
assert!(started.elapsed() < Duration::from_millis(500));
}
#[test]
#[timeout(15000)]
fn a_dropped_sleep_lets_the_helper_exit() {
let runtime = runtime();
{
let mut sleep = pin!(runtime.sleep(Duration::from_secs(10)));
assert!(block_on(poll_immediate(sleep.as_mut())).is_none());
assert!(runtime.helper_running());
}
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn a_sleep_until_starts_the_helper_and_dropping_it_lets_it_exit() {
let runtime = runtime();
{
let mut sleep = pin!(runtime.sleep_until(Instant::now() + Duration::from_secs(10)));
assert!(!runtime.helper_running());
assert!(block_on(poll_immediate(sleep.as_mut())).is_none());
assert!(runtime.helper_running());
}
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn the_helper_fires_a_sleep_reset_to_an_earlier_deadline() {
let runtime = runtime();
let woken = Arc::new(Woken::default());
let waker = Waker::from(woken.clone());
let mut sleep = runtime.sleep(Duration::from_secs(10));
let polled = Pin::new(&mut sleep).poll(&mut Context::from_waker(&waker));
assert!(polled.is_pending());
assert!(runtime.helper_running());
thread::sleep(Duration::from_millis(50));
sleep.reset_after(Duration::from_millis(20));
let deadline = Instant::now() + Duration::from_secs(5);
while woken.count() == 0 && Instant::now() < deadline {
thread::sleep(Duration::from_millis(10));
}
assert_eq!(woken.count(), 1);
}
#[test]
#[timeout(15000)]
fn a_sleep_reset_beyond_the_clock_lets_the_helper_exit() {
let runtime = runtime();
let mut sleep = pin!(runtime.sleep(Duration::from_secs(10)));
assert!(block_on(poll_immediate(sleep.as_mut())).is_none());
assert!(runtime.helper_running());
thread::sleep(Duration::from_millis(50));
sleep.reset_after(Duration::MAX);
assert!(helper_gone(&runtime));
assert!(!runtime.inner().is_busy());
}
#[test]
#[timeout(15000)]
fn a_timeout_starts_the_helper_and_dropping_it_lets_it_exit() {
let runtime = runtime();
{
let mut timeout = pin!(runtime.timeout(Duration::from_secs(10), pending::<()>()));
assert!(!runtime.helper_running());
assert!(block_on(poll_immediate(timeout.as_mut())).is_none());
assert!(runtime.helper_running());
}
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn a_timeout_whose_future_completes_at_once_starts_no_helper() {
let runtime = runtime();
let output = block_on(runtime.timeout(Duration::from_secs(10), async { 7 }));
assert_eq!(output.ok(), Some(7));
assert!(!runtime.helper_running());
assert!(!runtime.inner().is_busy());
}
#[test]
#[timeout(15000)]
fn an_interval_starts_the_helper_and_dropping_it_lets_it_exit() {
let runtime = runtime();
{
let mut interval = runtime.interval(Duration::from_secs(10));
assert!(!runtime.helper_running());
assert!(block_on(poll_immediate(interval.tick())).is_none());
assert!(runtime.helper_running());
}
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn the_helper_keeps_an_interval_ticking() {
let runtime = runtime();
let period = Duration::from_millis(20);
let woken = Arc::new(Woken::default());
let waker = Waker::from(woken.clone());
let mut cx = Context::from_waker(&waker);
let mut interval = runtime.interval(period);
let mut ticks = Vec::new();
while ticks.len() < 3 {
let seen = woken.count();
match interval.poll_tick(&mut cx) {
Poll::Ready(tick) => ticks.push(tick),
Poll::Pending => {
let deadline = Instant::now() + Duration::from_secs(5);
while woken.count() == seen && Instant::now() < deadline {
thread::sleep(Duration::from_millis(5));
}
assert!(woken.count() > seen);
}
}
}
assert_eq!(ticks[1] - ticks[0], period);
assert_eq!(ticks[2] - ticks[1], period);
}
#[test]
#[timeout(15000)]
fn a_sleep_beyond_the_clock_starts_no_helper() {
let runtime = runtime();
let mut sleep = pin!(runtime.sleep(Duration::MAX));
assert!(block_on(poll_immediate(sleep.as_mut())).is_none());
assert!(!runtime.helper_running());
assert!(!runtime.inner().is_busy());
}
#[test]
#[timeout(15000)]
fn an_interval_beyond_the_clock_starts_no_helper() {
let runtime = runtime();
let mut interval = runtime.interval(Duration::MAX);
assert!(block_on(poll_immediate(interval.tick())).is_none());
assert!(!runtime.helper_running());
assert!(!runtime.inner().is_busy());
}
#[test]
#[timeout(15000)]
fn a_spawn_during_the_helper_exit_is_not_stranded() {
let runtime = runtime();
for _ in 0..200 {
let started = Instant::now();
let task = runtime.spawn("a task that finishes at once", async {});
block_on(task).unwrap();
assert!(started.elapsed() < Duration::from_secs(5));
}
}
#[test]
#[timeout(15000)]
fn a_registration_without_tasks_keeps_the_helper_alive() {
let runtime = runtime();
let (source, _peer) = pair();
let _registration = runtime.register(source).unwrap();
assert!(runtime.helper_running());
thread::sleep(Duration::from_millis(200));
assert!(runtime.helper_running());
}
#[test]
#[timeout(15000)]
fn a_task_that_panics_leaves_the_helper_running() {
let runtime = runtime();
let _held = runtime.spawn("a task that never finishes", pending::<()>());
let panicking = runtime.spawn("a task that panics", async {
panic!("the task panicked on purpose");
});
assert!(block_on(panicking).is_err());
assert!(runtime.helper_running());
let next = runtime.spawn("the task after it", async { 7 });
assert_eq!(block_on(next).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn a_panic_in_a_futures_drop_leaves_the_helper_running() {
struct PanicOnDrop;
impl Future for PanicOnDrop {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
Poll::Ready(())
}
}
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("the future's drop panicked on purpose");
}
}
let runtime = runtime();
let _held = runtime.spawn("a task that never finishes", pending::<()>());
let going = runtime.spawn("a task that panics as it goes", PanicOnDrop);
assert!(block_on(going).is_ok());
assert!(runtime.helper_running());
let next = runtime.spawn("the task after it", async { 7 });
assert_eq!(block_on(next).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn a_cancelled_futures_drop_may_spawn_on_the_runtime() {
struct SpawnOnDrop {
runtime: SharedRuntime,
ran: Arc<AtomicBool>,
}
impl Drop for SpawnOnDrop {
fn drop(&mut self) {
let ran = self.ran.clone();
self.runtime
.spawn("a task spawned from a drop", async move {
ran.store(true, Ordering::Release);
})
.detach();
}
}
let runtime = runtime();
let ran = Arc::new(AtomicBool::new(false));
let polled = Arc::new(AtomicBool::new(false));
let task = {
let spawner = SpawnOnDrop {
runtime: runtime.clone(),
ran: ran.clone(),
};
let polled = polled.clone();
runtime.spawn("a task that never finishes", async move {
let _spawner = spawner;
polled.store(true, Ordering::Release);
pending::<()>().await;
})
};
assert!(within_a_second(|| polled.load(Ordering::Acquire)));
drop(task);
assert!(within_a_second(|| ran.load(Ordering::Acquire)));
}
#[test]
#[timeout(15000)]
fn a_helper_that_dies_is_replaced() {
struct PanickingWaker;
impl Wake for PanickingWaker {
fn wake(self: Arc<Self>) {
panic!("the waker panicked on purpose");
}
}
let runtime = runtime();
let _held = runtime.spawn("a task that never finishes", pending::<()>());
let waker = Waker::from(Arc::new(PanickingWaker));
let mut sleep = pin!(runtime.sleep(Duration::from_millis(1)));
let polled = sleep.as_mut().poll(&mut Context::from_waker(&waker));
assert!(polled.is_pending());
assert!(helper_gone(&runtime));
let task = runtime.spawn("the task after it", async { 7 });
assert_eq!(block_on(task).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn block_on_runs_the_tasks_on_its_own_thread() {
let runtime = runtime();
let ran_on = drive(&runtime, async {
runtime
.spawn("a task that names the thread it runs on", async {
thread::current().id()
})
.await
})
.unwrap();
assert_eq!(ran_on, thread::current().id());
assert!(!runtime.helper_running());
}
#[test]
#[timeout(15000)]
fn block_on_fires_a_timer_on_its_own_thread() {
let runtime = runtime();
let started = Instant::now();
drive(&runtime, runtime.sleep(Duration::from_millis(20)));
assert!(started.elapsed() >= Duration::from_millis(20));
assert!(!runtime.helper_running());
}
#[test]
#[timeout(15000)]
fn block_on_waits_for_readiness_on_its_own_thread() {
let runtime = runtime();
let (source, mut peer) = pair();
let writer = thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
peer.write_all(&[7]).unwrap();
});
let read = drive(&runtime, async {
let registration = runtime.register(source.clone()).unwrap();
poll_fn(|cx| {
let mut byte = [MaybeUninit::<u8>::uninit(); 1];
registration.poll_io(cx, Interest::Readable, || {
SockRef::from(&source).recv(&mut byte)
})
})
.await
});
assert_eq!(read.unwrap(), 1);
assert!(!runtime.helper_running());
writer.join().unwrap();
}
#[test]
#[timeout(15000)]
fn a_wake_from_another_thread_ends_the_wait_block_on_is_in() {
let runtime = runtime();
let wakers = Arc::new(Mutex::new(None::<Waker>));
let waker_thread = {
let wakers = wakers.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
if let Some(waker) = lock(&wakers).take() {
waker.wake();
}
})
};
let started = Instant::now();
drive(&runtime, async {
let mut polled = false;
poll_fn(|cx| {
if polled {
return Poll::Ready(());
}
polled = true;
*lock(&wakers) = Some(cx.waker().clone());
Poll::Pending
})
.await
});
assert!(started.elapsed() >= Duration::from_millis(50));
assert!(started.elapsed() < Duration::from_secs(5));
waker_thread.join().unwrap();
}
#[test]
#[timeout(15000)]
fn work_left_behind_by_block_on_goes_to_a_helper() {
let runtime = runtime();
let task = runtime.spawn("a task that never finishes", pending::<()>());
drop(task);
assert!(helper_gone(&runtime));
let mut task = None;
drive(&runtime, async {
task = Some(runtime.spawn("a task that never finishes", pending::<()>()));
});
assert!(within_a_second(|| runtime.helper_running()));
drop(task);
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn work_left_by_a_block_on_that_took_no_seat_goes_to_a_helper() {
let runtime = runtime();
let (resolve, _own) = resolve_on_the_second_ask(&runtime);
let mut task = None;
driver::block_on(driver::Target::Own, &*resolve, async {
task = Some(runtime.spawn("a task that never finishes", pending::<()>()));
});
assert!(within_a_second(|| runtime.helper_running()));
drop(task);
assert!(helper_gone(&runtime));
}
#[test]
#[timeout(15000)]
fn work_left_by_a_block_on_that_panicked_without_the_seat_goes_to_a_helper() {
let runtime = runtime();
let (resolve, _own) = resolve_on_the_second_ask(&runtime);
let mut task = None;
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
driver::block_on(driver::Target::Own, &*resolve, async {
task = Some(runtime.spawn("a task that never finishes", pending::<()>()));
panic!("a future that panics once it has spawned");
});
}));
assert!(panicked.is_err());
assert!(within_a_second(|| runtime.helper_running()));
drop(task);
assert!(helper_gone(&runtime));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_takes_the_seat_from_the_helper() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let (parked, watcher) = the_parked_helper_announced(&runtime);
let ran_on = drive(&runtime, async {
parked.await;
runtime
.spawn("a task that names the thread it runs on", async {
thread::current().id()
})
.await
})
.unwrap();
assert!(watcher.join().unwrap());
assert_eq!(ran_on, thread::current().id());
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn the_helper_takes_the_seat_back_when_block_on_leaves() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let (parked, watcher) = the_parked_helper_announced(&runtime);
drive(&runtime, parked);
assert!(watcher.join().unwrap());
let task = runtime.spawn("a task that names the thread it runs on", async {
thread::current().name().map(str::to_owned)
});
let name = block_on(task).unwrap();
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_that_leaves_without_the_seat_rouses_the_helper() {
let runtime = runtime();
let held = held_by_the_helper(&runtime);
let (parked, watcher) = the_parked_helper_announced(&runtime);
drive(&runtime, async {
block_on(parked);
});
assert!(watcher.join().unwrap());
assert!(within_a_second(|| !runtime.helper_parked()));
drop(held);
assert!(helper_gone(&runtime));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_parked_helper_leaves_once_nothing_is_left() {
let runtime = runtime();
let held = held_by_the_helper(&runtime);
drive(&runtime, async {});
assert!(runtime.helper_running());
drop(held);
assert!(helper_gone(&runtime));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_second_block_on_parks_while_the_first_drives() {
let runtime = runtime();
let release = Arc::new(Event::new());
let driver = {
let runtime = runtime.clone();
let release = release.clone();
thread::spawn(move || {
drive(&runtime, async move {
release.listen().await;
})
})
};
thread::sleep(Duration::from_millis(50));
let task = runtime.spawn("a task that names the thread it runs on", async {
thread::current().id()
});
let ran_on = drive(&runtime, task).unwrap();
assert_eq!(ran_on, driver.thread().id());
assert!(!runtime.helper_running());
release.notify(1);
driver.join().unwrap();
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_that_waited_for_the_seat_leaves_nothing_behind() {
let runtime = runtime();
let release = Arc::new(Event::new());
let driver = {
let runtime = runtime.clone();
let release = release.clone();
thread::spawn(move || {
drive(&runtime, async move {
release.listen().await;
})
})
};
thread::sleep(Duration::from_millis(50));
let task = runtime.spawn("a task that names the thread it runs on", async {
thread::current().id()
});
let ran_on = drive(&runtime, task).unwrap();
assert_eq!(ran_on, driver.thread().id());
assert_eq!(waiting(&runtime), 0);
release.notify(1);
driver.join().unwrap();
}
#[test]
#[timeout(15000)]
fn a_block_on_inside_a_task_panics_rather_than_hangs() {
let runtime = runtime();
let task = runtime.spawn("a task that calls block_on", {
let runtime = runtime.clone();
async move {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
drive(&runtime, async {});
}))
.is_err()
}
});
assert!(drive(&runtime, task).unwrap());
}
#[test]
#[timeout(15000)]
fn a_panic_in_the_future_frees_the_seat() {
let runtime = runtime();
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
drive(&runtime, async { panic!("a future that panics") });
}));
assert!(panicked.is_err());
let task = runtime.spawn("the task after it", async { 7 });
assert_eq!(drive(&runtime, task).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn every_handle_in_a_registry_shares_one_runtime() {
let registry = Mutex::new(Weak::new());
let first = SharedRuntime::from_inner(driver::shared_in(®istry).unwrap());
let second = SharedRuntime::from_inner(driver::shared_in(®istry).unwrap());
assert!(Arc::ptr_eq(first.inner(), second.inner()));
}
#[test]
#[timeout(15000)]
fn a_shared_runtime_goes_with_its_last_handle() {
let registry = Mutex::new(Weak::new());
let handle = SharedRuntime::from_inner(driver::shared_in(®istry).unwrap());
let inner = Arc::downgrade(handle.inner());
drop(handle);
assert!(inner.upgrade().is_none());
let next = SharedRuntime::from_inner(driver::shared_in(®istry).unwrap());
assert!(inner.upgrade().is_none());
drop(next);
}
#[test]
#[timeout(15000)]
fn each_thread_inside_block_on_has_a_runtime_of_its_own() {
let on_this_thread = crate::block_on(async { SharedRuntime::current().unwrap() });
let on_another = thread::spawn(|| crate::block_on(async { SharedRuntime::current().unwrap() }))
.join()
.unwrap();
assert!(!Arc::ptr_eq(on_this_thread.inner(), on_another.inner()));
let again = crate::block_on(async { SharedRuntime::current().unwrap() });
assert!(Arc::ptr_eq(again.inner(), on_this_thread.inner()));
}
#[test]
#[timeout(15000)]
fn work_built_under_another_executor_shares_one_runtime() {
let here = SharedRuntime::current().unwrap();
let there = thread::spawn(|| SharedRuntime::current().unwrap())
.join()
.unwrap();
assert!(Arc::ptr_eq(here.inner(), there.inner()));
}
#[test]
#[timeout(15000)]
fn a_value_built_on_a_helper_thread_joins_the_runtime_the_helper_runs() {
let runtime = runtime();
let expected = Arc::as_ptr(runtime.inner()) as usize;
let built_on = runtime.spawn("a task that builds a handle", async {
Arc::as_ptr(SharedRuntime::current().unwrap().inner()) as usize
});
assert_eq!(block_on(built_on).unwrap(), expected);
}
#[test]
#[timeout(15000)]
fn a_spawn_from_inside_another_threads_block_on_starts_a_helper() {
let runtime = runtime();
let ran = Arc::new(AtomicBool::new(false));
let mut task = None;
{
let ran = ran.clone();
driver::block_on(driver::Target::Own, &|| None, async {
task = Some(
runtime.spawn("a task of another thread's runtime", async move {
ran.store(true, Ordering::Release);
}),
);
});
}
block_on(task.unwrap()).unwrap();
assert!(ran.load(Ordering::Acquire));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_task_spawned_as_a_thread_ends_is_run_by_a_helper() {
struct BuildsAsItGoes(Option<Arc<Event>>);
impl Drop for BuildsAsItGoes {
fn drop(&mut self) {
let Some(ran) = self.0.take() else {
return;
};
SharedRuntime::current()
.unwrap()
.spawn("a task spawned as a thread ends", async move {
ran.notify(1);
})
.detach();
}
}
thread_local! {
static BUILDS_AS_IT_GOES: RefCell<BuildsAsItGoes> =
const { RefCell::new(BuildsAsItGoes(None)) };
}
let ran = Arc::new(Event::new());
let notified = ran.listen();
let thread = thread::spawn(move || {
BUILDS_AS_IT_GOES.with(|builds| builds.borrow_mut().0 = Some(ran));
drop(SharedRuntime::current().unwrap());
});
assert!(thread.join().is_ok());
block_on(notified);
}
#[test]
#[timeout(15000)]
fn block_on_as_a_thread_ends_does_not_abort() {
struct BlocksAsItGoes;
impl Drop for BlocksAsItGoes {
fn drop(&mut self) {
crate::block_on(async { drop(SharedRuntime::current().unwrap()) });
}
}
thread_local! {
static BLOCKS_AS_IT_GOES: BlocksAsItGoes = const { BlocksAsItGoes };
}
let thread = thread::spawn(|| {
BLOCKS_AS_IT_GOES.with(|_| ());
crate::block_on(async { drop(SharedRuntime::current().unwrap()) });
});
assert!(thread.join().is_ok());
}
#[test]
#[timeout(15000)]
fn a_block_on_taking_the_seat_as_a_thread_ends_does_not_abort() {
struct DrivesAsItGoes(Option<SharedRuntime>);
impl Drop for DrivesAsItGoes {
fn drop(&mut self) {
crate::block_on(async { drop(SharedRuntime::current().unwrap()) });
}
}
thread_local! {
static DRIVES_AS_IT_GOES: RefCell<DrivesAsItGoes> =
const { RefCell::new(DrivesAsItGoes(None)) };
}
let thread = thread::spawn(|| {
crate::block_on(async {
DRIVES_AS_IT_GOES
.with(|drives| drives.borrow_mut().0 = Some(SharedRuntime::current().unwrap()));
});
});
assert!(thread.join().is_ok());
}
#[test]
#[timeout(15000)]
fn a_runtime_made_by_new_starts_no_helper() {
let runtime = SharedRuntime::new().unwrap();
let ran = Arc::new(AtomicBool::new(false));
let task = {
let ran = ran.clone();
runtime.spawn("a task that names the thread it runs on", async move {
ran.store(true, Ordering::Release);
thread::current().id()
})
};
let (source, _peer) = pair();
let _registration = runtime.register(source).unwrap();
thread::sleep(Duration::from_millis(200));
assert!(!ran.load(Ordering::Acquire));
assert_eq!(runtime.block_on(task).unwrap(), thread::current().id());
assert!(ran.load(Ordering::Acquire));
}
#[test]
#[timeout(15000)]
fn current_inside_a_local_block_on_is_run_by_a_helper() {
let local = LocalRuntime::new().unwrap();
let name = local
.block_on(async {
SharedRuntime::current()
.unwrap()
.spawn("a task that names the thread it runs on", async {
thread::current().name().map(str::to_owned)
})
.await
})
.unwrap();
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[test]
#[timeout(15000)]
fn a_spawn_outside_any_block_on_goes_to_the_process_runtime() {
let process = SharedRuntime::current().unwrap();
let task = crate::spawn(async {
let runtime = SharedRuntime::current().unwrap();
(
Arc::as_ptr(runtime.inner()) as usize,
thread::current().name().map(str::to_owned),
)
});
let (built_on, name) = block_on(task).unwrap();
assert_eq!(built_on, Arc::as_ptr(process.inner()) as usize);
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[test]
#[timeout(15000)]
fn a_spawn_inside_block_on_goes_to_the_threads_own_runtime() {
let (ran_on, own) = crate::block_on(async {
let ran_on = crate::spawn(async { thread::current().id() })
.await
.unwrap();
(ran_on, SharedRuntime::current().unwrap())
});
assert_eq!(ran_on, thread::current().id());
assert!(!own.helper_running());
}
#[test]
#[timeout(15000)]
fn a_spawn_inside_block_on_as_a_thread_ends_runs_on_the_threads_own_runtime() {
struct SpawnsAsItGoes(Option<mpsc::Sender<Result<io::Result<u32>, String>>>);
impl Drop for SpawnsAsItGoes {
fn drop(&mut self) {
let Some(spawned) = self.0.take() else {
return;
};
let outcome = std::panic::catch_unwind(|| {
crate::block_on(async { crate::spawn(async { 7 }).await })
})
.map_err(|panic| format!("{panic:?}"));
let _ = spawned.send(outcome);
}
}
thread_local! {
static SPAWNS_AS_IT_GOES: RefCell<SpawnsAsItGoes> =
const { RefCell::new(SpawnsAsItGoes(None)) };
}
let (sender, spawned) = mpsc::channel();
let thread = thread::spawn(move || {
SPAWNS_AS_IT_GOES.with(|spawns| spawns.borrow_mut().0 = Some(sender));
SharedRuntime::current().unwrap().block_on(async {});
});
assert!(thread.join().is_ok());
let outcome = spawned.recv().unwrap().expect("no panic");
assert_eq!(outcome.map_err(|error| error.to_string()), Ok(7));
}
#[test]
#[timeout(15000)]
fn a_thread_leaving_a_seat_is_off_the_record() {
let process = SharedRuntime::current().unwrap();
let own = crate::block_on(async {
let own = SharedRuntime::current().unwrap();
yield_now().await;
let driven = Shared::driven().expect("the runtime this thread is in the seat of");
assert!(Arc::ptr_eq(&driven, own.inner()));
own
});
assert!(Shared::driven().is_none());
assert!(Arc::ptr_eq(
SharedRuntime::current().unwrap().inner(),
process.inner()
));
let task =
crate::spawn(async { Arc::as_ptr(SharedRuntime::current().unwrap().inner()) as usize });
assert_eq!(
block_on(task).unwrap(),
Arc::as_ptr(process.inner()) as usize
);
drop(own);
}
#[test]
#[timeout(15000)]
fn a_spawn_from_a_task_the_helper_runs_goes_on_its_runtime() {
let runtime = runtime();
let expected = Arc::as_ptr(runtime.inner()) as usize;
let task = runtime.spawn("a task that spawns another", async {
crate::spawn(async { Arc::as_ptr(SharedRuntime::current().unwrap().inner()) as usize })
.await
.unwrap()
});
assert_eq!(block_on(task).unwrap(), expected);
}
#[test]
#[timeout(15000)]
fn a_spawn_inside_a_block_on_on_a_runtime_made_by_new_goes_on_that_runtime() {
let runtime = SharedRuntime::new().unwrap();
let process = SharedRuntime::current().unwrap();
let (spawned, current, from_current) = runtime.block_on(async {
let spawned = crate::spawn(async { thread::current().id() })
.await
.unwrap();
let current = SharedRuntime::current().unwrap();
let from_current = current
.spawn("a task that names the thread it runs on", async {
thread::current().name().map(str::to_owned)
})
.await
.unwrap();
(spawned, current, from_current)
});
assert_eq!(spawned, thread::current().id());
assert!(Arc::ptr_eq(current.inner(), process.inner()));
assert_eq!(from_current.as_deref(), Some("zruntime"));
}
#[test]
#[timeout(15000)]
fn a_spawn_inside_a_local_block_on_goes_to_the_process_runtime() {
let local = LocalRuntime::new().unwrap();
let process = SharedRuntime::current().unwrap();
let (built_on, name) = local
.block_on(async {
crate::spawn(async {
let runtime = SharedRuntime::current().unwrap();
(
Arc::as_ptr(runtime.inner()) as usize,
thread::current().name().map(str::to_owned),
)
})
.await
})
.unwrap();
assert_eq!(built_on, Arc::as_ptr(process.inner()) as usize);
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_spawn_inside_a_block_on_that_holds_no_seat_goes_on_its_runtime() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let expected = Arc::as_ptr(runtime.inner()) as usize;
let built_on = runtime.block_on(async {
crate::spawn(async { Arc::as_ptr(SharedRuntime::current().unwrap().inner()) as usize })
.await
.unwrap()
});
assert_eq!(built_on, expected);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn work_built_in_a_block_on_on_another_runtime_goes_on_that_runtime() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let (built_on, answer) = runtime.block_on(async {
let built_on = SharedRuntime::current().unwrap();
let answer = built_on.spawn("an answer", async { 7 }).await.unwrap();
(built_on, answer)
});
assert!(Arc::ptr_eq(built_on.inner(), runtime.inner()));
assert_eq!(answer, 7);
}
#[test]
#[timeout(15000)]
fn work_handed_to_the_own_runtime_inside_another_runtimes_block_on_goes_to_a_helper() {
let own = crate::block_on(async { SharedRuntime::current().unwrap() });
let runtime = runtime();
let name = runtime
.block_on(async {
own.spawn("a task that names the thread it runs on", async {
thread::current().name().map(str::to_owned)
})
.await
})
.unwrap();
assert_eq!(name.as_deref(), Some("zruntime"));
}
#[test]
#[timeout(15000)]
fn a_core_block_on_inside_a_helper_block_on_panics_with_or_without_the_seat() {
let nested = || {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
LocalRuntime::new().unwrap().block_on(async {});
}))
.is_err()
};
assert!(crate::block_on(async { nested() }));
let runtime = runtime();
assert!(drive(&runtime, async { nested() }));
assert_eq!(LocalRuntime::new().unwrap().block_on(async { 7 }), 7);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_nested_in_a_waiting_block_on_leaves_its_runtime_to_the_helper() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let answer = runtime.block_on(async {
crate::block_on(async {
assert!(the_helper_parked_or_nobody_waiting(&runtime));
runtime.spawn("an answer", async { 7 }).await.unwrap()
})
});
assert_eq!(answer, 7);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_on_another_runtime_nested_in_a_waiting_one_leaves_its_runtime_to_the_helper() {
let other = runtime();
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let answer = runtime.block_on(async {
other.block_on(async {
assert!(the_helper_parked_or_nobody_waiting(&runtime));
runtime.spawn("an answer", async { 7 }).await.unwrap()
})
});
assert_eq!(answer, 7);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_nested_in_a_waiting_free_block_on_leaves_the_own_runtime_to_the_helper() {
let other = runtime();
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let answer = drive(&runtime, async {
other.block_on(async {
assert!(the_helper_parked_or_nobody_waiting(&runtime));
runtime.spawn("an answer", async { 7 }).await.unwrap()
})
});
assert_eq!(answer, 7);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_block_on_nested_in_a_waiting_block_on_on_the_same_runtime_takes_the_seat() {
let runtime = runtime();
let _held = held_by_the_helper(&runtime);
let (parked, watcher) = the_parked_helper_announced(&runtime);
let ran_on = runtime
.block_on(async {
runtime.block_on(async {
parked.await;
runtime
.spawn("a task that names the thread it runs on", async {
thread::current().id()
})
.await
})
})
.unwrap();
assert!(watcher.join().unwrap());
assert_eq!(ran_on, thread::current().id());
}
fn runtime() -> SharedRuntime {
SharedRuntime::from_inner(Core::with_seat().unwrap())
}
fn drive<F>(runtime: &SharedRuntime, future: F) -> F::Output
where
F: Future,
{
let _own = driver::own_for_the_call(runtime.inner());
let inner = runtime.inner().clone();
driver::block_on(driver::Target::Own, &move || Some(inner.clone()), future)
}
#[cfg(feature = "event")]
fn held_by_the_helper(runtime: &SharedRuntime) -> Task<(), Shared> {
let announce = Arc::new(Event::new());
let announced = announce.listen();
let task = runtime.spawn("a task that never finishes", async move {
announce.notify(1);
pending::<()>().await
});
block_on(announced);
task
}
#[cfg(feature = "event")]
fn the_parked_helper_announced(
runtime: &SharedRuntime,
) -> (EventListener, thread::JoinHandle<bool>) {
let announce = Arc::new(Event::new());
let announced = announce.listen();
let runtime = runtime.clone();
let watcher = thread::spawn(move || {
let parked = within_a_second(|| runtime.helper_parked());
announce.notify(1);
parked
});
(announced, watcher)
}
fn resolve_on_the_second_ask(runtime: &SharedRuntime) -> (Resolver, impl Drop) {
let inner = runtime.inner().clone();
let looked = AtomicBool::new(false);
let own = driver::own_for_the_call(runtime.inner());
let resolve = Box::new(move || {
if looked.swap(true, Ordering::AcqRel) {
return Some(inner.clone());
}
None
});
(resolve, own)
}
type Resolver = Box<dyn Fn() -> Option<Arc<Core<Shared>>>>;
#[cfg(feature = "event")]
fn the_helper_parked_or_nobody_waiting(runtime: &SharedRuntime) -> bool {
within_a_second(|| runtime.helper_parked() || waiting(runtime) == 0)
}
#[cfg(feature = "event")]
fn waiting(runtime: &SharedRuntime) -> usize {
driver::seat(runtime.inner()).waiting()
}
fn helper_gone(runtime: &SharedRuntime) -> bool {
within_a_second(|| !runtime.helper_running())
}
fn within_a_second(condition: impl Fn() -> bool) -> bool {
let deadline = Instant::now() + Duration::from_secs(1);
loop {
if condition() {
return true;
}
if Instant::now() >= deadline {
return false;
}
thread::sleep(Duration::from_millis(10));
}
}
#[derive(Default)]
struct Woken(AtomicUsize);
impl Woken {
fn count(&self) -> usize {
self.0.load(Ordering::Acquire)
}
}
impl Wake for Woken {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::AcqRel);
}
}