Skip to content

Commit 2e0a196

Browse files
authored
Add panic propagation (#37)
* Add panic propogation * Make propagation a builder option
1 parent e249342 commit 2e0a196

File tree

5 files changed

+133
-17
lines changed

5 files changed

+133
-17
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ atomic-waker = "1"
2323
easy-parallel = "3"
2424
flaky_test = "0.1"
2525
flume = { version = "0.10", default-features = false }
26+
futures-lite = "1.12.0"
2627
once_cell = "1"
2728
smol = "1"
2829

src/header.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub(crate) struct Header<M> {
3131
///
3232
/// This metadata may be provided to the user.
3333
pub(crate) metadata: M,
34+
35+
/// Whether or not a panic that occurs in the task should be propagated.
36+
#[cfg(feature = "std")]
37+
pub(crate) propagate_panic: bool,
3438
}
3539

3640
impl<M> Header<M> {

src/raw.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ use crate::state::*;
1313
use crate::utils::{abort, abort_on_panic, max, Layout};
1414
use crate::Runnable;
1515

16+
#[cfg(feature = "std")]
17+
pub(crate) type Panic = alloc::boxed::Box<dyn core::any::Any + Send + 'static>;
18+
19+
#[cfg(not(feature = "std"))]
20+
pub(crate) type Panic = core::convert::Infallible;
21+
1622
/// The vtable for a task.
1723
pub(crate) struct TaskVTable {
1824
/// Schedules the task.
@@ -76,7 +82,7 @@ pub(crate) struct RawTask<F, T, S, M> {
7682
pub(crate) future: *mut F,
7783

7884
/// The output of the future.
79-
pub(crate) output: *mut T,
85+
pub(crate) output: *mut Result<T, Panic>,
8086
}
8187

8288
impl<F, T, S, M> Copy for RawTask<F, T, S, M> {}
@@ -97,7 +103,7 @@ impl<F, T, S, M> RawTask<F, T, S, M> {
97103
let layout_header = Layout::new::<Header<M>>();
98104
let layout_s = Layout::new::<S>();
99105
let layout_f = Layout::new::<F>();
100-
let layout_r = Layout::new::<T>();
106+
let layout_r = Layout::new::<Result<T, Panic>>();
101107

102108
// Compute the layout for `union { F, T }`.
103109
let size_union = max(layout_f.size(), layout_r.size());
@@ -138,7 +144,7 @@ where
138144
pub(crate) fn allocate<'a, Gen: FnOnce(&'a M) -> F>(
139145
future: Gen,
140146
schedule: S,
141-
metadata: M,
147+
builder: crate::Builder<M>,
142148
) -> NonNull<()>
143149
where
144150
F: 'a,
@@ -158,6 +164,12 @@ where
158164

159165
let raw = Self::from_ptr(ptr.as_ptr());
160166

167+
let crate::Builder {
168+
metadata,
169+
#[cfg(feature = "std")]
170+
propagate_panic,
171+
} = builder;
172+
161173
// Write the header as the first field of the task.
162174
(raw.header as *mut Header<M>).write(Header {
163175
state: AtomicUsize::new(SCHEDULED | TASK | REFERENCE),
@@ -173,6 +185,8 @@ where
173185
layout_info: &Self::TASK_LAYOUT,
174186
},
175187
metadata,
188+
#[cfg(feature = "std")]
189+
propagate_panic,
176190
});
177191

178192
// Write the schedule function as the third field of the task.
@@ -199,7 +213,7 @@ where
199213
header: p as *const Header<M>,
200214
schedule: p.add(task_layout.offset_s) as *const S,
201215
future: p.add(task_layout.offset_f) as *mut F,
202-
output: p.add(task_layout.offset_r) as *mut T,
216+
output: p.add(task_layout.offset_r) as *mut Result<T, Panic>,
203217
}
204218
}
205219
}
@@ -525,8 +539,30 @@ where
525539

526540
// Poll the inner future, but surround it with a guard that closes the task in case polling
527541
// panics.
542+
// If available, we should also try to catch the panic so that it is propagated correctly.
528543
let guard = Guard(raw);
529-
let poll = <F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx);
544+
545+
// Panic propagation is not available for no_std.
546+
#[cfg(not(feature = "std"))]
547+
let poll = <F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx).map(Ok);
548+
549+
#[cfg(feature = "std")]
550+
let poll = {
551+
// Check if we should propagate panics.
552+
if (*raw.header).propagate_panic {
553+
// Use catch_unwind to catch the panic.
554+
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
555+
<F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx)
556+
})) {
557+
Ok(Poll::Ready(v)) => Poll::Ready(Ok(v)),
558+
Ok(Poll::Pending) => Poll::Pending,
559+
Err(e) => Poll::Ready(Err(e)),
560+
}
561+
} else {
562+
<F as Future>::poll(Pin::new_unchecked(&mut *raw.future), cx).map(Ok)
563+
}
564+
};
565+
530566
mem::forget(guard);
531567

