use std::cell::UnsafeCell;
use std::sync::{Arc, atomic::AtomicU8, atomic::Ordering};
use std::task::{Context, Poll, ready};
use std::{future::Future, mem, pin::Pin};
use atomic_waker::AtomicWaker;
const DONE: u8 = 1;
const CANCELLED: u8 = 2;
const JOINABLE: u8 = 4;
enum Stage<F: Future> {
Running(F),
Done(F::Output),
Empty,
}
pub(crate) struct Job<F: Future> {
state: AtomicU8,
join_waker: AtomicWaker,
run_waker: AtomicWaker,
stage: UnsafeCell<Stage<F>>,
}
unsafe impl<F: Future + Send> Send for Job<F> where F::Output: Send {}
unsafe impl<F: Future + Send> Sync for Job<F> where F::Output: Send {}
impl<F: Future> Job<F> {
pub(crate) fn new(future: F, joinable: bool) -> Arc<Self> {
Arc::new(Self {
state: AtomicU8::new(if joinable { JOINABLE } else { 0 }),
join_waker: AtomicWaker::new(),
run_waker: AtomicWaker::new(),
stage: UnsafeCell::new(Stage::Running(future)),
})
}
pub(crate) fn close(&self) {
if self.state.load(Ordering::Acquire) & DONE == 0 {
self.finish(Stage::Empty);
}
}
fn finish(&self, stage: Stage<F>) {
struct Complete<'a, F: Future>(&'a Job<F>);
impl<F: Future> Drop for Complete<'_, F> {
fn drop(&mut self) {
self.0.complete();
}
}
let _complete = Complete(self);
unsafe { *self.stage.get() = stage };
}
fn complete(&self) {
self.state.fetch_or(DONE, Ordering::AcqRel);
drop(self.run_waker.take());
self.join_waker.wake();
}
}
pub(crate) struct Runner<F: Future>(pub(crate) Arc<Job<F>>);
impl<F: Future> Future for Runner<F> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let job = &*self.0;
let state = job.state.load(Ordering::Acquire);
if state & DONE != 0 {
return Poll::Ready(());
}
if state & CANCELLED != 0 {
job.close();
return Poll::Ready(());
}
if state & JOINABLE != 0 {
job.run_waker.register(cx.waker());
if job.state.load(Ordering::Acquire) & CANCELLED != 0 {
job.close();
return Poll::Ready(());
}
}
let slot = unsafe { &mut *job.stage.get() };
let Stage::Running(fut) = slot else {
unreachable!("job polled after completion")
};
let out = ready!(unsafe { Pin::new_unchecked(fut) }.poll(cx));
job.finish(Stage::Done(out));
Poll::Ready(())
}
}
impl<F: Future> Drop for Runner<F> {
fn drop(&mut self) {
self.0.close();
}
}
trait JoinJob<T>: Send + Sync {
fn poll_join(&self, cx: &mut Context<'_>) -> Poll<Option<T>>;
fn cancel(&self);
fn detach(&self);
fn is_finished(&self) -> bool;
}
impl<F> JoinJob<F::Output> for Job<F>
where
F: Future + Send,
F::Output: Send,
{
fn poll_join(&self, cx: &mut Context<'_>) -> Poll<Option<F::Output>> {
if self.state.load(Ordering::Acquire) & DONE == 0 {
self.join_waker.register(cx.waker());
if self.state.load(Ordering::Acquire) & DONE == 0 {
return Poll::Pending;
}
}
match mem::replace(unsafe { &mut *self.stage.get() }, Stage::Empty) {
Stage::Done(out) => Poll::Ready(Some(out)),
_ => Poll::Ready(None),
}
}
fn cancel(&self) {
self.state.fetch_or(CANCELLED, Ordering::AcqRel);
self.run_waker.wake();
}
fn detach(&self) {
self.state.fetch_and(!JOINABLE, Ordering::AcqRel);
}
fn is_finished(&self) -> bool {
self.state.load(Ordering::Acquire) & DONE != 0
}
}
pub(crate) struct RemoteJoin<T>(Arc<dyn JoinJob<T>>);
impl<T> RemoteJoin<T> {
pub(crate) fn new<F>(job: Arc<Job<F>>) -> Self
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
Self(job)
}
pub(crate) fn poll_join(&self, cx: &mut Context<'_>) -> Poll<Option<T>> {
self.0.poll_join(cx)
}
pub(crate) fn cancel(&self) {
self.0.cancel();
}
pub(crate) fn is_finished(&self) -> bool {
self.0.is_finished()
}
}
impl<T> Drop for RemoteJoin<T> {
fn drop(&mut self) {
self.0.detach();
}
}
impl<T> std::fmt::Debug for RemoteJoin<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RemoteJoin")
.field("finished", &self.is_finished())
.finish()
}
}
#[cfg(any(feature = "tokio", feature = "compio"))]
pub(crate) use self::spawner::Spawner;
#[cfg(any(feature = "tokio", feature = "compio"))]
mod spawner {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::task::{Context, Poll};
use std::{fmt, future::Future, pin::Pin, sync::Arc, thread};
use atomic_waker::AtomicWaker;
use crossbeam_queue::SegQueue;
use super::{Job, Runner};
use crate::rt::{ThreadId, current_thread_id, try_current_thread_id};
const BATCH: usize = 128;
const CLOSED: usize = 1 << (usize::BITS - 1);
trait RunJob: Send + Sync {
fn spawn_local(self: Arc<Self>);
fn close(&self);
}
impl<F> RunJob for Job<F>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
fn spawn_local(self: Arc<Self>) {
crate::spawn_detached(Runner(self));
}
fn close(&self) {
Job::close(self);
}
}
pub(crate) struct Spawner {
owner: ThreadId,
admission: AtomicUsize,
notified: AtomicBool,
waker: AtomicWaker,
queue: SegQueue<Arc<dyn RunJob>>,
}
impl Spawner {
pub(crate) fn start() -> Arc<Self> {
let spawner = Arc::new(Self {
owner: current_thread_id(),
admission: AtomicUsize::new(0),
notified: AtomicBool::new(false),
waker: AtomicWaker::new(),
queue: SegQueue::new(),
});
crate::spawn_detached(Drain(spawner.clone()));
spawner
}
pub(crate) fn is_owner(&self) -> bool {
Some(self.owner) == try_current_thread_id()
}
pub(crate) fn submit<F>(&self, job: Arc<Job<F>>)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
if !self.admit() {
job.close();
return;
}
self.queue.push(job);
self.admission.fetch_sub(1, Ordering::Release);
if !self.notified.swap(true, Ordering::AcqRel) {
self.waker.wake();
}
}
fn admit(&self) -> bool {
let mut state = self.admission.load(Ordering::Acquire);
loop {
if state & CLOSED != 0 {
return false;
}
match self.admission.compare_exchange_weak(
state,
state + 1,
Ordering::Acquire,
Ordering::Acquire,
) {
Ok(_) => return true,
Err(v) => state = v,
}
}
}
fn close(&self) {
self.admission.fetch_or(CLOSED, Ordering::AcqRel);
while self.admission.load(Ordering::Acquire) != CLOSED {
thread::yield_now();
}
while let Some(job) = self.queue.pop() {
job.close();
}
}
}
impl fmt::Debug for Spawner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Spawner")
.field("owner", &self.owner)
.finish_non_exhaustive()
}
}
struct Drain(Arc<Spawner>);
impl Future for Drain {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let spawner = &*self.0;
spawner.waker.register(cx.waker());
spawner.notified.swap(false, Ordering::AcqRel);
for _ in 0..BATCH {
match spawner.queue.pop() {
Some(job) => job.spawn_local(),
None => return Poll::Pending,
}
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
impl Drop for Drain {
fn drop(&mut self) {
self.0.close();
}
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::marker::PhantomPinned;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::task::{Wake, Waker};
use super::*;
#[derive(Default)]
struct Probe {
drops: AtomicUsize,
output_drops: AtomicUsize,
moved: AtomicBool,
wakes: AtomicUsize,
early_wake: AtomicBool,
}
impl Wake for Probe {
fn wake(self: Arc<Self>) {
self.wakes.fetch_add(1, Ordering::SeqCst);
self.early_wake
.fetch_or(self.drops.load(Ordering::SeqCst) == 0, Ordering::SeqCst);
}
}
struct Output(Arc<Probe>);
impl Drop for Output {
fn drop(&mut self) {
self.0.output_drops.fetch_add(1, Ordering::SeqCst);
}
}
struct PinnedFuture {
address: Cell<usize>,
probe: Arc<Probe>,
ready: bool,
panics: bool,
_pin: PhantomPinned,
}
impl Future for PinnedFuture {
type Output = Output;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Output> {
let this = self.as_ref().get_ref();
this.address.set(std::ptr::from_ref(this).addr());
if this.ready {
Poll::Ready(Output(this.probe.clone()))
} else {
Poll::Pending
}
}
}
impl Drop for PinnedFuture {
fn drop(&mut self) {
self.probe.moved.store(
self.address.get() != std::ptr::from_ref(self).addr(),
Ordering::SeqCst,
);
self.probe.drops.fetch_add(1, Ordering::SeqCst);
assert!(!self.panics, "future drop");
}
}
fn pinned_job(ready: bool, panics: bool) -> (Arc<Job<PinnedFuture>>, Arc<Probe>) {
let probe = Arc::new(Probe::default());
let job = Job::new(
PinnedFuture {
address: Cell::new(0),
probe: probe.clone(),
ready,
panics,
_pin: PhantomPinned,
},
true,
);
(job, probe)
}
#[test]
fn completion_drops_pinned_future_in_place() {
for panics in [false, true] {
let (job, probe) = pinned_job(true, panics);
let mut runner = Runner(job.clone());
let join = RemoteJoin::new(job.clone());
let waker = Waker::from(probe.clone());
let mut cx = Context::from_waker(&waker);
assert!(join.poll_join(&mut cx).is_pending());
let result = catch_unwind(AssertUnwindSafe(|| {
assert!(
Pin::new(&mut runner)
.poll(&mut Context::from_waker(Waker::noop()))
.is_ready()
);
}));
assert_eq!(result.is_err(), panics);
assert!(join.is_finished());
assert!(!probe.moved.load(Ordering::SeqCst));
assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
assert_eq!(probe.wakes.load(Ordering::SeqCst), 1);
assert!(!probe.early_wake.load(Ordering::SeqCst));
let Poll::Ready(Some(output)) = join.poll_join(&mut cx) else {
panic!("completed output lost");
};
assert!(matches!(join.poll_join(&mut cx), Poll::Ready(None)));
drop((runner, join, job));
assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
assert_eq!(probe.output_drops.load(Ordering::SeqCst), 0);
drop(output);
assert_eq!(probe.output_drops.load(Ordering::SeqCst), 1);
}
}
#[test]
fn cancellation_drops_pinned_future_in_place() {
for cancel in [false, true] {
for panics in [false, true] {
let (job, probe) = pinned_job(false, panics);
let mut runner = Runner(job.clone());
let join = RemoteJoin::new(job.clone());
let waker = Waker::from(probe.clone());
let mut cx = Context::from_waker(&waker);
assert!(join.poll_join(&mut cx).is_pending());
assert!(
Pin::new(&mut runner)
.poll(&mut Context::from_waker(Waker::noop()))
.is_pending()
);
let result = catch_unwind(AssertUnwindSafe(|| {
if cancel {
join.cancel();
assert!(
Pin::new(&mut runner)
.poll(&mut Context::from_waker(Waker::noop()))
.is_ready()
);
}
drop(runner);
}));
assert_eq!(result.is_err(), panics);
assert!(join.is_finished());
assert!(!probe.moved.load(Ordering::SeqCst));
assert!(matches!(join.poll_join(&mut cx), Poll::Ready(None)));
assert_eq!(probe.wakes.load(Ordering::SeqCst), 1);
assert!(!probe.early_wake.load(Ordering::SeqCst));
job.close();
drop((join, job));
assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
assert_eq!(probe.output_drops.load(Ordering::SeqCst), 0);
}
}
}
}