#![allow(clippy::waker_clone_wake)]
use std::future::poll_fn;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::{Barrier, Mutex};
use std::task::{Context, Poll, Waker};
use std::thread;
use super::{Arc, Future, Location, Notify, Ordering, RunnableQueue, Runtime, io};
use crate::rt::task::{self, Runnable, Task};
#[derive(Debug)]
struct NoopNotify;
impl Notify for NoopNotify {
fn notify(&self) -> io::Result<()> {
Ok(())
}
}
#[derive(Debug, Default, Clone)]
struct Counter(Arc<AtomicUsize>);
impl Counter {
fn get(&self) -> usize {
self.0.load(Ordering::SeqCst)
}
fn inc(&self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
fn token(&self) -> Token {
Token(self.clone())
}
}
#[derive(Debug)]
struct Token(Counter);
impl Drop for Token {
fn drop(&mut self) {
self.0.inc();
}
}
struct OnDrop<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
self.0.take().unwrap()();
}
}
fn pending_with_drop(f: impl FnOnce()) -> impl Future<Output = ()> {
let hook = OnDrop(Some(f));
async move {
let _hook = hook;
std::future::pending::<()>().await;
}
}
#[derive(Default)]
struct CountWaker(AtomicUsize);
impl std::task::Wake for CountWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum Res {
Pending,
Ready,
Panic,
}
#[derive(Default, Clone)]
struct Probe {
polls: Counter,
drops: Counter,
outputs: Counter,
waker: Arc<Mutex<Option<Waker>>>,
}
impl Probe {
fn future(&self, res: Res) -> Fut {
Fut {
p: self.clone(),
res,
gate: None,
_drop: self.drops.token(),
}
}
fn gated(&self, res: Res, gate: &Arc<Barrier>) -> Fut {
Fut {
gate: Some(gate.clone()),
..self.future(res)
}
}
fn waker(&self) -> Waker {
self.waker.lock().unwrap().take().unwrap()
}
fn waker_clone(&self) -> Waker {
self.waker.lock().unwrap().clone().unwrap()
}
#[track_caller]
fn check(&self, polls: usize, drops: usize, outputs: usize) {
assert_eq!(
(self.polls.get(), self.drops.get(), self.outputs.get()),
(polls, drops, outputs),
"(polls, drops, outputs)"
);
}
}
struct Fut {
p: Probe,
res: Res,
gate: Option<Arc<Barrier>>,
_drop: Token,
}
impl Future for Fut {
type Output = Token;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Token> {
let this = self.get_mut();
*this.p.waker.lock().unwrap() = Some(cx.waker().clone());
this.p.polls.inc();
if let Some(gate) = this.gate.take() {
gate.wait();
gate.wait();
}
match this.res {
Res::Pending => Poll::Pending,
Res::Ready => Poll::Ready(this.p.outputs.token()),
Res::Panic => panic!("task panic"),
}
}
}
fn new_rt() -> Runtime {
Runtime::new(Box::new(NoopNotify))
}
fn spawn<F: Future + 'static>(rt: &Runtime, f: F) -> Task<F::Output> {
unsafe { task::spawn(f, &rt.queue, Location::caller()) }
}
fn locals(rt: &Runtime) -> Vec<Runnable> {
std::iter::from_fn(|| rt.queue.pop_local()).collect()
}
#[track_caller]
fn one_local(rt: &Runtime) -> Runnable {
flush_references(rt);
let mut q = locals(rt);
assert_eq!(q.len(), 1, "local queue");
q.pop().unwrap()
}
#[track_caller]
fn one_remote(rt: &Runtime) -> Option<Runnable> {
let wakes: Vec<_> = std::iter::from_fn(|| rt.queue.wake_queue.pop()).collect();
assert!(!wakes.is_empty(), "wake queue");
let mut runnable = None;
for task in wakes
.into_iter()
.filter_map(|wake| wake.into_runnable(&rt.queue))
{
assert!(runnable.replace(task).is_none(), "duplicate run reference");
}
runnable
}
#[track_caller]
fn flush_references(rt: &Runtime) {
while let Some(wake) = rt.queue.wake_queue.pop() {
assert!(
wake.into_runnable(&rt.queue).is_none(),
"unexpected remote wake"
);
}
}
#[track_caller]
fn assert_idle(rt: &Runtime) {
flush_references(rt);
assert!(rt.queue.local_queue.is_empty(), "local queue");
assert!(rt.queue.wake_queue.is_empty(), "wake queue");
}
#[track_caller]
fn assert_freed(rt: &Runtime) {
assert_idle(rt);
assert_eq!(rt.queue.task_count(), 0, "task allocations retained");
}
fn join<T>(task: &mut Task<T>) -> Poll<Option<T>> {
task.poll_join(&Context::from_waker(Waker::noop()))
}
fn on_thread<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
thread::spawn(f).join().unwrap()
}
#[test]
fn runnable_is_owner_local() {
trait AmbiguousIfSend<A> {
fn check() {}
}
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
struct IsSend;
impl<T: ?Sized + Send> AmbiguousIfSend<IsSend> for T {}
let _ = <Runnable as AmbiguousIfSend<_>>::check;
}
#[test]
fn drop_and_detach() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
drop(one_local(&rt));
p.check(0, 1, 0);
assert!(task.is_finished());
drop(task);
assert_freed(&rt);
}
#[test]
fn detach_and_drop() {
let rt = new_rt();
let p = Probe::default();
drop(spawn(&rt, p.future(Res::Ready)));
p.check(0, 0, 0);
drop(one_local(&rt));
p.check(0, 1, 0);
assert_freed(&rt);
}
#[test]
fn detach_and_run() {
let rt = new_rt();
let p = Probe::default();
drop(spawn(&rt, p.future(Res::Ready)));
assert!(!one_local(&rt).run());
drop(p.waker());
p.check(1, 1, 1);
assert_freed(&rt);
}
#[test]
fn run_and_detach() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
one_local(&rt).run();
drop(p.waker());
p.check(1, 1, 0);
drop(task);
p.check(1, 1, 1);
assert_freed(&rt);
}
#[test]
fn cancel_and_run() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
task.cancel();
p.check(0, 0, 0);
one_local(&rt).run();
p.check(0, 1, 0);
assert_freed(&rt);
}
#[test]
fn run_and_cancel() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
one_local(&rt).run();
drop(p.waker());
task.cancel();
p.check(1, 1, 1);
assert_freed(&rt);
}
#[test]
fn drop_finished() {
for cancel in [false, true] {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
one_local(&rt).run();
p.check(1, 1, 0);
if cancel {
task.cancel();
} else {
drop(task);
}
p.check(1, 1, 1);
drop(p.waker());
assert_freed(&rt);
}
}
#[test]
fn drop_and_complete() {
for cancel in [false, true] {
let rt = new_rt();
let p = Probe::default();
let fut = p.future(Res::Ready);
let task = spawn(&rt, async move {
yield_now().await;
fut.await
});
assert!(one_local(&rt).run());
let r = one_local(&rt);
if cancel {
task.cancel();
} else {
drop(task);
}
r.run();
let ran = usize::from(!cancel);
p.check(ran, 1, ran);
if !cancel {
drop(p.waker());
}
assert_freed(&rt);
}
}
#[test]
fn spawn_detached() {
for remote in [false, true] {
let rt = new_rt();
let p = Probe::default();
let fut = p.future(Res::Ready);
let q = rt.queue.clone();
let spawn =
move || unsafe { task::spawn_detached::<_, false>(fut, &q, Location::caller()) };
let r = if remote {
on_thread(spawn);
one_remote(&rt).unwrap()
} else {
spawn();
one_local(&rt)
};
assert!(!r.run());
p.check(1, 1, 1);
drop(p.waker());
assert_freed(&rt);
}
let rt = new_rt();
let p = Probe::default();
unsafe { task::spawn_detached::<_, true>(p.future(Res::Ready), &rt.queue, Location::caller()) };
drop(one_local(&rt));
p.check(0, 1, 0);
assert_freed(&rt);
}
#[test]
#[cfg_attr(miri, ignore = "spawns a process")]
fn spawn_notify_panic_aborts() {
const CHILD: &str = "NTEX_RT_NOTIFY_PANIC_CHILD";
#[derive(Debug)]
struct PanicNotify;
impl Notify for PanicNotify {
fn notify(&self) -> io::Result<()> {
panic!("spawn notification");
}
}
if let Ok(case) = std::env::var(CHILD) {
let rt = Runtime::new(Box::new(PanicNotify));
if case == "remote" {
let handle = rt.handle();
on_thread(move || handle.spawn_detached(async {}));
} else {
super::CURRENT_RUNTIME.set(&rt, || rt.spawn_detached(async {}));
}
return;
}
for case in ["local", "remote"] {
let out = std::process::Command::new(std::env::current_exe().unwrap())
.args(["rt::task_tests::spawn_notify_panic_aborts", "--exact"])
.args(["--nocapture", "--test-threads=1"])
.env(CHILD, case)
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success(), "{case}: {stderr}");
assert_ne!(out.status.code(), Some(101), "{case}: {stderr}");
assert!(stderr.contains("spawn notification"), "{case}: {stderr}");
}
}
#[test]
fn finish_unshared() {
struct PanicOnDrop;
struct PanicOnWake;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("output drop");
}
}
impl std::task::Wake for PanicOnWake {
fn wake(self: Arc<Self>) {
panic!("awaiter wake");
}
}
let rt = new_rt();
let out = Counter::default();
let tok = out.token();
let mut tok = Some(tok);
let fut = poll_fn(move |_| Poll::Ready(tok.take().unwrap()));
unsafe { task::spawn_detached::<_, true>(fut, &rt.queue, Location::caller()) };
assert!(!one_local(&rt).run());
assert_eq!(out.get(), 1);
assert_freed(&rt);
let rt = new_rt();
let slot = Arc::new(Mutex::new(None::<Waker>));
let slot2 = slot.clone();
let mut tok = Some(out.token());
let mut polled = false;
let mut task = spawn(
&rt,
poll_fn(move |cx| {
if polled {
Poll::Ready(tok.take().unwrap())
} else {
polled = true;
*slot2.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
}
}),
);
one_local(&rt).run();
let awaiter = Arc::new(CountWaker::default());
let w = Waker::from(awaiter.clone());
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
drop(task);
assert_eq!(Arc::strong_count(&awaiter), 2);
slot.lock().unwrap().take().unwrap().wake();
one_local(&rt).run();
assert_eq!(awaiter.0.load(Ordering::SeqCst), 0);
assert_eq!(out.get(), 2);
assert_freed(&rt);
drop(w);
let rt = new_rt();
let mut tok = Some(out.token());
let mut task = spawn(&rt, poll_fn(move |_| Poll::Ready(tok.take().unwrap())));
let w = Waker::from(Arc::new(PanicOnWake));
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
drop(task);
assert!(!one_local(&rt).run());
assert_eq!(out.get(), 3);
assert_freed(&rt);
let rt = new_rt();
let fut = poll_fn(|_| Poll::Ready(PanicOnDrop));
unsafe { task::spawn_detached::<_, true>(fut, &rt.queue, Location::caller()) };
let r = one_local(&rt);
assert!(catch_unwind(AssertUnwindSafe(|| r.run())).is_err());
assert_freed(&rt);
}
#[test]
fn untouched_completion_reclaims_without_fifo_service() {
let rt = new_rt();
unsafe { task::spawn_detached::<_, true>(async {}, &rt.queue, Location::caller()) };
one_local(&rt).run();
assert_eq!(rt.queue.task_count(), 0);
assert!(rt.queue.wake_queue.is_empty());
assert_freed(&rt);
let task = spawn(&rt, async {});
one_local(&rt).run();
drop(task);
let slot = Arc::new(Mutex::new(None::<Waker>));
let slot2 = slot.clone();
let fut = poll_fn(move |cx| {
*slot2.lock().unwrap() = Some(cx.waker().clone());
Poll::Ready(())
});
unsafe { task::spawn_detached::<_, true>(fut, &rt.queue, Location::caller()) };
one_local(&rt).run();
drop(slot.lock().unwrap().take());
let slot2 = slot.clone();
let mut polled = false;
let fut = poll_fn(move |cx| {
if polled {
return Poll::Ready(());
}
polled = true;
*slot2.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
});
unsafe { task::spawn_detached::<_, true>(fut, &rt.queue, Location::caller()) };
one_local(&rt).run();
let w = slot.lock().unwrap().take().unwrap();
let (w, cloned) = on_thread(move || {
let cloned = w.clone();
(w, cloned)
});
w.wake();
one_local(&rt).run();
drop(cloned);
assert_freed(&rt);
}
#[test]
fn detached_awaiter() {
for cancel in [false, true] {
let rt = new_rt();
let p = Probe::default();
let fut = p.future(Res::Ready);
let mut task = spawn(&rt, async move {
yield_now().await;
fut.await
});
assert!(one_local(&rt).run());
let r = one_local(&rt);
let awaiter = Arc::new(CountWaker::default());
let w = Waker::from(awaiter.clone());
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
drop(w);
assert_eq!(Arc::strong_count(&awaiter), 2);
if cancel {
task.cancel();
} else {
drop(task);
}
assert_eq!(Arc::strong_count(&awaiter), 1);
r.run();
let ran = usize::from(!cancel);
p.check(ran, 1, ran);
if !cancel {
drop(p.waker());
}
assert_freed(&rt);
assert_eq!(awaiter.0.load(Ordering::SeqCst), 0);
}
}
#[test]
fn cancel_pending() {
for cancel_on_drop in [false, true] {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
task.set_cancel_on_drop(cancel_on_drop);
one_local(&rt).run();
if cancel_on_drop {
drop(task);
} else {
task.cancel();
}
p.check(1, 0, 0);
one_local(&rt).run();
p.check(1, 1, 0);
let w = p.waker();
w.wake_by_ref();
w.clone().wake();
assert_idle(&rt);
drop(w);
assert_freed(&rt);
}
}
#[test]
fn schedule() {
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
assert_idle(&rt);
let w = p.waker();
w.wake_by_ref();
w.wake_by_ref();
w.clone().wake();
let r = one_local(&rt);
w.wake_by_ref();
assert_idle(&rt);
r.run();
p.check(2, 0, 0);
drop(w);
for _ in 0..3 {
p.waker().wake();
one_local(&rt).run();
}
p.check(5, 0, 0);
assert_eq!(join(&mut task).map(|v| v.is_some()), Poll::Pending);
drop(task);
assert_idle(&rt);
drop(p.waker());
one_local(&rt).run();
p.check(5, 1, 0);
assert_freed(&rt);
}
#[test]
fn self_wake_during_run() {
let rt = new_rt();
let polls = Counter::default();
let polls2 = polls.clone();
drop(spawn(
&rt,
std::future::poll_fn(move |cx| {
polls2.inc();
if polls2.get() < 3 {
cx.waker().wake_by_ref();
cx.waker().clone().wake();
Poll::Pending
} else {
Poll::Ready(())
}
}),
));
assert!(one_local(&rt).run());
assert!(one_local(&rt).run());
assert!(!one_local(&rt).run());
assert_eq!(polls.get(), 3);
assert_freed(&rt);
}
#[test]
fn join_twice() {
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Ready));
assert!(join(&mut task).is_pending());
one_local(&rt).run();
assert!(matches!(join(&mut task), Poll::Ready(Some(_))));
p.check(1, 1, 1);
assert!(matches!(join(&mut task), Poll::Ready(None)));
drop(task);
drop(p.waker());
assert_freed(&rt);
}
#[test]
fn join_waker_replaced() {
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Pending));
let a = Arc::new(CountWaker::default());
let b = Arc::new(CountWaker::default());
let wa = Waker::from(a.clone());
assert!(task.poll_join(&Context::from_waker(&wa)).is_pending());
drop(wa);
one_local(&rt).run();
let wb = Waker::from(b.clone());
assert!(task.poll_join(&Context::from_waker(&wb)).is_pending());
drop(wb);
assert_eq!(Arc::strong_count(&a), 1);
assert_eq!(Arc::strong_count(&b), 2);
task.cancel();
assert_eq!(Arc::strong_count(&b), 1);
one_local(&rt).run();
assert_eq!(a.0.load(Ordering::SeqCst), 0);
assert_eq!(b.0.load(Ordering::SeqCst), 0);
drop(p.waker());
assert_freed(&rt);
}
#[test]
fn join_by_task_waker() {
let rt = new_rt();
let p = Probe::default();
let mut inner = Some(spawn(&rt, p.future(Res::Ready)));
let out = Arc::new(Mutex::new(None));
let out2 = out.clone();
drop(spawn(
&rt,
std::future::poll_fn(move |cx| match inner.as_mut().unwrap().poll_join(cx) {
Poll::Ready(v) => {
*out2.lock().unwrap() = Some(v.is_some());
inner = None;
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}),
));
let mut q = locals(&rt);
assert_eq!(q.len(), 2);
let outer = q.pop().unwrap();
let first = q.pop().unwrap();
outer.run();
first.run();
one_local(&rt).run();
assert_eq!(*out.lock().unwrap(), Some(true));
p.check(1, 1, 1);
drop(p.waker());
assert_freed(&rt);
}
#[test]
fn wake_completed() {
let rt = new_rt();
for remote in [false, true] {
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
one_local(&rt).run();
let w = p.waker();
drop(task);
p.check(1, 1, 1);
if remote {
on_thread(move || {
w.wake_by_ref();
w.clone().wake();
w.wake();
});
} else {
w.wake_by_ref();
w.clone().wake();
assert_idle(&rt);
w.wake();
}
p.check(1, 1, 1);
assert_freed(&rt);
}
}
#[test]
fn wake_dropped() {
let rt = new_rt();
for remote in [false, true] {
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
task.cancel();
one_local(&rt).run();
p.check(1, 1, 0);
let w = p.waker();
if remote {
on_thread(move || w.wake());
} else {
w.wake();
}
p.check(1, 1, 0);
assert_freed(&rt);
}
}
#[test]
fn release_after_shutdown() {
for wake in [false, true] {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Ready));
one_local(&rt).run();
let w = p.waker();
let w2 = w.clone();
drop(task);
drop(rt);
assert!(queue.upgrade().is_none());
on_thread(move || {
if wake {
w.wake_by_ref();
w2.wake();
}
drop(w);
});
p.check(1, 1, 1);
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
fn release_output_after_shutdown() {
struct Output {
waker: Option<Waker>,
panics: bool,
_drop: Token,
}
impl Drop for Output {
fn drop(&mut self) {
drop(self.waker.take());
assert!(!self.panics, "output drop");
}
}
for cancel in [false, true] {
for panics in [false, true] {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let drops = Counter::default();
let mut token = Some(drops.token());
let task = spawn(
&rt,
poll_fn(move |cx| {
Poll::Ready(Output {
waker: Some(cx.waker().clone()),
panics,
_drop: token.take().unwrap(),
})
}),
);
one_local(&rt).run();
drop(rt);
assert!(queue.upgrade().is_none());
let panicked = catch_unwind(AssertUnwindSafe(|| {
if cancel {
task.cancel();
} else {
drop(task);
}
}))
.is_err();
assert_eq!(panicked, panics);
assert_eq!(drops.get(), 1);
assert!(queue.upgrade().is_none(), "task alive");
}
}
}
#[test]
fn queued_then_finished() {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let slot = Arc::new(Mutex::new(None::<Waker>));
let slot2 = slot.clone();
let mut polled = false;
let task = spawn(
&rt,
poll_fn(move |cx| {
if polled {
Poll::Ready(())
} else {
polled = true;
*slot2.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
}
}),
);
one_local(&rt).run();
let w = slot.lock().unwrap().take().unwrap();
let w2 = w.clone();
on_thread(move || w2.wake());
w.wake_by_ref();
one_local(&rt).run();
assert!(task.is_finished());
drop(task);
drop(rt);
assert!(queue.upgrade().is_none());
on_thread(move || drop(w));
assert!(queue.upgrade().is_none(), "task alive");
}
#[test]
fn shutdown_drops_pending() {
for remote_spawn in [false, true] {
for detach in [false, true] {
for release in 0..4 {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let p = Probe::default();
let fut = p.future(Res::Pending);
let (task, r) = if remote_spawn {
let handle = rt.handle();
on_thread(move || drop(handle.spawn(fut)));
(None, one_remote(&rt).unwrap())
} else {
(Some(spawn(&rt, fut)), one_local(&rt))
};
assert!(!r.run());
let w = p.waker();
let mut task = if detach {
drop(task);
None
} else {
task
};
drop(rt);
p.check(1, 1, 0);
if let Some(task) = &mut task {
assert!(task.is_finished());
assert!(matches!(join(task), Poll::Ready(None)));
}
drop(task);
assert!(queue.upgrade().is_none());
match release {
0 => drop(w),
1 => w.wake(),
2 => on_thread(move || drop(w)),
_ => on_thread(move || w.wake()),
}
assert!(queue.upgrade().is_none(), "task alive");
}
}
}
}
#[test]
fn shutdown_drop_wakes_and_spawns() {
for panics in [false, true] {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let (p1, p2) = (Probe::default(), Probe::default());
let t1 = spawn(&rt, p1.future(Res::Pending));
one_local(&rt).run();
let w1 = p1.waker();
let q = rt.queue.clone();
let fut2 = p2.future(Res::Pending);
let t0 = spawn(
&rt,
pending_with_drop(move || {
w1.wake();
unsafe { task::spawn_detached::<_, false>(fut2, &q, Location::caller()) };
assert!(!panics, "reentrant cleanup panic");
}),
);
one_local(&rt).run();
assert_eq!(catch_unwind(AssertUnwindSafe(|| drop(rt))).is_err(), panics);
p1.check(1, 1, 0);
p2.check(0, 1, 0);
assert!(t0.is_finished() && t1.is_finished());
drop((t0, t1));
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
fn shutdown_continues_after_panics() {
for path in 0..3 {
let rt = new_rt();
let handle = rt.handle();
let queue = Arc::downgrade(&rt.queue);
let owner = thread::current().id();
let drops = Counter::default();
let panics = Counter::default();
let mut tasks = Vec::new();
for panics_on_drop in [true, false, false] {
let (drops, panics) = (drops.clone(), panics.clone());
let fut = pending_with_drop(move || {
assert_eq!(thread::current().id(), owner);
drops.inc();
if panics_on_drop {
std::panic::panic_any(panics.0.fetch_add(1, Ordering::SeqCst));
}
});
let task = if path == 2 {
let handle = rt.handle();
on_thread(move || drop(handle.spawn(fut)));
None
} else {
let task = spawn(&rt, fut);
if path == 1 {
one_local(&rt).run();
}
Some(task)
};
tasks.push(task);
}
let payload = catch_unwind(AssertUnwindSafe(|| drop(rt))).unwrap_err();
assert_eq!(*payload.downcast::<usize>().unwrap(), 0);
assert_eq!(drops.get(), 3);
assert_eq!(panics.get(), 1);
assert!(handle.queue.is_closed());
for mut task in tasks.into_iter().flatten() {
assert!(task.is_finished());
assert!(matches!(join(&mut task), Poll::Ready(None)));
}
for remote in [false, true] {
let p = Probe::default();
let (h, fut) = (handle.clone(), p.future(Res::Ready));
let submit = move || drop(h.spawn(fut));
if remote {
on_thread(submit);
} else {
submit();
}
p.check(0, 1, 0);
}
drop(handle);
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
fn shutdown_completion_panics() {
struct PanicWake {
drops: bool,
calls: Counter,
}
impl std::task::Wake for PanicWake {
fn wake(self: Arc<Self>) {
assert!(!self.drops, "detached awaiter was woken");
self.calls.inc();
panic!("completion panic");
}
}
impl Drop for PanicWake {
fn drop(&mut self) {
if self.drops {
self.calls.inc();
panic!("completion panic");
}
}
}
{
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let drops = Counter::default();
let calls = Counter::default();
let d = drops.clone();
let owner = thread::current().id();
let local = std::rc::Rc::new(());
let mut task = Some(spawn(
&rt,
pending_with_drop(move || {
assert_eq!(thread::current().id(), owner);
drop(local);
d.inc();
}),
));
let d = drops.clone();
let mut other = spawn(&rt, pending_with_drop(move || d.inc()));
let w = Waker::from(Arc::new(PanicWake {
drops: false,
calls: calls.clone(),
}));
assert!(
task.as_mut()
.unwrap()
.poll_join(&Context::from_waker(&w))
.is_pending()
);
drop(w);
let payload = catch_unwind(AssertUnwindSafe(|| drop(rt))).unwrap_err();
assert_eq!(
payload.downcast_ref::<&str>().copied(),
Some("completion panic")
);
assert_eq!(drops.get(), 2);
assert_eq!(calls.get(), 1);
assert!(other.is_finished());
assert!(matches!(join(&mut other), Poll::Ready(None)));
if let Some(task) = task.as_mut() {
assert!(task.is_finished());
assert!(matches!(join(task), Poll::Ready(None)));
}
drop((task, other));
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
#[cfg_attr(miri, ignore = "spawns a process")]
fn shutdown_second_panic_aborts() {
const CHILD: &str = "NTEX_RT_SECOND_PANIC_CHILD";
struct PanicWake(bool);
impl std::task::Wake for PanicWake {
fn wake(self: Arc<Self>) {
panic!("second panic");
}
}
impl Drop for PanicWake {
fn drop(&mut self) {
assert!(!self.0, "second panic");
}
}
struct PanicOutput {
_waker: Waker,
}
impl Drop for PanicOutput {
fn drop(&mut self) {
panic!("first panic");
}
}
if let Ok(case) = std::env::var(CHILD) {
let rt = new_rt();
match case.as_str() {
"cleanup" => {
drop(spawn(&rt, pending_with_drop(|| panic!("first panic"))));
drop(spawn(&rt, pending_with_drop(|| panic!("second panic"))));
drop(rt);
}
"unwinding" => {
drop(spawn(&rt, pending_with_drop(|| panic!("second panic"))));
let _rt = rt;
panic!("first panic");
}
"completion-wake" | "completion-drop" => {
let dropped = case == "completion-drop";
let mut task = spawn(&rt, pending_with_drop(|| panic!("first panic")));
let w = Waker::from(Arc::new(PanicWake(dropped)));
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
drop(w);
if dropped {
let _drop = OnDrop(Some(|| drop(task)));
panic!("first panic");
}
drop(rt);
drop(task);
}
"poll-wake" => {
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Panic));
let w = Waker::from(Arc::new(PanicWake(false)));
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
let _ = catch_unwind(AssertUnwindSafe(|| one_local(&rt).run()));
}
"output-drop" => {
let w = Waker::from(Arc::new(PanicWake(true)));
drop(spawn(&rt, async move { PanicOutput { _waker: w } }));
let _ = catch_unwind(AssertUnwindSafe(|| one_local(&rt).run()));
}
_ => unreachable!(),
}
return;
}
for case in [
"cleanup",
"unwinding",
"completion-wake",
"completion-drop",
"poll-wake",
"output-drop",
] {
let out = std::process::Command::new(std::env::current_exe().unwrap())
.args(["rt::task_tests::shutdown_second_panic_aborts", "--exact"])
.args(["--nocapture", "--test-threads=1"])
.env(CHILD, case)
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success(), "{case}: {stderr}");
assert_ne!(out.status.code(), Some(101), "{case}: {stderr}");
assert!(stderr.contains("second panic"), "{case}: {stderr}");
}
}
#[test]
fn shutdown_panic_waits_for_admitted_publication() {
use std::sync::mpsc;
use std::time::{Duration, Instant};
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let drops = Counter::default();
let d = drops.clone();
let mut local = spawn(
&rt,
pending_with_drop(move || {
d.inc();
panic!("first future panic");
}),
);
let (q, d) = (rt.queue.clone(), drops.clone());
on_thread(move || unsafe {
task::spawn_detached::<_, false>(
pending_with_drop(move || d.inc()),
&q,
Location::caller(),
);
});
let entry = rt.queue.wake_queue.pop().unwrap().into_remote();
let (q, (tx, rx)) = (rt.queue.clone(), mpsc::channel());
let th = thread::spawn(move || {
let permit = q.admit(false).unwrap();
tx.send(()).unwrap();
let start = Instant::now();
while !q.is_closed() {
assert!(
start.elapsed() < Duration::from_secs(30),
"admission stayed open"
);
thread::yield_now();
}
permit.schedule(entry);
});
rx.recv_timeout(Duration::from_secs(30)).unwrap();
let payload = catch_unwind(AssertUnwindSafe(|| drop(rt))).unwrap_err();
th.join().unwrap();
assert_eq!(
payload.downcast_ref::<&str>().copied(),
Some("first future panic")
);
assert_eq!(drops.get(), 2);
assert!(local.is_finished());
assert!(matches!(join(&mut local), Poll::Ready(None)));
drop(local);
assert!(queue.upgrade().is_none(), "task alive");
}
#[test]
fn shutdown_with_blocked_notification() {
use std::sync::mpsc;
use std::time::Duration;
#[derive(Debug)]
struct BlockingNotify {
entered: mpsc::Sender<()>,
release: Mutex<mpsc::Receiver<()>>,
interrupted: AtomicBool,
}
impl Notify for BlockingNotify {
fn notify(&self) -> io::Result<()> {
if !self.interrupted.swap(true, Ordering::Relaxed) {
return Err(io::ErrorKind::Interrupted.into());
}
self.entered.send(()).unwrap();
self.release
.lock()
.unwrap()
.recv_timeout(Duration::from_secs(30))
.expect("shutdown waited for a notifier");
Ok(())
}
}
struct OwnerDrop {
owner: thread::ThreadId,
_local: std::rc::Rc<()>,
}
impl Drop for OwnerDrop {
fn drop(&mut self) {
assert_eq!(thread::current().id(), self.owner);
}
}
for path in 0..3 {
let (entered, rx) = mpsc::channel();
let (tx, release) = mpsc::channel();
let rt = Runtime::new(Box::new(BlockingNotify {
entered,
release: Mutex::new(release),
interrupted: AtomicBool::new(false),
}));
rt.awake();
let queue = Arc::downgrade(&rt.queue);
let p = Probe::default();
let fut = p.future(Res::Pending);
let th = if path != 0 {
let handle = rt.handle();
thread::spawn(move || {
if path == 1 {
handle.spawn(fut);
} else {
handle.spawn_detached(fut);
}
})
} else {
let owner = OwnerDrop {
owner: thread::current().id(),
_local: std::rc::Rc::new(()),
};
let task = spawn(&rt, async move {
let _owner = owner;
fut.await
});
one_local(&rt).run();
let w = p.waker();
drop(task);
thread::spawn(move || w.wake())
};
rx.recv_timeout(Duration::from_secs(30)).unwrap();
drop(rt);
p.check(usize::from(path == 0), 1, 0);
assert!(queue.upgrade().is_some());
tx.send(()).unwrap();
th.join().unwrap();
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
fn spawn_after_shutdown() {
for remote in [false, true] {
for detached in [false, true] {
let rt = new_rt();
let q = rt.queue.clone();
let queue = Arc::downgrade(&rt.queue);
drop(rt);
let p = Probe::default();
let fut = p.future(Res::Ready);
let task = if remote {
on_thread(move || unsafe {
task::spawn_detached::<_, false>(fut, &q, Location::caller());
});
None
} else if detached {
unsafe { task::spawn_detached::<_, true>(fut, &q, Location::caller()) };
drop(q);
None
} else {
let task = unsafe { task::spawn(fut, &q, Location::caller()) };
drop(q);
Some(task)
};
p.check(0, 1, 0);
if let Some(mut task) = task {
assert!(task.is_finished());
assert!(matches!(join(&mut task), Poll::Ready(None)));
}
assert!(queue.upgrade().is_none(), "task alive");
}
}
}
#[test]
fn remote_wake_variants() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
let w = p.waker();
let w = on_thread(move || {
w.wake_by_ref();
w.clone().wake();
let w2 = w.clone();
w2.wake_by_ref();
w2.wake();
w
});
one_remote(&rt).unwrap().run();
p.check(2, 0, 0);
assert_idle(&rt);
on_thread(move || drop(w.clone()));
assert_idle(&rt);
p.check(2, 0, 0);
let w = p.waker_clone();
p.waker().wake();
on_thread(move || w.wake());
assert!(one_remote(&rt).is_none());
let r = one_local(&rt);
r.run();
p.check(3, 0, 0);
drop(task);
drop(p.waker());
one_local(&rt).run();
p.check(3, 1, 0);
assert_freed(&rt);
}
#[test]
fn local_waker_scheduling_and_orphan_cleanup() {
use crate::LocalWaker;
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
drop(task);
let waker = p.waker();
let mut first = LocalWaker::from_waker(&waker);
let second = LocalWaker::from(waker);
first.clone_from(&second);
first.wake_by_ref();
second.clone().wake();
one_local(&rt).run();
p.check(2, 0, 0);
assert_idle(&rt);
assert!(second.will_wake(&p.waker()));
let remote = first.into_waker();
on_thread(move || remote.wake());
one_remote(&rt).unwrap().run();
p.check(3, 0, 0);
drop(p.waker());
assert_idle(&rt);
drop(second);
one_local(&rt).run();
p.check(3, 1, 0);
assert_freed(&rt);
}
#[test]
fn local_waker_foreign_thread_capture() {
use crate::LocalWaker;
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
let waker = p.waker();
let waker = on_thread(move || {
let borrowed = LocalWaker::from_waker(&waker);
let owned = LocalWaker::from(waker);
borrowed.wake_by_ref();
owned.clone().wake();
drop(borrowed);
owned.into_waker()
});
one_remote(&rt).unwrap().run();
p.check(2, 0, 0);
drop(p.waker());
task.cancel();
one_local(&rt).run();
let queue = Arc::downgrade(&rt.queue);
drop(rt);
on_thread(move || {
let local = LocalWaker::from(waker);
local.clone().wake();
local.wake();
});
p.check(2, 1, 0);
assert!(queue.upgrade().is_none());
}
#[test]
fn local_waker_completion_and_shutdown() {
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::LocalWaker;
for result in [Res::Pending, Res::Ready, Res::Panic] {
let rt = new_rt();
let queue = Arc::downgrade(&rt.queue);
let slot = Rc::new(RefCell::new(None));
let saved = slot.clone();
let ready = Rc::new(Cell::new(false));
let complete = ready.clone();
let drops = Counter::default();
let token = drops.token();
let owner = thread::current().id();
let hook = OnDrop(Some(move || {
assert_eq!(thread::current().id(), owner);
drop(token);
}));
let mut task = spawn(&rt, async move {
let _hook = hook;
poll_fn(move |cx| {
if !complete.get() {
*saved.borrow_mut() = Some(LocalWaker::from_waker(cx.waker()));
return Poll::Pending;
}
assert_ne!(result, Res::Panic, "task panic");
Poll::Ready(Rc::new(42))
})
.await
});
one_local(&rt).run();
let local = slot.borrow_mut().take().unwrap();
if result != Res::Pending {
ready.set(true);
local.wake_by_ref();
let ran = catch_unwind(AssertUnwindSafe(|| one_local(&rt).run()));
assert_eq!(ran.is_err(), result == Res::Panic);
if result == Res::Ready {
let Poll::Ready(Some(value)) = join(&mut task) else {
panic!("missing output");
};
assert_eq!(*value, 42);
}
}
drop(rt);
drop(task);
assert_eq!(drops.get(), 1);
local.wake_by_ref();
local.clone().wake();
let standard = local.clone().into_waker();
LocalWaker::from_waker(&standard).wake();
LocalWaker::from(standard).wake();
let remote = local.into_waker();
on_thread(move || {
remote.clone().wake();
drop(remote);
});
assert!(queue.upgrade().is_none(), "task alive");
}
}
#[test]
fn drop_last_waker() {
let rt = new_rt();
for remote in [false, true] {
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
drop(task);
p.check(1, 0, 0);
let w = p.waker();
if remote {
on_thread(move || drop(w));
assert!(one_remote(&rt).is_none());
} else {
drop(w);
}
one_local(&rt).run();
p.check(1, 1, 0);
assert_freed(&rt);
}
}
#[test]
fn drop_last_task() {
let rt = new_rt();
for cancel in [false, true] {
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
drop(p.waker());
p.check(1, 0, 0);
assert_idle(&rt);
if cancel {
task.cancel();
} else {
drop(task);
}
one_local(&rt).run();
p.check(1, 1, 0);
assert_freed(&rt);
}
}
#[test]
fn remote_wake_merged_while_running() {
let rt = new_rt();
let queue = rt.queue.clone();
let polls = Counter::default();
let polls2 = polls.clone();
let mut task = spawn(
&rt,
poll_fn(move |cx| {
polls2.inc();
if polls2.get() > 1 {
return Poll::Ready(());
}
let w = cx.waker().clone();
on_thread(move || w.wake());
let remote = queue.wake_queue.pop().unwrap();
assert!(remote.into_runnable(&queue).is_none());
Poll::Pending
}),
);
one_local(&rt).run();
one_local(&rt).run();
assert_eq!(polls.get(), 2);
assert_eq!(join(&mut task), Poll::Ready(Some(())));
drop(task);
assert_freed(&rt);
}
#[test]
fn remote_spawn() {
let rt = new_rt();
let p = Probe::default();
let handle = rt.handle();
let fut = p.future(Res::Ready);
on_thread(move || drop(handle.spawn(fut)));
assert!(rt.queue.local_queue.is_empty());
one_remote(&rt).unwrap().run();
p.check(1, 1, 1);
drop(p.waker());
p.check(1, 1, 1);
assert_freed(&rt);
}
#[test]
fn remote_spawn_shutdown() {
let rt = new_rt();
let p = Probe::default();
let handle = rt.handle();
let fut = p.future(Res::Ready);
on_thread(move || drop(handle.spawn(fut)));
let queue = rt.queue.clone();
drop(rt);
p.check(0, 1, 0);
assert_eq!(queue.task_count(), 0);
}
fn during_run<T: Send + 'static>(
r: Runnable,
gate: &Arc<Barrier>,
during: impl FnOnce() -> T + Send + 'static,
) -> (bool, T) {
let gate2 = gate.clone();
let th = thread::spawn(move || {
gate2.wait();
let res = during();
gate2.wait();
res
});
let res = catch_unwind(AssertUnwindSafe(|| r.run()));
(res.is_err(), th.join().unwrap())
}
fn gated_task(rt: &Runtime, p: &Probe, res: Res, gate: &Arc<Barrier>) -> (Task<Token>, Runnable) {
let fut = p.gated(res, gate);
let task = spawn(rt, async move {
yield_now().await;
fut.await
});
assert!(one_local(rt).run());
(task, one_local(rt))
}
async fn yield_now() {
let mut yielded = false;
std::future::poll_fn(|cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await;
}
#[test]
fn wake_during_run() {
for res in [Res::Pending, Res::Panic] {
let rt = new_rt();
let p = Probe::default();
let gate = Arc::new(Barrier::new(2));
let (task, r) = gated_task(&rt, &p, res, &gate);
let p2 = p.clone();
let (panicked, ()) = during_run(r, &gate, move || p2.waker().wake());
assert_eq!(panicked, res == Res::Panic);
assert!(rt.queue.local_queue.is_empty());
let r = one_remote(&rt);
if res == Res::Pending {
p.check(1, 0, 0);
r.unwrap().run();
p.check(2, 0, 0);
drop(task);
drop(p.waker());
one_local(&rt).run();
} else {
assert!(r.is_none());
drop(task);
}
p.check(p.polls.get(), 1, 0);
assert_freed(&rt);
}
}
#[test]
fn panic_and_join() {
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Panic));
let r = one_local(&rt);
assert!(catch_unwind(AssertUnwindSafe(|| r.run())).is_err());
p.check(1, 1, 0);
assert!(task.is_finished());
assert!(matches!(join(&mut task), Poll::Ready(None)));
p.waker().wake();
assert_idle(&rt);
drop(task);
assert_freed(&rt);
}
#[test]
fn panicking_completion_waker() {
struct PanicWake;
impl std::task::Wake for PanicWake {
fn wake(self: Arc<Self>) {
panic!("completion waker panic");
}
}
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Ready));
let w = Waker::from(Arc::new(PanicWake));
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
assert!(catch_unwind(AssertUnwindSafe(|| one_local(&rt).run())).is_err());
assert!(matches!(join(&mut task), Poll::Ready(Some(_))));
p.check(1, 1, 1);
drop(task);
drop(p.waker());
assert_freed(&rt);
}
#[test]
fn panicking_detached_awaiter_drop() {
struct PanicDrop;
impl std::task::Wake for PanicDrop {
fn wake(self: Arc<Self>) {
unreachable!("the awaiter of a dropped join handle is woken");
}
}
impl Drop for PanicDrop {
fn drop(&mut self) {
panic!("completion waker drop panic");
}
}
let rt = new_rt();
let outputs = Counter::default();
let output = outputs.token();
let mut task = spawn(&rt, async move { output });
let w = Waker::from(Arc::new(PanicDrop));
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
drop(w);
assert!(catch_unwind(AssertUnwindSafe(|| drop(task))).is_err());
one_local(&rt).run();
assert_eq!(outputs.get(), 1);
assert_freed(&rt);
}
fn panic_clone_waker() -> Waker {
static PANIC: std::task::RawWakerVTable =
std::task::RawWakerVTable::new(|_| panic!("waker clone panic"), |_| (), |_| (), |_| ());
unsafe { Waker::from_raw(std::task::RawWaker::new(std::ptr::null(), &PANIC)) }
}
#[test]
fn panicking_local_registration() {
let rt = new_rt();
let p = Probe::default();
let mut task = spawn(&rt, p.future(Res::Ready));
let awaiter = Arc::new(CountWaker::default());
let w = Waker::from(awaiter.clone());
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
let bad = panic_clone_waker();
let res = catch_unwind(AssertUnwindSafe(|| {
task.poll_join(&Context::from_waker(&bad))
}));
assert!(res.is_err());
assert!(task.poll_join(&Context::from_waker(&w)).is_pending());
one_local(&rt).run();
assert_eq!(awaiter.0.load(Ordering::SeqCst), 1);
assert!(matches!(join(&mut task), Poll::Ready(Some(_))));
drop(task);
drop(p.waker());
assert_freed(&rt);
}
#[test]
fn runtime_drop_merges_remote_refs() {
let rt = new_rt();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
let w = p.waker();
drop(task);
let w2 = on_thread(move || {
let w2 = w.clone();
w.wake();
w2
});
w2.wake_by_ref();
drop(w2);
let q = rt.queue.clone();
drop(rt);
p.check(1, 1, 0);
assert_eq!(Arc::strong_count(&q), 1, "tasks alive");
}
struct Slot {
waker: Mutex<Option<Waker>>,
left: AtomicUsize,
}
struct StressFut {
slots: Arc<Vec<Slot>>,
idx: usize,
outputs: Counter,
created: Counter,
_drop: Token,
}
impl Future for StressFut {
type Output = Token;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Token> {
let slot = &self.slots[self.idx];
*slot.waker.lock().unwrap() = Some(cx.waker().clone());
let left = slot.left.fetch_sub(1, Ordering::Relaxed);
if left <= 1 {
self.created.inc();
Poll::Ready(self.outputs.token())
} else {
if left.is_multiple_of(3) {
cx.waker().wake_by_ref();
}
Poll::Pending
}
}
}
fn rand(s: &mut u64, n: usize) -> usize {
*s ^= *s << 13;
*s ^= *s >> 7;
*s ^= *s << 17;
usize::try_from(*s % n as u64).unwrap()
}
fn slot_waker(slots: &[Slot], idx: usize) -> Option<Waker> {
slots[idx].waker.lock().unwrap().clone()
}
fn stress_worker(mut rng: u64, slots: &[Slot], queue: &Arc<RunnableQueue>, futs: Vec<StressFut>) {
let mut indices = Vec::new();
for f in futs {
indices.push(f.idx);
unsafe {
task::spawn_detached::<_, false>(f, queue, Location::caller());
}
}
for idx in indices {
for _ in 0..rand(&mut rng, 4) {
if let Some(w) = slot_waker(slots, rand(&mut rng, slots.len())) {
match rand(&mut rng, 3) {
0 => w.wake_by_ref(),
1 => w.clone().wake(),
_ => drop(w.clone()),
}
}
}
while slots[idx].left.load(Ordering::Relaxed) != 0 {
if let Some(w) = slot_waker(slots, idx) {
w.wake();
}
thread::yield_now();
}
}
}
fn run_until_done(rt: &Runtime, done: impl Fn() -> bool) {
loop {
let finished = done();
let again = rt.poll() == crate::PollResult::PollAgain;
if finished && !again {
break;
}
if !again {
thread::yield_now();
}
}
}
#[test]
fn stress() {
const WORKERS: usize = if cfg!(miri) { 2 } else { 4 };
let (rounds, per_worker) = if cfg!(miri) {
(1, 2)
} else {
let rounds = std::env::var("NTEX_RAW_STRESS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(20u64);
(rounds, 32)
};
let rt = new_rt();
for round in 0..rounds {
let total = WORKERS * per_worker * 2;
let slots: Arc<Vec<Slot>> = Arc::new(
(0..total)
.map(|i| Slot {
waker: Mutex::new(None),
left: AtomicUsize::new(1 + i % 7),
})
.collect(),
);
let (drops, outputs, created) =
(Counter::default(), Counter::default(), Counter::default());
let fut = |idx| StressFut {
slots: slots.clone(),
idx,
outputs: outputs.clone(),
created: created.clone(),
_drop: drops.token(),
};
let mut handles = Vec::new();
for idx in 0..total / 2 {
handles.push(spawn(&rt, fut(idx)));
}
let done = Arc::new(AtomicUsize::new(0));
let workers: Vec<_> = (0..WORKERS)
.map(|w| {
let futs = (0..per_worker)
.map(|i| fut(total / 2 + w * per_worker + i))
.collect();
let (slots, done, q) = (slots.clone(), done.clone(), rt.queue.clone());
let rng = (round + 1) * 0x9E37_79B9 + w as u64 * 7 + 1;
thread::spawn(move || {
stress_worker(rng, &slots, &q, futs);
done.fetch_add(1, Ordering::SeqCst);
})
})
.collect();
for (i, task) in handles.into_iter().enumerate() {
if i.is_multiple_of(2) {
task.cancel();
} else {
drop(task);
}
}
run_until_done(&rt, || done.load(Ordering::SeqCst) == WORKERS);
for w in workers {
w.join().unwrap();
}
for slot in slots.iter() {
drop(slot.waker.lock().unwrap().take());
}
run_until_done(&rt, || true);
assert_eq!(drops.get(), total, "futures dropped");
assert_eq!(outputs.get(), created.get(), "outputs dropped");
assert_freed(&rt);
}
}
#[test]
#[cfg(not(ntex_loom))]
fn remote_use_assigns_no_thread_index() {
let rt = new_rt();
let handle = rt.handle();
let p = Probe::default();
let task = spawn(&rt, p.future(Res::Pending));
one_local(&rt).run();
let w = p.waker_clone();
let assigned = on_thread(move || {
w.wake_by_ref();
drop(w.clone());
drop(w);
handle.spawn_detached(async {});
drop(handle.spawn(async {}));
super::try_current_thread_id()
});
assert_eq!(assigned, None);
drop(task);
drop(rt);
}
#[test]
#[cfg(not(ntex_loom))]
#[cfg_attr(miri, ignore = "spawns a process")]
fn id_exhaustion_aborts() {
const CHILD: &str = "NTEX_RT_ID_EXHAUSTION_CHILD";
if let Ok(case) = std::env::var(CHILD) {
if case == "runtime" {
task::wake::set_runtime_index(u16::MAX - 1);
let _rt = new_rt();
eprintln!("last id issued");
let _rt = new_rt();
} else {
super::set_thread_index(u16::MAX - 1);
let _rt = new_rt();
eprintln!("last id issued");
on_thread(|| drop(new_rt()));
}
eprintln!("ids not exhausted");
return;
}
for case in ["runtime", "thread"] {
let out = std::process::Command::new(std::env::current_exe().unwrap())
.args(["rt::task_tests::id_exhaustion_aborts", "--exact"])
.args(["--nocapture", "--test-threads=1"])
.env(CHILD, case)
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success(), "{case}: {stderr}");
assert_ne!(out.status.code(), Some(101), "{case}: {stderr}");
assert!(stderr.contains("last id issued"), "{case}: {stderr}");
assert!(!stderr.contains("ids not exhausted"), "{case}: {stderr}");
}
}