532568
match poll {

src/runnable.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ use crate::Task;
1717
#[derive(Debug)]
1818
pub struct Builder<M> {
1919
/// The metadata associated with the task.
20-
metadata: M,
20+
pub(crate) metadata: M,
21+
22+
/// Whether or not a panic that occurs in the task should be propagated.
23+
#[cfg(feature = "std")]
24+
pub(crate) propagate_panic: bool,
2125
}
2226

2327
impl<M: Default> Default for Builder<M> {
@@ -40,7 +44,11 @@ impl Builder<()> {
4044
/// let (runnable, task) = Builder::new().spawn(|()| async {}, |_| {});
4145
/// ```
4246
pub fn new() -> Builder<()> {
43-
Builder { metadata: () }
47+
Builder {
48+
metadata: (),
49+
#[cfg(feature = "std")]
50+
propagate_panic: false,
51+
}
4452
}
4553

4654
/// Adds metadata to the task.
@@ -123,11 +131,63 @@ impl Builder<()> {
123131
/// # });
124132
/// ```
125133
pub fn metadata<M>(self, metadata: M) -> Builder<M> {
126-
Builder { metadata }
134+
Builder {
135+
metadata,
136+
#[cfg(feature = "std")]
137+
propagate_panic: self.propagate_panic,
138+
}
127139
}
128140
}
129141

130142
impl<M> Builder<M> {
143+
/// Propagates panics that occur in the task.
144+
///
145+
/// When this is `true`, panics that occur in the task will be propagated to the caller of
146+
/// the [`Task`]. When this is false, no special action is taken when a panic occurs in the
147+
/// task, meaning that the caller of [`Runnable::run`] will observe a panic.
148+
///
149+
/// This is only available when the `std` feature is enabled. By default, this is `false`.
150+
///
151+
/// # Examples
152+
///
153+
/// ```
154+
/// use async_task::Builder;
155+
/// use futures_lite::future::poll_fn;
156+
/// use std::future::Future;
157+
/// use std::panic;
158+
/// use std::pin::Pin;
159+
/// use std::task::{Context, Poll};
160+
///
161+
/// fn did_panic<F: FnOnce()>(f: F) -> bool {
162+
/// panic::catch_unwind(panic::AssertUnwindSafe(f)).is_err()
163+
/// }
164+
///
165+
/// # smol::future::block_on(async {
166+
/// let (runnable1, mut task1) = Builder::new()
167+
/// .propagate_panic(true)
168+
/// .spawn(|()| async move { panic!() }, |_| {});
169+
///
170+
/// let (runnable2, mut task2) = Builder::new()
171+
/// .propagate_panic(false)
172+
/// .spawn(|()| async move { panic!() }, |_| {});
173+
///
174+
/// assert!(!did_panic(|| { runnable1.run(); }));
175+
/// assert!(did_panic(|| { runnable2.run(); }));
176+
///
177+
/// let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await;
178+
/// let mut cx = Context::from_waker(&waker);
179+
/// assert!(did_panic(|| { let _ = Pin::new(&mut task1).poll(&mut cx); }));
180+
/// assert!(did_panic(|| { let _ = Pin::new(&mut task2).poll(&mut cx); }));
181+
/// # });
182+
/// ```
183+
#[cfg(feature = "std")]
184+
pub fn propagate_panic(self, propagate_panic: bool) -> Builder<M> {
185+
Builder {
186+
metadata: self.metadata,
187+
propagate_panic,
188+
}
189+
}
190+
131191
/// Creates a new task.
132192
///
133193
/// The returned [`Runnable`] is used to poll the `future`, and the [`Task`] is used to await its
@@ -313,18 +373,16 @@ impl<M> Builder<M> {
313373
S: Fn(Runnable<M>),
314374
M: 'a,
315375
{
316-
let Self { metadata } = self;
317-
318376
// Allocate large futures on the heap.
319377
let ptr = if mem::size_of::<Fut>() >= 2048 {
320378
let future = |meta| {
321379
let future = future(meta);
322380
Box::pin(future)
323381
};
324382

325-
RawTask::<_, Fut::Output, S, M>::allocate(future, schedule, metadata)
383+
RawTask::<_, Fut::Output, S, M>::allocate(future, schedule, self)
326384
} else {
327-
RawTask::<Fut, Fut::Output, S, M>::allocate(future, schedule, metadata)
385+
RawTask::<Fut, Fut::Output, S, M>::allocate(future, schedule, self)
328386
};
329387

330388
let runnable = Runnable {

src/task.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use core::sync::atomic::Ordering;
88
use core::task::{Context, Poll};
99

1010
use crate::header::Header;
11+
use crate::raw::Panic;
1112
use crate::state::*;
1213

1314
/// A spawned task.
@@ -226,7 +227,7 @@ impl<T, M> Task<T, M> {
226227
}
227228

228229
/// Puts the task in detached state.
229-
fn set_detached(&mut self) -> Option<T> {
230+
fn set_detached(&mut self) -> Option<Result<T, Panic>> {
230231
let ptr = self.ptr.as_ptr();
231232
let header = ptr as *const Header<M>;
232233

@@ -256,8 +257,10 @@ impl<T, M> Task<T, M> {
256257
) {
257258
Ok(_) => {
258259
// Read the output.
259-
output =
260-
Some((((*header).vtable.get_output)(ptr) as *mut T).read());
260+
output = Some(
261+
(((*header).vtable.get_output)(ptr) as *mut Result<T, Panic>)
262+
.read(),
263+
);
261264

262265
// Update the state variable because we're continuing the loop.
263266
state |= CLOSED;
@@ -382,8 +385,22 @@ impl<T, M> Task<T, M> {
382385
}
383386

384387
// Take the output from the task.
385-
let output = ((*header).vtable.get_output)(ptr) as *mut T;
386-
return Poll::Ready(Some(output.read()));
388+
let output = ((*header).vtable.get_output)(ptr) as *mut Result<T, Panic>;
389+
let output = output.read();
390+
391+
// Propagate the panic if the task panicked.
392+
let output = match output {
393+
Ok(output) => output,
394+
Err(panic) => {
395+
#[cfg(feature = "std")]
396+
std::panic::resume_unwind(panic);
397+
398+
#[cfg(not(feature = "std"))]
399+
match panic {}
400+
}
401+
};
402+
403+
return Poll::Ready(Some(output));
387404
}
388405
Err(s) => state = s,
389406
}

0 commit comments

Comments
 (0)