use crate::atomic_waker::AtomicWaker;
use std::cell::UnsafeCell;
use std::future::Future;
use std::mem::MaybeUninit;
use std::panic;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::task::{Context, Poll};
use std::thread;
const EMPTY: u8 = 0;
const READY: u8 = 1;
const TAKEN: u8 = 2;
pub(crate) struct SpawnCompletion<R> {
state: AtomicU8,
result: UnsafeCell<MaybeUninit<thread::Result<R>>>,
waker: AtomicWaker,
}
unsafe impl<R: Send> Send for SpawnCompletion<R> {}
unsafe impl<R: Send> Sync for SpawnCompletion<R> {}
impl<R> SpawnCompletion<R> {
pub(crate) fn new() -> Self {
Self {
state: AtomicU8::new(EMPTY),
result: UnsafeCell::new(MaybeUninit::uninit()),
waker: AtomicWaker::new(),
}
}
pub(crate) fn complete(&self, value: thread::Result<R>) {
unsafe {
(*self.result.get()).write(value);
}
self.state.store(READY, Ordering::Release);
self.waker.wake();
}
}
impl<R> Drop for SpawnCompletion<R> {
fn drop(&mut self) {
if *self.state.get_mut() == READY {
unsafe {
(*self.result.get()).assume_init_drop();
}
}
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct SpawnHandle<R> {
completion: Option<Arc<SpawnCompletion<R>>>,
}
impl<R> SpawnHandle<R> {
pub(crate) fn new(completion: Arc<SpawnCompletion<R>>) -> Self {
Self {
completion: Some(completion),
}
}
fn take_ready(&mut self) -> Poll<R> {
let completion = self.completion.as_ref().expect("already taken");
let value = unsafe { (*completion.result.get()).assume_init_read() };
completion.state.store(TAKEN, Ordering::Release);
self.completion = None;
match value {
Ok(v) => Poll::Ready(v),
Err(payload) => panic::resume_unwind(payload),
}
}
}
impl<R> Future for SpawnHandle<R> {
type Output = R;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = Pin::into_inner(self);
let completion = this.completion.as_ref().expect("SpawnHandle polled after completion");
if completion.state.load(Ordering::Acquire) == READY {
return this.take_ready();
}
completion.waker.register(cx.waker());
if completion.state.load(Ordering::Acquire) == READY {
return this.take_ready();
}
Poll::Pending
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::task::Waker;
fn noop_ctx() -> Context<'static> {
Context::from_waker(Waker::noop())
}
#[test]
fn poll_after_complete_returns_value() {
let completion = Arc::new(SpawnCompletion::<u64>::new());
completion.complete(Ok(0xdead_beef));
let mut handle = SpawnHandle::new(completion);
let mut cx = noop_ctx();
match Pin::new(&mut handle).poll(&mut cx) {
Poll::Ready(v) => assert_eq!(v, 0xdead_beef),
Poll::Pending => panic!("should be Ready after complete"),
}
}
#[test]
fn poll_pending_then_complete_then_ready() {
let completion = Arc::new(SpawnCompletion::<u32>::new());
let mut handle = SpawnHandle::new(completion.clone());
let mut cx = noop_ctx();
match Pin::new(&mut handle).poll(&mut cx) {
Poll::Pending => {}
Poll::Ready(_) => panic!("should be Pending before complete"),
}
completion.complete(Ok(42));
match Pin::new(&mut handle).poll(&mut cx) {
Poll::Ready(v) => assert_eq!(v, 42),
Poll::Pending => panic!("should be Ready after complete"),
}
}
#[test]
fn drop_without_poll_frees_written_payload() {
struct Tracker(Arc<std::sync::atomic::AtomicUsize>);
impl Drop for Tracker {
fn drop(&mut self) {
self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
let drops = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let completion: Arc<SpawnCompletion<Tracker>> = Arc::new(SpawnCompletion::new());
completion.complete(Ok(Tracker(drops.clone())));
let handle = SpawnHandle::new(completion);
drop(handle);
assert_eq!(drops.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[test]
fn drop_without_complete_is_safe() {
let completion: Arc<SpawnCompletion<u64>> = Arc::new(SpawnCompletion::new());
let mut handle = SpawnHandle::new(completion);
let mut cx = noop_ctx();
let _ = Pin::new(&mut handle).poll(&mut cx);
drop(handle);
}
#[test]
fn panic_path_resumes_on_awaiter() {
let completion = Arc::new(SpawnCompletion::<u64>::new());
let panic_payload: Box<dyn std::any::Any + Send> = Box::new("boom");
completion.complete(Err(panic_payload));
let mut handle = SpawnHandle::new(completion);
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut cx = noop_ctx();
let _ = Pin::new(&mut handle).poll(&mut cx);
}));
assert!(res.is_err(), "panic must propagate");
}
}