#[cfg(all(debug_assertions, test))]
use std::backtrace::Backtrace;
use std::cell::{Cell, UnsafeCell};
use std::fmt::Debug;
use std::marker::{PhantomData, PhantomPinned};
use std::mem::ManuallyDrop;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::pin::Pin;
#[cfg(all(debug_assertions, test))]
use std::sync::Arc;
use std::task;
use events_once::PooledLocalSender;
use observed::Sink;
use crate::remote::Sender as RemoteSender;
#[cfg(all(debug_assertions, test))]
use crate::{DiagnosticWaker, DiagnosticWakerRegistry};
use crate::{TaskCreatedReporter, TaskOptions, TaskPanicPayload, WakeSignal, discard_task_panic_payload};
#[derive(Debug)]
pub(crate) struct Task<F, R, T = PooledLocalSender<R>>
where
F: Future<Output = R> + 'static,
R: 'static,
T: TaskResultTarget<R>,
{
payload: UnsafeCell<Option<Payload<F, R, T>>>,
wake_signal: UnsafeCell<Option<WakeSignal>>,
options: TaskOptions,
sink: Sink,
poll_count: Cell<u64>,
#[cfg(all(debug_assertions, test))]
diagnostic_waker_registry: Arc<DiagnosticWakerRegistry>,
_single_threaded: PhantomData<*const ()>,
_requires_pin: PhantomPinned,
}
impl<F, R> Task<F, R>
where
F: Future<Output = R> + 'static,
R: 'static,
{
#[cfg(test)]
#[must_use]
pub(crate) fn new(future: F, result_tx: PooledLocalSender<R>) -> Self {
Self::new_with_options(future, result_tx, TaskOptions::default(), Sink::noop())
}
#[must_use]
pub(crate) fn new_with_options(future: F, result_tx: PooledLocalSender<R>, options: TaskOptions, sink: Sink) -> Self {
Self::new_with_target(future, result_tx, options, sink)
}
#[cfg(test)]
fn has_payload(&self) -> bool {
unsafe { self.payload.get().as_ref() }
.expect("UnsafeCell pointer cannot be null")
.is_some()
}
}
impl<F, R> Task<F, R, RemoteSender<R>>
where
F: Future<Output = R> + 'static,
R: Send + 'static,
{
pub(crate) fn new_remote(future: F, options: TaskOptions, sink: Sink, remote_result_tx: RemoteSender<R>) -> Self {
Self::new_with_target(future, remote_result_tx, options, sink)
}
}
impl<F, R, T> Task<F, R, T>
where
F: Future<Output = R> + 'static,
R: 'static,
T: TaskResultTarget<R>,
{
fn new_with_target(future: F, result_target: T, options: TaskOptions, sink: Sink) -> Self {
Self {
payload: UnsafeCell::new(Some(Payload {
future: FutureStorage::new(future),
result_target: Some(result_target),
})),
wake_signal: UnsafeCell::new(None),
options,
sink,
poll_count: Cell::new(0),
#[cfg(all(debug_assertions, test))]
diagnostic_waker_registry: Arc::new(DiagnosticWakerRegistry::new()),
_single_threaded: PhantomData,
_requires_pin: PhantomPinned,
}
}
}
impl<F, R, T> TypeErasedTask for Task<F, R, T>
where
F: Future<Output = R> + 'static,
R: 'static,
T: TaskResultTarget<R>,
{
fn poll(self: Pin<&Self>) -> task::Poll<()> {
self.poll_count.set(self.poll_count.get().saturating_add(1));
self.options.with_context(|| self.poll_in_context())
}
fn created_reporter(&self) -> TaskCreatedReporter {
self.options.created_reporter(self.sink.clone())
}
#[cfg_attr(test, mutants::skip)] fn consume_awakened(&self) -> bool {
unsafe {
self.wake_signal
.get()
.as_ref()
.expect("UnsafeCell pointer cannot be null")
.as_ref()
.expect("task must be initialized before consume_awakened()")
.consume_awakened()
}
}
fn is_awakened(&self) -> bool {
unsafe {
self.wake_signal
.get()
.as_ref()
.expect("UnsafeCell pointer cannot be null")
.as_ref()
.expect("task must be initialized before checking its wake state")
.is_awakened()
}
}
fn clear_queued_notification(&self) {
unsafe {
self.wake_signal
.get()
.as_ref()
.expect("UnsafeCell pointer cannot be null")
.as_ref()
.expect("task must be initialized before clearing a wake notification")
.clear_queued_notification();
}
}
#[cfg_attr(test, mutants::skip)] fn abort(self: Pin<&Self>) {
let wake_signal = unsafe { &*self.wake_signal.get() };
if let Some(wake_signal) = wake_signal {
wake_signal.retire();
}
let maybe_payload = unsafe { self.payload.get().as_mut() }.expect("UnsafeCell pointer cannot be null");
self.options
.with_context(|| self.finish_terminal(maybe_payload, TerminalOutcome::Cancelled));
}
fn initialize(self: Pin<&Self>, wake_signal: WakeSignal) {
let maybe_wake_signal = unsafe { self.wake_signal.get().as_mut().expect("UnsafeCell pointer cannot be null") };
assert!(maybe_wake_signal.is_none(), "task must be initialized exactly once");
unsafe { std::ptr::write(maybe_wake_signal, Some(wake_signal)) };
}
#[cfg(all(debug_assertions, test))]
fn inspect_waker_backtraces(&self, f: &mut dyn FnMut(&Backtrace)) {
self.diagnostic_waker_registry.inspect_backtraces(f);
}
}
impl<F, R, T> Task<F, R, T>
where
F: Future<Output = R> + 'static,
R: 'static,
T: TaskResultTarget<R>,
{
fn poll_in_context(self: Pin<&Self>) -> task::Poll<()> {
let maybe_payload = unsafe { self.payload.get().as_mut().expect("UnsafeCell pointer cannot be null") };
let Some(payload) = maybe_payload else {
unreachable!("attempted to poll a task whose future was already dropped");
};
let future_as_mut_pinned = unsafe { Pin::new_unchecked(&mut payload.future) };
let wake_signal = unsafe {
self.wake_signal
.get()
.as_ref()
.expect("UnsafeCell pointer cannot be null")
.as_ref()
.expect("task must be initialized before poll()")
};
let wake_signal = unsafe { Pin::new_unchecked(wake_signal) };
let poll_result = {
let waker = unsafe { wake_signal.waker_ref() };
#[cfg(all(debug_assertions, test))]
let waker = DiagnosticWaker::with_inner_and_registry(waker.clone(), Arc::clone(&self.diagnostic_waker_registry));
let mut cx = task::Context::from_waker(&waker);
match catch_unwind(AssertUnwindSafe(|| future_as_mut_pinned.poll(&mut cx))) {
Ok(x) => x,
Err(panic) => return self.finish_panicked(maybe_payload, wake_signal, panic),
}
};
match poll_result {
task::Poll::Ready(result) => {
wake_signal.retire();
self.finish_terminal(maybe_payload, TerminalOutcome::Succeeded(result));
task::Poll::Ready(())
}
task::Poll::Pending => task::Poll::Pending,
}
}
fn finish_panicked(
self: Pin<&Self>,
maybe_payload: &mut Option<Payload<F, R, T>>,
wake_signal: Pin<&WakeSignal>,
panic: TaskPanicPayload,
) -> task::Poll<()> {
let payload = maybe_payload.as_mut().expect("a polled task retains its payload");
let result_is_remote = payload
.result_target
.as_ref()
.expect("a polled task retains its result target")
.is_remote();
if !result_is_remote {
let _result_target = payload.result_target.take().expect("a polled task retains its result target");
on_unhandled_task_panic(panic);
}
wake_signal.retire();
self.finish_terminal(maybe_payload, TerminalOutcome::Panicked(panic));
task::Poll::Ready(())
}
fn finish_terminal(self: Pin<&Self>, maybe_payload: &mut Option<Payload<F, R, T>>, outcome: TerminalOutcome<R>) {
let Some(payload) = maybe_payload.as_mut() else {
debug_assert!(matches!(outcome, TerminalOutcome::Cancelled));
return;
};
let result_target = payload
.result_target
.take()
.expect("the result target is present until the task enters a terminal state");
let remote = result_target.is_remote();
let future_panic = if matches!(&outcome, TerminalOutcome::Succeeded(_)) {
None
} else {
unsafe { Pin::new_unchecked(&mut payload.future) }.destroy_pinned().err()
};
unsafe { Pin::new_unchecked(maybe_payload) }.set(None);
match outcome {
TerminalOutcome::Succeeded(result) => {
if let Err(panic) = result_target.complete(result) {
if remote {
discard_task_panic_payload(panic);
} else {
self.options.report_panicked(&self.sink, self.poll_count.get());
on_unhandled_task_panic(panic);
}
}
self.options.report_succeeded(&self.sink, self.poll_count.get());
}
TerminalOutcome::Panicked(panic) => {
debug_assert!(remote, "plain task panics must follow the fail-fast path");
if let Some(disposal) = future_panic {
discard_task_panic_payload(disposal);
}
if let Err(disposal) = result_target.panicked(panic) {
discard_task_panic_payload(disposal);
}
self.options.report_panicked(&self.sink, self.poll_count.get());
}
TerminalOutcome::Cancelled => {
if future_panic.is_some() {
self.options.report_panicked(&self.sink, self.poll_count.get());
}
let result_panic = catch_unwind(AssertUnwindSafe(|| drop(result_target))).err();
for panic in [future_panic, result_panic].into_iter().flatten() {
if remote {
discard_task_panic_payload(panic);
} else {
on_unhandled_task_panic(panic);
}
}
}
}
}
}
#[cfg_attr(
not(test),
expect(clippy::needless_pass_by_value, reason = "semantically correct to consume the panic")
)]
#[cfg_attr(test, mutants::skip)] #[cfg_attr(coverage_nightly, coverage(off))]
fn on_unhandled_task_panic(panic: TaskPanicPayload) -> ! {
#[cfg(test)]
std::panic::resume_unwind(panic);
#[cfg(not(test))]
if let Some(s) = panic.downcast_ref::<&str>() {
eprintln!("unhandled panic in async task - terminating process: {s}");
} else {
eprintln!("unhandled panic in async task - terminating process");
}
#[cfg(not(test))]
std::process::abort();
}
struct Payload<F, R, T>
where
F: Future<Output = R> + 'static,
R: 'static,
T: TaskResultTarget<R>,
{
future: FutureStorage<F>,
result_target: Option<T>,
}
enum TerminalOutcome<R> {
Succeeded(R),
Panicked(TaskPanicPayload),
Cancelled,
}
pub(crate) trait TaskResultTarget<R>: Debug + 'static {
fn is_remote(&self) -> bool;
fn complete(self, result: R) -> Result<(), TaskPanicPayload>;
fn panicked(self, panic: TaskPanicPayload) -> Result<(), TaskPanicPayload>;
}
impl<R: 'static> TaskResultTarget<R> for PooledLocalSender<R> {
fn is_remote(&self) -> bool {
false
}
fn complete(self, result: R) -> Result<(), TaskPanicPayload> {
catch_unwind(AssertUnwindSafe(|| self.send(result)))
}
fn panicked(self, _panic: TaskPanicPayload) -> Result<(), TaskPanicPayload> {
unreachable!("local task panics terminate the process")
}
}
impl<R: Send + 'static> TaskResultTarget<R> for RemoteSender<R> {
fn is_remote(&self) -> bool {
true
}
fn complete(self, result: R) -> Result<(), TaskPanicPayload> {
self.completed(result)
}
fn panicked(self, panic: TaskPanicPayload) -> Result<(), TaskPanicPayload> {
self.panicked(panic)
}
}
struct FutureStorage<F> {
inner: ManuallyDrop<F>,
live: bool,
_pinned: PhantomPinned,
}
impl<F> FutureStorage<F> {
fn new(inner: F) -> Self {
Self {
inner: ManuallyDrop::new(inner),
live: true,
_pinned: PhantomPinned,
}
}
fn destroy_pinned(self: Pin<&mut Self>) -> Result<(), TaskPanicPayload> {
let this = unsafe { self.get_unchecked_mut() };
Self::destroy(&mut this.inner, &mut this.live)
}
fn destroy(inner: &mut ManuallyDrop<F>, live: &mut bool) -> Result<(), TaskPanicPayload> {
if !std::mem::replace(live, false) {
return Ok(());
}
catch_unwind(AssertUnwindSafe(|| {
unsafe { ManuallyDrop::drop(inner) };
}))
}
}
impl<F: Future> Future for FutureStorage<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
assert!(this.live, "task future polled after completion");
let outcome = unsafe { Pin::new_unchecked(&mut *this.inner) }.poll(cx);
if outcome.is_ready()
&& let Err(panic) = Self::destroy(&mut this.inner, &mut this.live)
{
std::panic::resume_unwind(panic);
}
outcome
}
}
#[cfg_attr(coverage_nightly, coverage(off))] impl<F> Drop for FutureStorage<F> {
fn drop(&mut self) {
if let Err(panic) = Self::destroy(&mut self.inner, &mut self.live) {
on_unhandled_task_panic(panic);
}
}
}
pub(crate) trait TypeErasedTask {
fn poll(self: Pin<&Self>) -> task::Poll<()>;
fn created_reporter(&self) -> TaskCreatedReporter;
fn consume_awakened(&self) -> bool;
fn is_awakened(&self) -> bool;
fn clear_queued_notification(&self);
fn abort(self: Pin<&Self>);
fn initialize(self: Pin<&Self>, wake_signal: WakeSignal);
#[cfg(all(debug_assertions, test))]
fn inspect_waker_backtraces(&self, f: &mut dyn FnMut(&Backtrace));
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::future::{Ready, pending};
use std::pin::pin;
use std::rc::Rc;
#[cfg(not(debug_assertions))]
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Wake, Waker};
use events_once::{Disconnected, LocalEventPool};
use observed_testing::{TEST_ID, test_emitter};
use static_assertions::assert_not_impl_any;
use testing_aids::assert_panic;
use super::*;
use crate::remote::Pool as RemotePool;
use crate::testing::TestSubjectFuture;
struct PinnedDropFuture {
polled_at: Cell<*const Self>,
dropped: Rc<Cell<bool>>,
completes: bool,
_pin: PhantomPinned,
}
impl Future for PinnedDropFuture {
type Output = u64;
fn poll(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll<Self::Output> {
self.polled_at.set(std::ptr::from_ref(self.as_ref().get_ref()));
if self.completes {
task::Poll::Ready(42)
} else {
task::Poll::Pending
}
}
}
impl Drop for PinnedDropFuture {
fn drop(&mut self) {
assert_eq!(self.polled_at.get(), std::ptr::from_ref(self));
self.dropped.set(true);
}
}
fn check_pinned_future_destruction(completes: bool) {
let event_pool = LocalEventPool::<u64>::new();
let (tx, mut rx) = event_pool.rent();
let dropped = Rc::new(Cell::new(false));
let task = pin!(Task::new(
PinnedDropFuture {
polled_at: Cell::new(std::ptr::null()),
dropped: Rc::clone(&dropped),
completes,
_pin: PhantomPinned,
},
tx,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
let result = task.poll();
if completes {
assert!(result.is_ready());
} else {
assert!(result.is_pending());
task.abort();
}
assert!(dropped.get());
assert!(!task.has_payload());
let mut cx = task::Context::from_waker(Waker::noop());
let result = Pin::new(&mut rx).poll(&mut cx);
if completes {
assert_eq!(result, task::Poll::Ready(Ok(42)));
} else {
assert_eq!(result, task::Poll::Ready(Err(Disconnected)));
}
}
#[test]
fn clear_queued_notification_forwards_to_the_wake_signal() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let task = pin!(Task::new(pending::<u64>(), tx));
let task = task.as_ref();
let fake_task_ref = unsafe { crate::TaskRef::fake() };
let queue = Arc::new(std::sync::Mutex::new(std::collections::VecDeque::with_capacity(1)));
let signal = WakeSignal::new_with_parts(
Arc::clone(&queue),
Arc::new(std::sync::atomic::AtomicBool::new(false)),
Waker::noop().clone(),
fake_task_ref,
);
task.initialize(signal);
let signal = unsafe { &*task.wake_signal.get() }.as_ref().unwrap();
let signal = unsafe { Pin::new_unchecked(signal) };
let waker = unsafe { signal.waker_ref() };
waker.wake_by_ref();
assert!(signal.has_queued_notification());
task.clear_queued_notification();
assert!(!signal.has_queued_notification());
drop(waker);
task.abort();
}
#[test]
fn completed_future_is_destroyed_at_its_pinned_address() {
check_pinned_future_destruction(true);
}
#[test]
fn cancelled_future_is_destroyed_at_its_pinned_address() {
check_pinned_future_destruction(false);
}
struct DropCountingFuture(std::sync::Arc<AtomicUsize>);
impl Future for DropCountingFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll<Self::Output> {
task::Poll::Pending
}
}
impl Drop for DropCountingFuture {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn future_storage_destroy_pinned_destroys_exactly_once() {
let drops = std::sync::Arc::new(AtomicUsize::new(0));
{
let mut storage = pin!(FutureStorage::new(DropCountingFuture(std::sync::Arc::clone(&drops))));
storage.as_mut().destroy_pinned().unwrap();
assert_eq!(drops.load(Ordering::Relaxed), 1);
}
assert_eq!(drops.load(Ordering::Relaxed), 1);
}
#[test]
fn future_storage_drop_destroys_live_future() {
let drops = std::sync::Arc::new(AtomicUsize::new(0));
drop(FutureStorage::new(DropCountingFuture(std::sync::Arc::clone(&drops))));
assert_eq!(drops.load(Ordering::Relaxed), 1);
}
struct PanicOnDropFuture(Ready<u64>);
impl Future for PanicOnDropFuture {
type Output = u64;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
impl Drop for PanicOnDropFuture {
fn drop(&mut self) {
panic!("panic from future destructor");
}
}
struct PanicOnPollAndDropFuture;
impl Future for PanicOnPollAndDropFuture {
type Output = u64;
fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
panic!("panic from future poll");
}
}
impl Drop for PanicOnPollAndDropFuture {
fn drop(&mut self) {
panic!("panic from future destructor");
}
}
#[test]
fn smoke_test() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, mut rx) = event_pool.rent();
let task = pin!(Task::new(async { 42 }, tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert!(task.has_payload());
let task_result = task.poll();
assert!(matches!(task_result, task::Poll::Ready(())));
let mut cx = task::Context::from_waker(Waker::noop());
let real_result = Pin::new(&mut rx).poll(&mut cx);
assert!(matches!(real_result, task::Poll::Ready(Ok(42))));
assert!(!task.has_payload());
}
#[test]
fn observed_task_reports_aggregate_poll_count_on_completion() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let (sink, processor) = test_emitter(TEST_ID);
let polls = Rc::new(Cell::new(0));
let task = pin!(Task::new_with_options(
std::future::poll_fn({
let polls = Rc::clone(&polls);
move |_| {
polls.set(polls.get() + 1);
if polls.get() == 2 {
task::Poll::Ready(42)
} else {
task::Poll::Pending
}
}
}),
tx,
TaskOptions::observed(sink.transfer_context(), crate::TaskPlacement::Any),
sink,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
task.created_reporter().report();
assert!(task.poll().is_pending());
assert!(task.poll().is_ready());
let events = processor.events();
let completed = events.iter().find(|event| event.name() == "arty.rt.task.succeeded").unwrap();
assert_eq!(completed.field_metrics()[0].field_key(), "polls");
assert!((completed.field_metrics()[0].value() - 2.0).abs() < f64::EPSILON);
}
#[test]
fn observed_remote_panic_reports_aggregate_poll_count() {
let (result_tx, mut result_rx) = RemotePool::new().channel::<u64>();
let (sink, processor) = test_emitter(TEST_ID);
let polls = Rc::new(Cell::new(0));
let task = pin!(Task::new_remote(
std::future::poll_fn({
let polls = Rc::clone(&polls);
move |_| {
polls.set(polls.get() + 1);
assert_ne!(polls.get(), 2, "captured task panic");
task::Poll::Pending
}
}),
TaskOptions::observed(sink.transfer_context(), crate::TaskPlacement::Any),
sink,
result_tx,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
task.created_reporter().report();
assert!(task.poll().is_pending());
assert!(task.poll().is_ready());
let mut cx = task::Context::from_waker(Waker::noop());
let task::Poll::Ready(Err(error)) = Pin::new(&mut result_rx).poll(&mut cx) else {
panic!("captured panic must be published to the remote join handle");
};
assert!(error.is_panic());
discard_task_panic_payload(error.into_panic().expect("the join error was checked as a panic"));
let events = processor.events();
let panicked = events.iter().find(|event| event.name() == "arty.rt.task.panicked").unwrap();
assert_eq!(panicked.field_metrics()[0].field_key(), "polls");
assert!((panicked.field_metrics()[0].value() - 2.0).abs() < f64::EPSILON);
assert!(!events.iter().any(|event| event.name() == "arty.rt.task.succeeded"));
}
#[test]
fn cancellation_emits_no_terminal_task_event() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let (sink, processor) = test_emitter(TEST_ID);
let task = pin!(Task::new_with_options(
pending::<u64>(),
tx,
TaskOptions::observed(sink.transfer_context(), crate::TaskPlacement::Any),
sink,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
task.created_reporter().report();
task.abort();
task.abort();
let events = processor.events();
assert!(events.iter().any(|event| event.name() == "arty.rt.task.spawned"));
assert!(
!events
.iter()
.any(|event| { matches!(event.name(), "arty.rt.task.succeeded" | "arty.rt.task.panicked") })
);
}
struct PanickingJoinWake;
impl Wake for PanickingJoinWake {
fn wake(self: Arc<Self>) {
panic!("join notification panic");
}
}
#[test]
fn cancellation_join_notification_panic_is_not_reported_as_a_task_panic() {
let (result_tx, mut result_rx) = RemotePool::new().channel::<u64>();
let (sink, processor) = test_emitter(TEST_ID);
let waker = Waker::from(Arc::new(PanickingJoinWake));
assert!(Pin::new(&mut result_rx).poll(&mut task::Context::from_waker(&waker)).is_pending());
let task = pin!(Task::new_remote(pending::<u64>(), TaskOptions::default(), sink, result_tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
task.created_reporter().report();
task.abort();
let events = processor.events();
assert!(events.iter().any(|event| event.name() == "arty.rt.task.spawned"));
assert!(!events.iter().any(|event| event.name() == "arty.rt.task.panicked"));
}
#[test]
fn local_completion_notification_panic_follows_fail_fast_path() {
let event_pool = LocalEventPool::<u64>::new();
let (result_tx, mut result_rx) = event_pool.rent();
let waker = Waker::from(Arc::new(PanickingJoinWake));
assert!(Pin::new(&mut result_rx).poll(&mut task::Context::from_waker(&waker)).is_pending());
let task = pin!(Task::new(std::future::ready(42), result_tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert_panic!(task.poll());
}
#[test]
fn remote_poll_and_destructor_panics_are_both_contained() {
let (result_tx, mut result_rx) = RemotePool::new().channel::<u64>();
let task = pin!(Task::new_remote(
PanicOnPollAndDropFuture,
TaskOptions::default(),
Sink::noop(),
result_tx,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert!(task.poll().is_ready());
let mut cx = task::Context::from_waker(Waker::noop());
let task::Poll::Ready(Err(error)) = Pin::new(&mut result_rx).poll(&mut cx) else {
panic!("captured poll panic must reach the remote join");
};
discard_task_panic_payload(error.into_panic().unwrap());
}
#[test]
fn plain_task_panic_remains_unhandled() {
let event_pool = LocalEventPool::<()>::new();
let (tx, mut rx) = event_pool.rent();
let task = pin!(Task::new(
std::future::poll_fn(|_| -> task::Poll<()> {
panic!("plain task panic");
}),
tx
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert_panic!(task.poll());
assert!(task.has_payload());
let mut cx = task::Context::from_waker(Waker::noop());
assert_eq!(Pin::new(&mut rx).poll(&mut cx), task::Poll::Ready(Err(Disconnected)));
}
#[test]
fn local_result_target_rejects_panic_reporting() {
let event_pool = LocalEventPool::<u64>::new();
let (sender, _receiver) = event_pool.rent();
assert_panic!(<PooledLocalSender<u64> as TaskResultTarget<u64>>::panicked(
sender,
Box::new("local task panic"),
));
}
#[test]
fn uninitialized_poll_panics() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let task = pin!(Task::new(async { 42 }, tx));
let task = task.as_ref();
assert_panic!(task.poll());
}
#[test]
#[cfg(debug_assertions)]
fn inspect_wakers_returns_stashed_wakers() {
use std::cell::RefCell;
use std::future::poll_fn;
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let stashed_wakers = Rc::new(RefCell::new(Vec::new()));
let task = pin!(Task::new(
{
let stashed_wakers = Rc::clone(&stashed_wakers);
poll_fn(move |cx| {
stashed_wakers.borrow_mut().push(cx.waker().clone());
task::Poll::Pending
})
},
tx,
));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
_ = task.poll();
_ = task.poll();
_ = task.poll();
assert!(task.has_payload());
let mut inspected_waker_count: usize = 0;
task.inspect_waker_backtraces(&mut |_| inspected_waker_count += 1);
assert_eq!(inspected_waker_count, stashed_wakers.borrow().len());
task.abort();
drop(stashed_wakers);
assert!(!task.has_payload());
}
#[test]
fn future_and_event_dropped_after_abort() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, mut rx) = event_pool.rent();
let shared_state = Rc::new(123);
let shared_state_weak = Rc::downgrade(&shared_state);
let task = pin!(Task::new(async move { *shared_state }, tx,));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
task.abort();
assert!(shared_state_weak.upgrade().is_none());
let mut cx = task::Context::from_waker(Waker::noop());
let event_result = Pin::new(&mut rx).poll(&mut cx);
assert!(matches!(event_result, task::Poll::Ready(Err(Disconnected))));
}
#[test]
fn future_dropped_after_poll_complete() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let shared_state = Rc::new(123);
let shared_state_weak = Rc::downgrade(&shared_state);
let task = pin!(Task::new(async move { *shared_state }, tx,));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
let result = task.poll();
assert!(matches!(result, task::Poll::Ready(())));
assert!(shared_state_weak.upgrade().is_none());
}
#[test]
fn panic_while_dropping_completed_future_is_contained() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, mut rx) = event_pool.rent();
let task = pin!(Task::new(PanicOnDropFuture(std::future::ready(42)), tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert_panic!(task.poll());
let mut cx = task::Context::from_waker(Waker::noop());
let event_result = Pin::new(&mut rx).poll(&mut cx);
assert!(matches!(event_result, task::Poll::Ready(Err(Disconnected))));
}
#[test]
fn panic_while_dropping_aborted_future_is_contained() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, mut rx) = event_pool.rent();
let task = pin!(Task::new(PanicOnDropFuture(std::future::ready(42)), tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
assert_panic!(task.abort());
let mut cx = task::Context::from_waker(Waker::noop());
let event_result = Pin::new(&mut rx).poll(&mut cx);
assert!(matches!(event_result, task::Poll::Ready(Err(Disconnected))));
}
#[test]
fn poll_after_complete_panics() {
let event_pool = LocalEventPool::<u64>::new();
let (tx, _rx) = event_pool.rent();
let task = pin!(Task::new(async { 42 }, tx));
let task = task.as_ref();
task.initialize(WakeSignal::fake());
let task_result = task.poll();
assert!(matches!(task_result, task::Poll::Ready(())));
assert_panic!(task.poll());
}
#[test]
fn thread_safety() {
assert_not_impl_any!(Task<TestSubjectFuture, ()>: Send, Sync);
}
}