#![allow(dead_code)]
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use crate::platform::current::runtime::{
ThreadHandle, current_thread_handle, queue_microtask, queue_task,
};
#[cfg(test)]
use std::cell::Cell;
#[cfg(test)]
thread_local! {
pub(crate) static LOCAL_WAKE_COUNT: Cell<u64> = const { Cell::new(0) };
pub(crate) static REMOTE_WAKE_COUNT: Cell<u64> = const { Cell::new(0) };
}
type CancelCallback = Box<dyn FnOnce() + Send + 'static>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum WakeClass {
Microtask,
Macrotask,
}
struct CompletionState<T> {
owner: ThreadHandle,
wake_class: WakeClass,
interested: AtomicBool,
finished: AtomicBool,
wake_queued: AtomicBool,
result: Mutex<Option<T>>,
waker: Mutex<Option<Waker>>,
cancel: Mutex<Option<CancelCallback>>,
}
impl<T: Send + 'static> CompletionState<T> {
fn queue_wake(self: &Arc<Self>) {
if self.wake_queued.swap(true, Ordering::AcqRel) {
return;
}
if self.owner.is_current() {
#[cfg(test)]
LOCAL_WAKE_COUNT.with(|c| c.set(c.get() + 1));
let state = Arc::clone(self);
let wake = move || {
state.wake_queued.store(false, Ordering::Release);
if let Some(waker) = state.waker.lock().unwrap().take() {
waker.wake();
}
};
match self.wake_class {
WakeClass::Microtask => queue_microtask(wake),
WakeClass::Macrotask => queue_task(wake),
}
return;
}
#[cfg(test)]
REMOTE_WAKE_COUNT.with(|c| c.set(c.get() + 1));
let state = Arc::clone(self);
if self
.owner
.queue_internal_wake(move || {
state.wake_queued.store(false, Ordering::Release);
if let Some(waker) = state.waker.lock().unwrap().take() {
waker.wake();
}
})
.is_err()
{
self.wake_queued.store(false, Ordering::Release);
}
}
}
pub(crate) struct CompletionFuture<T> {
state: Arc<CompletionState<T>>,
}
pub(crate) struct CompletionHandle<T> {
state: Arc<CompletionState<T>>,
}
impl<T> Clone for CompletionHandle<T> {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
}
}
}
pub(crate) fn completion<T: Send + 'static>(
owner: ThreadHandle,
wake_class: WakeClass,
) -> (CompletionFuture<T>, CompletionHandle<T>) {
owner.begin_async_operation();
let state = Arc::new(CompletionState {
owner,
wake_class,
interested: AtomicBool::new(true),
finished: AtomicBool::new(false),
wake_queued: AtomicBool::new(false),
result: Mutex::new(None),
waker: Mutex::new(None),
cancel: Mutex::new(None),
});
(
CompletionFuture {
state: Arc::clone(&state),
},
CompletionHandle { state },
)
}
pub(crate) fn completion_for_current_thread<T: Send + 'static>()
-> (CompletionFuture<T>, CompletionHandle<T>) {
completion(current_thread_handle(), WakeClass::Macrotask)
}
impl<T: Send + 'static> CompletionHandle<T> {
pub(crate) fn complete(self, value: T) {
self.finish(Some(value));
}
pub(crate) fn finish(self, value: Option<T>) {
if self.state.finished.swap(true, Ordering::AcqRel) {
return;
}
let interested = self.state.interested.load(Ordering::Acquire);
if interested {
*self.state.result.lock().unwrap() = value;
self.state.queue_wake();
}
let _ = self.state.cancel.lock().unwrap().take();
self.state.owner.finish_async_operation();
}
pub(crate) fn set_cancel(&self, cancel: impl FnOnce() + Send + 'static) {
*self.state.cancel.lock().unwrap() = Some(Box::new(cancel));
}
pub(crate) fn is_interested(&self) -> bool {
self.state.interested.load(Ordering::Acquire)
}
}
impl<T> Future for CompletionFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(value) = self.state.result.lock().unwrap().take() {
return Poll::Ready(value);
}
*self.state.waker.lock().unwrap() = Some(cx.waker().clone());
if let Some(value) = self.state.result.lock().unwrap().take() {
let _ = self.state.waker.lock().unwrap().take();
return Poll::Ready(value);
}
Poll::Pending
}
}
impl<T> Drop for CompletionFuture<T> {
fn drop(&mut self) {
if !self.state.interested.swap(false, Ordering::AcqRel) {
return;
}
let _ = self.state.result.lock().unwrap().take();
let _ = self.state.waker.lock().unwrap().take();
if self.state.finished.load(Ordering::Acquire) {
return;
}
let cancel = self.state.cancel.lock().unwrap().take();
if let Some(cancel) = cancel {
cancel();
} else {
if !self.state.finished.swap(true, Ordering::AcqRel) {
self.state.owner.finish_async_operation();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
use crate::{queue_macrotask, run, spawn};
#[test]
fn local_completion_uses_local_macrotask_path() {
LOCAL_WAKE_COUNT.with(|c| c.set(0));
REMOTE_WAKE_COUNT.with(|c| c.set(0));
let observed = Arc::new(Mutex::new(None::<i32>));
{
let observed = Arc::clone(&observed);
queue_macrotask(move || {
let (future, handle) = completion_for_current_thread::<i32>();
spawn(async move {
let value = future.await;
*observed.lock().unwrap() = Some(value);
});
handle.complete(42);
});
}
run();
assert_eq!(*observed.lock().unwrap(), Some(42));
let local = LOCAL_WAKE_COUNT.with(|c| c.get());
let remote = REMOTE_WAKE_COUNT.with(|c| c.get());
assert_eq!(
local, 1,
"expected exactly one local macrotask wake on this thread, got {local}"
);
assert_eq!(
remote, 0,
"local completion must not hit the cross-thread macrotask path, \
got {remote} remote wakes on this thread"
);
}
#[test]
fn cross_thread_completion_uses_macrotask_path() {
let observed = Arc::new(Mutex::new(None::<i32>));
let worker_local = Arc::new(std::sync::atomic::AtomicU64::new(0));
let worker_remote = Arc::new(std::sync::atomic::AtomicU64::new(0));
let worker_handle = Arc::new(Mutex::new(None::<std::thread::JoinHandle<()>>));
{
let observed = Arc::clone(&observed);
let worker_local = Arc::clone(&worker_local);
let worker_remote = Arc::clone(&worker_remote);
let worker_handle = Arc::clone(&worker_handle);
queue_macrotask(move || {
let (future, handle) = completion_for_current_thread::<i32>();
let join = std::thread::spawn(move || {
LOCAL_WAKE_COUNT.with(|c| c.set(0));
REMOTE_WAKE_COUNT.with(|c| c.set(0));
handle.complete(7);
worker_local.store(
LOCAL_WAKE_COUNT.with(|c| c.get()),
std::sync::atomic::Ordering::Release,
);
worker_remote.store(
REMOTE_WAKE_COUNT.with(|c| c.get()),
std::sync::atomic::Ordering::Release,
);
});
*worker_handle.lock().unwrap() = Some(join);
spawn(async move {
let value = future.await;
*observed.lock().unwrap() = Some(value);
});
});
}
run();
worker_handle
.lock()
.unwrap()
.take()
.unwrap()
.join()
.unwrap();
assert_eq!(*observed.lock().unwrap(), Some(7));
let local_on_worker = worker_local.load(std::sync::atomic::Ordering::Acquire);
let remote_on_worker = worker_remote.load(std::sync::atomic::Ordering::Acquire);
assert_eq!(
remote_on_worker, 1,
"expected exactly one remote macrotask wake on the worker, got {remote_on_worker}"
);
assert_eq!(
local_on_worker, 0,
"cross-thread completion must not hit the local same-thread path, \
got {local_on_worker} local wakes on the worker"
);
}
#[test]
fn is_current_reflects_owner_thread() {
let handle = current_thread_handle();
assert!(
handle.is_current(),
"handle of current thread must be current"
);
let h = handle.clone();
let result = std::thread::spawn(move || h.is_current()).join().unwrap();
assert!(
!result,
"handle must not be reported as current on a non-runtime thread"
);
}
}