use std::{
borrow::{Borrow, Cow},
collections::HashMap,
fmt,
future::{Future, poll_fn},
hash::{BuildHasherDefault, Hasher},
io, mem,
panic::{AssertUnwindSafe, Location, catch_unwind, resume_unwind},
pin::{Pin, pin},
rc::Rc,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
task::{Context, Poll, Wake, Waker},
thread,
};
use crate::{
Local, Mode, Shared, coop,
log::error,
mode::sealed::Lock,
runtime::{Core, Remote},
};
pub(crate) struct Scheduler<M>
where
M: Mode,
{
tasks: M::Lock<Tasks<M>>,
remote: Arc<Remote>,
}
impl<M> Scheduler<M>
where
M: Mode,
{
pub(crate) fn new(remote: Arc<Remote>) -> Self {
Self {
tasks: Lock::new(Tasks {
slots: HashMap::default(),
next_id: 0,
}),
remote,
}
}
pub(crate) fn run_batch(&self, ids: &mut [u64]) {
let mut budget = ids.len();
while budget > 0 {
let taken = self.remote.take_ready(&mut ids[..budget]);
if taken == 0 {
break;
}
budget -= taken;
let mut left = Requeue {
remote: &self.remote,
ids: &ids[..taken],
};
while let Some((&id, rest)) = left.ids.split_first() {
left.ids = rest;
self.run(id);
}
}
}
#[cfg(test)]
pub(crate) fn run_one(&self) -> bool {
let Some(id) = self.remote.next_ready() else {
return false;
};
self.run(id);
true
}
fn run(&self, id: u64) {
let taken = {
let mut tasks = self.tasks.lock();
tasks.slots.get_mut(&id).and_then(|slot| {
match mem::replace(slot, Slot::Running { cancelled: false }) {
Slot::Idle { future, waker } => Some((future, waker)),
running => {
*slot = running;
None
}
}
})
};
let Some((mut future, waker)) = taken else {
return;
};
let polled = catch_unwind(AssertUnwindSafe(|| {
coop::budgeted(|| Pin::new(&mut future).poll(&mut Context::from_waker(&waker)))
}));
let ended = match polled {
Ok(Poll::Ready(())) => true,
Ok(Poll::Pending) => false,
Err(payload) => {
error!("A task panicked outside its own future");
dispose(payload);
true
}
};
let finished = {
let mut tasks = self.tasks.lock();
let Some(slot) = tasks.slots.get_mut(&id) else {
unreachable!("a task being polled keeps its slot");
};
let Slot::Running { cancelled } = *slot else {
unreachable!("only this call takes a slot out of `Running`");
};
if ended || cancelled {
tasks.slots.remove(&id);
Some((future, waker))
} else {
*slot = Slot::Idle { future, waker };
None
}
};
if let Some((future, waker)) = finished {
dispose(future);
dispose(waker);
}
}
#[cfg(any(test, feature = "helper"))]
pub(crate) fn live_tasks(&self) -> usize {
self.tasks.lock().slots.len()
}
#[cfg(test)]
pub(crate) fn waker(&self, id: u64) -> Waker {
match &self.tasks.lock().slots[&id] {
Slot::Idle { waker, .. } => waker.clone(),
Slot::Running { .. } => unreachable!("the task is not being polled"),
}
}
}
impl<M> Drop for Scheduler<M>
where
M: Mode,
{
fn drop(&mut self) {
let slots: Vec<Slot<M>> = self
.tasks
.get_mut()
.slots
.drain()
.map(|(_, slot)| slot)
.collect();
for slot in slots {
dispose(slot);
}
}
}
pub(crate) fn spawn_local<F>(
core: &Rc<Core<Local>>,
name: Name,
future: F,
) -> JoinHandle<F::Output, Local>
where
F: Future + 'static,
F::Output: 'static,
{
spawn(core, move |task_waker| {
let (join, wrapper) = task::<Local, _>(name, task_waker, future);
let waker = Waker::from(join.waker.clone());
let wrapper: Pin<Box<dyn Future<Output = ()>>> = Box::pin(wrapper);
(join, waker, wrapper)
})
}
pub(crate) fn spawn_shared<F>(
core: &Arc<Core<Shared>>,
name: Name,
future: F,
) -> JoinHandle<F::Output, Shared>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
spawn(core, move |task_waker| {
let (join, wrapper) = task::<Shared, _>(name, task_waker, future);
let waker = Waker::from(join.clone());
let wrapper: Pin<Box<dyn Future<Output = ()> + Send>> = Box::pin(wrapper);
(join, waker, wrapper)
})
}
fn spawn<M, T>(
core: &M::Ptr<Core<M>>,
make: impl FnOnce(TaskWaker) -> (M::Ptr<Join<M, T>>, Waker, M::BoxFuture),
) -> JoinHandle<T, M>
where
M: Mode,
{
let (id, join) = {
let mut tasks = core.scheduler.tasks.lock();
let id = tasks.next_id;
tasks.next_id += 1;
let (join, waker, future) = make(TaskWaker {
id,
queued: AtomicBool::new(true),
remote: core.remote.clone(),
});
tasks.slots.insert(id, Slot::Idle { future, waker });
(id, join)
};
core.remote.schedule(id);
JoinHandle {
id,
core: M::downgrade(core),
join,
detached: false,
cancelled: false,
}
}
struct Requeue<'a> {
remote: &'a Remote,
ids: &'a [u64],
}
impl Drop for Requeue<'_> {
fn drop(&mut self) {
if !self.ids.is_empty() {
self.remote.requeue(self.ids);
}
}
}
fn task<M, F>(
name: Name,
task_waker: TaskWaker,
future: F,
) -> (
M::Ptr<Join<M, F::Output>>,
impl Future<Output = ()> + 'static,
)
where
M: Mode,
F: Future + 'static,
F::Output: 'static,
{
let join = M::new_ptr(Join {
name,
waker: task_waker.into(),
state: Lock::new(JoinState {
output: None,
done: false,
join_waker: None,
}),
});
let unpolled = Unpolled {
future: Some(future),
guard: CompleteOnDrop::<M, F::Output>(join.clone()),
};
let wrapper = async move {
let unpolled = unpolled;
let guard = unpolled.guard;
let mut future = pin!(unpolled.future);
let polled = catch_unwind_polls(guard.0.task_waker(), future.as_mut()).await;
let dropped = catch_unwind(AssertUnwindSafe(|| future.set(None)));
let output = match polled {
Ok(value) => Ok(value),
Err(payload) => {
error!("The task {} panicked", guard.0.name);
dispose(payload);
Err(io::Error::other("the task panicked"))
}
};
if let Err(payload) = dropped {
dispose(payload);
}
guard.complete(output);
};
(join, wrapper)
}
struct Unpolled<F, G> {
future: Option<F>,
guard: G,
}
pub(crate) struct JoinHandle<T, M>
where
M: Mode,
{
id: u64,
core: M::Weak<Core<M>>,
join: M::Ptr<Join<M, T>>,
detached: bool,
cancelled: bool,
}
impl<T, M> JoinHandle<T, M>
where
M: Mode,
{
pub(crate) fn detach(mut self) {
self.detached = true;
self.settle();
}
pub(crate) fn cancel(mut self) -> impl Future<Output = Option<T>> {
self.cancelled = true;
let unsettled = {
let mut state = self.join.state.lock();
(!state.done).then(|| state.join_waker.take())
};
if let Some(join_waker) = unsettled {
self.stop(join_waker);
}
poll_fn(move |cx| self.poll_cancelled(cx))
}
pub(crate) fn poll_join(&self, cx: &mut Context<'_>) -> Poll<io::Result<T>> {
let waker = cx.waker().clone();
let replaced = {
let mut state = self.join.state.lock();
if let Some(output) = state.output.take() {
return Poll::Ready(output);
}
state.join_waker.replace(waker)
};
drop(replaced);
Poll::Pending
}
pub(crate) fn is_finished(&self) -> bool {
self.join.state.lock().done
}
fn poll_cancelled(&self, cx: &mut Context<'_>) -> Poll<Option<T>> {
let waker = cx.waker().clone();
let replaced = {
let mut state = self.join.state.lock();
if state.done {
let output = state.output.take();
drop(state);
return Poll::Ready(output.and_then(Result::ok));
}
state.join_waker.replace(waker)
};
drop(replaced);
Poll::Pending
}
fn stop(&self, join_waker: Option<Waker>) {
let Some(core) = M::upgrade(&self.core) else {
if let Some(waker) = join_waker {
waker.wake();
}
return;
};
let removed = {
let mut tasks = core.scheduler.tasks.lock();
let running = match tasks.slots.get_mut(&self.id) {
Some(Slot::Running { cancelled }) => {
*cancelled = true;
true
}
Some(_) => false,
None => true,
};
(!running).then(|| tasks.slots.remove(&self.id)).flatten()
};
let dropped = removed.map(|slot| catch_unwind(AssertUnwindSafe(move || drop(slot))));
if let Some(waker) = join_waker {
waker.wake();
}
core.remote.notify();
if let Some(Err(payload)) = dropped {
resume_unwind(payload);
}
}
fn settle(&self) {
let (output, join_waker) = {
let mut state = self.join.state.lock();
state.done = true;
(state.output.take(), state.join_waker.take())
};
drop(output);
drop(join_waker);
}
}
impl<T, M> Drop for JoinHandle<T, M>
where
M: Mode,
{
fn drop(&mut self) {
if self.detached {
return;
}
if self.cancelled {
self.settle();
return;
}
let join_waker = {
let mut state = self.join.state.lock();
if state.done {
let settled = (state.output.take(), state.join_waker.take());
drop(state);
drop(settled);
return;
}
state.done = true;
state.join_waker.take()
};
self.stop(join_waker);
}
}
impl<T, M> fmt::Debug for JoinHandle<T, M>
where
M: Mode,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinHandle")
.field("task", &self.join.name)
.field("detached", &self.detached)
.finish_non_exhaustive()
}
}
pub(crate) struct Join<M, T>
where
M: Mode,
{
name: Name,
waker: M::HeldWaker,
state: M::Lock<JoinState<T>>,
}
impl<M, T> Join<M, T>
where
M: Mode,
{
fn task_waker(&self) -> &TaskWaker {
self.waker.borrow()
}
}
impl<T> Wake for Join<Shared, T>
where
T: Send + 'static,
{
fn wake(self: Arc<Self>) {
self.waker.wake();
}
fn wake_by_ref(self: &Arc<Self>) {
self.waker.wake();
}
}
pub(crate) enum Name {
Given(Cow<'static, str>),
SpawnedAt(&'static Location<'static>),
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Given(name) => write!(f, "`{name}`"),
Self::SpawnedAt(location) => write!(f, "spawned at {location}"),
}
}
}
impl fmt::Debug for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Given(name) => fmt::Debug::fmt(name, f),
Self::SpawnedAt(_) => fmt::Display::fmt(self, f),
}
}
}
struct JoinState<T> {
output: Option<io::Result<T>>,
done: bool,
join_waker: Option<Waker>,
}
struct CompleteOnDrop<M, T>(M::Ptr<Join<M, T>>)
where
M: Mode;
impl<M, T> CompleteOnDrop<M, T>
where
M: Mode,
{
fn complete(&self, output: io::Result<T>) {
let (unwanted, join_waker) = {
let mut state = self.0.state.lock();
if state.done {
(Some(output), None)
} else {
state.done = true;
state.output = Some(output);
(None, state.join_waker.take())
}
};
drop(unwanted);
if let Some(waker) = join_waker {
waker.wake();
}
}
}
impl<M, T> Drop for CompleteOnDrop<M, T>
where
M: Mode,
{
fn drop(&mut self) {
let join_waker = {
let mut state = self.0.state.lock();
if state.done {
return;
}
state.done = true;
state.output = Some(Err(io::Error::other("the task's runtime is gone")));
state.join_waker.take()
};
if let Some(waker) = join_waker {
waker.wake();
}
}
}
fn catch_unwind_polls<'a, F>(
task_waker: &'a TaskWaker,
mut future: Pin<&'a mut Option<F>>,
) -> impl Future<Output = thread::Result<F::Output>> + 'a
where
F: Future + 'a,
{
poll_fn(move |cx| {
task_waker.queued.swap(false, Ordering::AcqRel);
let Some(future) = future.as_mut().as_pin_mut() else {
unreachable!("the future is polled until it is done, and dropped only then");
};
match catch_unwind(AssertUnwindSafe(|| future.poll(cx))) {
Ok(Poll::Ready(value)) => Poll::Ready(Ok(value)),
Ok(Poll::Pending) => Poll::Pending,
Err(payload) => Poll::Ready(Err(payload)),
}
})
}
fn dispose<T>(value: T) {
let Err(payload) = catch_unwind(AssertUnwindSafe(move || drop(value))) else {
return;
};
if let Err(payload) = catch_unwind(AssertUnwindSafe(move || drop(payload))) {
mem::forget(payload);
}
}
struct Tasks<M>
where
M: Mode,
{
slots: HashMap<u64, Slot<M>, BuildHasherDefault<IdHasher>>,
next_id: u64,
}
#[derive(Default)]
struct IdHasher(u64);
impl Hasher for IdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 = (self.0 << 8) | u64::from(*byte);
}
}
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
}
enum Slot<M>
where
M: Mode,
{
Idle { future: M::BoxFuture, waker: Waker },
Running {
cancelled: bool,
},
}
pub struct TaskWaker {
id: u64,
queued: AtomicBool,
remote: Arc<Remote>,
}
impl TaskWaker {
fn wake(&self) {
if self.queued.swap(true, Ordering::AcqRel) {
return;
}
self.remote.schedule(self.id);
}
}
impl Wake for TaskWaker {
fn wake(self: Arc<Self>) {
TaskWaker::wake(&self);
}
fn wake_by_ref(self: &Arc<Self>) {
TaskWaker::wake(self);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "event")]
use std::thread;
use std::{
cell::{Cell, RefCell},
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use futures::executor::block_on;
use ntest::timeout;
use super::*;
#[cfg(feature = "event")]
use crate::Event;
use crate::{LocalRuntime, Task, tests::yield_now};
#[test]
#[timeout(15000)]
fn a_spawned_task_runs_and_hands_its_output_back() {
let runtime = runtime();
let handle = runtime.spawn("an answer", async { 42 });
drive(&runtime);
assert_eq!(block_on(handle).unwrap(), 42);
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[cfg(feature = "event")]
#[test]
#[timeout(15000)]
fn a_wake_from_another_thread_notifies_the_driver() {
let runtime = runtime();
let event = Arc::new(Event::new());
let handle = {
let event = event.clone();
runtime.spawn("a waiting task", async move {
event.listen().await;
"woken"
})
};
drive(&runtime);
assert!(!runtime.core.remote.has_ready());
take_wake_up(&runtime);
thread::spawn(move || {
event.notify(1);
})
.join()
.unwrap();
assert!(runtime.core.remote.wake_pending());
assert!(runtime.core.remote.has_ready());
drive(&runtime);
assert_eq!(block_on(handle).unwrap(), "woken");
}
#[test]
#[timeout(15000)]
fn a_task_woken_during_its_own_poll_runs_again() {
struct WakeOnce(bool);
impl Future for WakeOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.0 {
return Poll::Ready(());
}
self.0 = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let handle = runtime.spawn("a self-waking task", WakeOnce(false));
assert!(scheduler.run_one());
assert!(runtime.core.remote.has_ready());
assert!(scheduler.run_one());
assert!(!scheduler.run_one());
assert!(block_on(handle).is_ok());
}
#[test]
#[timeout(15000)]
fn a_task_woken_during_a_batch_runs_again_in_it() {
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let handle = runtime.spawn("a task that yields once", async {
yield_now().await;
5
});
scheduler.run_batch(&mut [0; 1]);
assert_eq!(runtime.core.remote.ready_len(), 1);
scheduler.run_batch(&mut [0; 1]);
assert_eq!(scheduler.live_tasks(), 0);
let handle_2 = runtime.spawn("another task that yields once", async {
yield_now().await;
6
});
scheduler.run_batch(&mut [0; 2]);
assert_eq!(scheduler.live_tasks(), 0);
assert!(!runtime.core.remote.has_ready());
assert_eq!(block_on(handle).unwrap(), 5);
assert_eq!(block_on(handle_2).unwrap(), 6);
}
#[test]
#[timeout(15000)]
fn dropping_the_handle_cancels_and_drops_the_future() {
let runtime = runtime();
let dropped = Arc::new(AtomicBool::new(false));
let handle = {
let marker = SetOnDrop(dropped.clone());
runtime.spawn("a task that never finishes", async move {
let _marker = marker;
std::future::pending::<()>().await;
})
};
drive(&runtime);
assert!(!dropped.load(Ordering::Acquire));
drop(handle);
assert!(dropped.load(Ordering::Acquire));
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn a_task_cancelled_while_queued_leaves_an_id_that_is_skipped() {
let runtime = runtime();
let polled = Rc::new(Cell::new(false));
let handle = {
let polled = polled.clone();
runtime.spawn("a task that is never polled", async move {
polled.set(true);
})
};
drop(handle);
assert_eq!(runtime.core.remote.ready_len(), 1);
assert!(runtime.core.scheduler.run_one());
assert!(!runtime.core.scheduler.run_one());
assert!(!polled.get());
}
#[test]
#[timeout(15000)]
fn cancelling_notifies_the_driver() {
let runtime = runtime();
let handle = runtime.spawn("a task that never finishes", std::future::pending::<()>());
drive(&runtime);
take_wake_up(&runtime);
drop(handle);
assert!(runtime.core.remote.wake_pending());
}
#[test]
#[timeout(15000)]
fn a_detached_task_runs_to_completion() {
let runtime = runtime();
let ran = Arc::new(AtomicBool::new(false));
{
let ran = ran.clone();
runtime
.spawn("a detached task", async move {
yield_now().await;
ran.store(true, Ordering::Release);
})
.detach();
}
drive(&runtime);
assert!(ran.load(Ordering::Acquire));
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn a_panicking_task_fails_its_handle_and_is_forgotten() {
let runtime = runtime();
let handle = runtime.spawn("a task that panics", async {
panic!("the task panicked on purpose");
});
drive(&runtime);
let error = block_on(handle).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::Other);
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn a_panic_payload_that_panics_as_it_goes_still_ends_its_task() {
struct PanickingPayload;
impl Drop for PanickingPayload {
fn drop(&mut self) {
panic!("the panic payload's drop panicked on purpose");
}
}
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let handle = runtime.spawn("a task that panics with a payload of its own", async {
std::panic::panic_any(PanickingPayload);
});
assert!(scheduler.run_one());
assert_eq!(scheduler.live_tasks(), 0);
assert_eq!(block_on(handle).unwrap_err().kind(), io::ErrorKind::Other);
assert!(!scheduler.run_one());
}
#[test]
#[timeout(15000)]
fn cancelling_a_task_whose_drop_panics_with_a_panicking_payload_is_contained() {
struct PanickingPayload;
impl Drop for PanickingPayload {
fn drop(&mut self) {
panic!("the payload's drop panicked on purpose");
}
}
struct PanicsWithAPayloadWhenDropped;
impl Drop for PanicsWithAPayloadWhenDropped {
fn drop(&mut self) {
std::panic::panic_any(PanickingPayload);
}
}
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let own_handle: Rc<RefCell<Option<Task<()>>>> = Rc::new(RefCell::new(None));
let dropped_from_inside = own_handle.clone();
let handle = runtime.spawn(
"a task that cancels itself from within its own poll",
async move {
let _marker = PanicsWithAPayloadWhenDropped;
dropped_from_inside.borrow_mut().take();
std::future::pending::<()>().await;
},
);
*own_handle.borrow_mut() = Some(handle);
let ran = Arc::new(AtomicBool::new(false));
let flag = ran.clone();
let other = runtime.spawn("an unrelated task", async move {
flag.store(true, Ordering::Release);
});
assert!(scheduler.run_one());
assert!(scheduler.run_one());
assert!(ran.load(Ordering::Acquire));
assert!(block_on(other).is_ok());
assert_eq!(scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn a_panic_payload_whose_drop_panics_with_another_payload_is_contained() {
struct Inner;
impl Drop for Inner {
fn drop(&mut self) {
panic!("the inner payload's drop panicked on purpose");
}
}
struct Outer;
impl Drop for Outer {
fn drop(&mut self) {
std::panic::panic_any(Inner);
}
}
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let handle = runtime.spawn("a task whose panic payload panics twice over", async {
std::panic::panic_any(Outer);
});
assert!(scheduler.run_one());
assert_eq!(scheduler.live_tasks(), 0);
assert_eq!(block_on(handle).unwrap_err().kind(), io::ErrorKind::Other);
let next = runtime.spawn("the task after it", async { 7 });
drive(&runtime);
assert_eq!(block_on(next).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn a_panic_in_a_futures_drop_is_contained() {
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 handle = runtime.spawn("a task that panics as it goes", PanicOnDrop);
assert!(runtime.core.scheduler.run_one());
assert!(block_on(handle).is_ok());
let next = runtime.spawn("the task after it", async { 7 });
drive(&runtime);
assert_eq!(block_on(next).unwrap(), 7);
}
#[test]
#[timeout(15000)]
fn a_panic_in_a_cancelled_futures_drop_still_notifies() {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("the future's drop panicked on purpose");
}
}
let runtime = runtime();
let handle = {
let marker = PanicOnDrop;
runtime.spawn("a task that never finishes", async move {
let _marker = marker;
std::future::pending::<()>().await;
})
};
drive(&runtime);
take_wake_up(&runtime);
let cancelled = catch_unwind(AssertUnwindSafe(|| drop(handle)));
assert!(cancelled.is_err());
assert!(runtime.core.remote.wake_pending());
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn a_ready_task_is_queued_once_however_often_it_is_woken() {
let runtime = runtime();
let scheduler = &runtime.core.scheduler;
let handle = runtime.spawn("a task that never finishes", std::future::pending::<()>());
let waker = scheduler.waker(handle.0.id);
for _ in 0..3 {
waker.wake_by_ref();
}
assert_eq!(runtime.core.remote.ready_len(), 1);
assert!(scheduler.run_one());
assert!(!scheduler.run_one());
}
#[test]
#[timeout(15000)]
fn a_cancelled_futures_drop_may_spawn_on_the_scheduler() {
struct SpawnOnDrop {
runtime: LocalRuntime,
ran: Rc<Cell<bool>>,
}
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.set(true);
})
.detach();
}
}
let runtime = runtime();
let ran = Rc::new(Cell::new(false));
let handle = {
let spawner = SpawnOnDrop {
runtime: runtime.clone(),
ran: ran.clone(),
};
runtime.spawn("a task that never finishes", async move {
let _spawner = spawner;
std::future::pending::<()>().await;
})
};
drive(&runtime);
drop(handle);
assert!(!ran.get());
drive(&runtime);
assert!(ran.get());
}
#[test]
#[timeout(15000)]
fn live_tasks_counts_unfinished_futures_only() {
let runtime = runtime();
let done = runtime.spawn("a task that finishes at once", async {});
let pending = runtime.spawn("a task that never finishes", std::future::pending::<()>());
assert_eq!(runtime.core.scheduler.live_tasks(), 2);
drive(&runtime);
assert_eq!(runtime.core.scheduler.live_tasks(), 1);
assert!(block_on(done).is_ok());
drop(pending);
assert_eq!(runtime.core.scheduler.live_tasks(), 0);
}
#[test]
#[timeout(15000)]
fn dropping_the_runtime_fails_pending_joins() {
let runtime = runtime();
let handle = runtime.spawn("a task that never finishes", std::future::pending::<()>());
drive(&runtime);
drop(runtime);
assert!(block_on(handle).is_err());
}
#[test]
#[timeout(15000)]
fn a_task_dropped_with_its_runtime_drops_its_future_before_failing_its_handle() {
for polled in [false, true] {
let runtime = runtime();
let handle = Rc::new(RefCell::new(None));
let outcome_seen = Rc::new(Cell::new(None));
let probe = ProbeOnDrop {
handle: handle.clone(),
outcome_seen: outcome_seen.clone(),
};
let task = runtime.spawn("a task that never finishes", async move {
let _probe = probe;
std::future::pending::<()>().await;
});
*handle.borrow_mut() = Some(task);
if polled {
drive(&runtime);
}
drop(runtime);
assert_eq!(outcome_seen.get(), Some(false), "polled: {polled}");
let task = handle.borrow_mut().take().unwrap();
assert!(block_on(task).is_err());
}
}
#[test]
#[timeout(15000)]
fn a_name_reads_naturally_in_the_log_and_in_debug() {
let given = Name::Given("socket reader".into());
let location = Location::caller();
let spawned_at = Name::SpawnedAt(location);
assert_eq!(
format!("The task {given} panicked"),
"The task `socket reader` panicked"
);
assert_eq!(format!("{given:?}"), r#""socket reader""#);
assert_eq!(
format!("The task {spawned_at} panicked"),
format!("The task spawned at {location} panicked")
);
assert_eq!(format!("{spawned_at:?}"), format!("spawned at {location}"));
assert!(location.file().ends_with("scheduler.rs"));
}
fn drive(runtime: &LocalRuntime) {
while runtime.core.scheduler.run_one() {}
}
fn take_wake_up(runtime: &LocalRuntime) {
runtime.core.reactor.wait(Some(Duration::ZERO)).unwrap();
assert!(!runtime.core.remote.wake_pending());
}
fn runtime() -> LocalRuntime {
LocalRuntime::new().unwrap()
}
struct SetOnDrop(Arc<AtomicBool>);
impl Drop for SetOnDrop {
fn drop(&mut self) {
self.0.store(true, Ordering::Release);
}
}
struct ProbeOnDrop {
handle: Rc<RefCell<Option<Task<()>>>>,
outcome_seen: Rc<Cell<Option<bool>>>,
}
impl Drop for ProbeOnDrop {
fn drop(&mut self) {
let mut handle = self.handle.borrow_mut();
let Some(task) = handle.as_mut() else {
return;
};
let polled = Pin::new(task).poll(&mut Context::from_waker(Waker::noop()));
self.outcome_seen.set(Some(polled.is_ready()));
}
}
}