use std::{future::Future, pin::Pin, sync::Arc, sync::atomic::AtomicUsize, sync::atomic::Ordering};
use std::{task::Context, task::Poll};
static mut CBS: Option<Arc<dyn CallbacksApi>> = None;
static STATE: AtomicUsize = AtomicUsize::new(0);
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
trait CallbacksApi {
fn before(&self) -> Option<*const ()>;
fn enter(&self, _: *const ()) -> *const ();
fn exit(&self, _: *const ());
fn after(&self, _: *const ());
}
#[allow(clippy::struct_field_names)]
struct Callbacks<A, B, C, D> {
f_before: A,
f_enter: B,
f_exit: C,
f_after: D,
}
impl<A, B, C, D> CallbacksApi for Callbacks<A, B, C, D>
where
A: Fn() -> Option<*const ()> + 'static,
B: Fn(*const ()) -> *const () + 'static,
C: Fn(*const ()) + 'static,
D: Fn(*const ()) + 'static,
{
fn before(&self) -> Option<*const ()> {
(self.f_before)()
}
fn enter(&self, d: *const ()) -> *const () {
(self.f_enter)(d)
}
fn exit(&self, d: *const ()) {
(self.f_exit)(d);
}
fn after(&self, d: *const ()) {
(self.f_after)(d);
}
}
pub(crate) struct Data {
cb: &'static dyn CallbacksApi,
ptr: *const (),
}
impl Data {
#[allow(clippy::if_not_else)]
pub(crate) fn load() -> Option<Data> {
let cb = if STATE.load(Ordering::Acquire) != INITIALIZED {
None
} else {
#[allow(static_mut_refs)]
unsafe {
Some(CBS.as_ref().map(AsRef::as_ref).unwrap())
}
};
if let Some(cb) = cb
&& let Some(ptr) = cb.before()
{
return Some(Data { cb, ptr });
}
None
}
pub(crate) fn run<F, R>(&mut self, f: F) -> R
where
F: FnOnce() -> R,
{
struct Exit(&'static dyn CallbacksApi, *const ());
impl Drop for Exit {
fn drop(&mut self) {
self.0.exit(self.1);
}
}
let _exit = Exit(self.cb, self.cb.enter(self.ptr));
f()
}
}
pub(crate) fn wrap<F: Future>(fut: F) -> impl Future<Output = F::Output> {
let mut data = Data::load();
async move {
let mut f = std::pin::pin!(fut);
std::future::poll_fn(|cx| match data.as_mut() {
Some(data) => data.run(|| f.as_mut().poll(cx)),
None => f.as_mut().poll(cx),
})
.await
}
}
impl Drop for Data {
fn drop(&mut self) {
self.cb.after(self.ptr);
}
}
#[track_caller]
pub fn spawn_sendable<F>(fut: F) -> SendableJoinHandle<F::Output>
where
F: Future + 'static,
F::Output: Send,
{
let (tx, rx) = oneshot::async_channel();
let (cancel, canceled) = oneshot::async_channel::<()>();
crate::spawn_detached(async move {
let output = {
let mut fut = std::pin::pin!(fut);
let mut canceled = Some(canceled);
std::future::poll_fn(|cx| {
if let Some(rx) = &mut canceled {
match Pin::new(rx).poll(cx) {
Poll::Ready(Ok(())) => return Poll::Ready(None),
Poll::Ready(Err(_)) => canceled = None,
Poll::Pending => {}
}
}
fut.as_mut().poll(cx).map(Some)
})
.await
};
if let Some(output) = output {
let _ = tx.send(output);
}
});
SendableJoinHandle {
rx,
cancel: Some(cancel),
cancel_on_drop: false,
}
}
#[derive(Debug)]
pub struct SendableJoinHandle<T> {
rx: oneshot::AsyncReceiver<T>,
cancel: Option<oneshot::Sender<()>>,
cancel_on_drop: bool,
}
impl<T> SendableJoinHandle<T> {
#[must_use]
pub fn cancel_on_drop(mut self) -> Self {
self.cancel_on_drop = true;
self
}
pub fn cancel(mut self) {
self.cancel_on_drop = true;
}
pub fn is_finished(&self) -> bool {
self.rx.has_message() || self.rx.is_closed()
}
}
impl<T> Drop for SendableJoinHandle<T> {
fn drop(&mut self) {
if self.cancel_on_drop
&& let Some(cancel) = self.cancel.take()
{
let _ = cancel.send(());
}
}
}
impl<T> Future for SendableJoinHandle<T> {
type Output = Result<T, crate::JoinError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.rx)
.poll(cx)
.map(|result| result.map_err(|_| crate::JoinError))
}
}
pub unsafe fn task_callbacks<FBefore, FEnter, FExit, FAfter>(
f_before: FBefore,
f_enter: FEnter,
f_exit: FExit,
f_after: FAfter,
) where
FBefore: Fn() -> Option<*const ()> + 'static + Sync,
FEnter: Fn(*const ()) -> *const () + 'static + Sync,
FExit: Fn(*const ()) + 'static + Sync,
FAfter: Fn(*const ()) + 'static + Sync,
{
let new = Arc::new(Callbacks {
f_before,
f_enter,
f_exit,
f_after,
});
let _ = set_cbs(new);
}
pub unsafe fn task_opt_callbacks<FBefore, FEnter, FExit, FAfter>(
f_before: FBefore,
f_enter: FEnter,
f_exit: FExit,
f_after: FAfter,
) -> bool
where
FBefore: Fn() -> Option<*const ()> + Sync + 'static,
FEnter: Fn(*const ()) -> *const () + Sync + 'static,
FExit: Fn(*const ()) + Sync + 'static,
FAfter: Fn(*const ()) + Sync + 'static,
{
let new = Arc::new(Callbacks {
f_before,
f_enter,
f_exit,
f_after,
});
set_cbs(new).is_ok()
}
fn set_cbs(cbs: Arc<dyn CallbacksApi>) -> Result<(), ()> {
match STATE.compare_exchange(
UNINITIALIZED,
INITIALIZING,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(UNINITIALIZED) => {
unsafe {
CBS = Some(cbs);
}
STATE.store(INITIALIZED, Ordering::Release);
Ok(())
}
Err(INITIALIZING) => {
while STATE.load(Ordering::Relaxed) == INITIALIZING {
std::hint::spin_loop();
}
Err(())
}
_ => Err(()),
}
}
#[cfg(test)]
#[path = "../tests/unit/task.rs"]
mod tests;