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)]
#[path = "../tests/unit/remote.rs"]
mod tests;