use crate::{
ExitStatus,
config::Config,
continuation::{Continuation, Label, Share, erased::State, token},
error::NotInit,
fsm::*,
ptr::AsIrc,
ptr::Irc,
simulator::{Prec, Sim},
};
use core::{
any::Any,
fmt,
future::IntoFuture,
mem::ManuallyDrop,
panic::Location,
ptr::NonNull,
task::{RawWaker, RawWakerVTable},
};
pub struct Puck<C: ?Sized + Config>(Irc<Continuation<'static, C>>);
impl<C: ?Sized + Config> Puck<C> {
pub(crate) fn new<'b, T>(task: Irc<Continuation<'b, C>>, _: &T) -> Self
where
T: Into<token::Init<'b>>,
{
Puck(Irc::map(task, Continuation::detach))
}
pub(crate) fn checked(task: Irc<Continuation<'static, C>>) -> Result<Self, NotInit> {
let state = task.state().erased();
state.is_init().then(|| Puck(task)).ok_or(NotInit(state))
}
pub(crate) fn into_inner(self) -> Irc<Continuation<'static, C>> {
self.0
}
pub(crate) fn share(&self) -> &Share<C> {
unsafe { self.0.share().unwrap_unchecked() }
}
pub(crate) fn is_same(&self, other: &Continuation<'_, C>) -> bool {
core::ptr::eq(&*self.0, other.detach())
}
}
impl<C: Config> Puck<C> {
const VTABLE: RawWakerVTable = RawWakerVTable::new(
Self::waker_clone,
Self::waker_wake,
Self::waker_wake_by_ref,
Self::waker_drop,
);
pub(crate) fn into_waker(self) -> RawWaker {
RawWaker::new(Irc::into_raw(self.0).cast().as_ptr(), &Self::VTABLE)
}
unsafe fn waker_clone(task: *const ()) -> RawWaker {
let task_ptr = NonNull::new(task as *mut Continuation<'static, C>).unwrap();
let task = unsafe { Irc::from_raw(task_ptr) };
if !unsafe { task.is_same_thread() } {
abort();
}
let puck = ManuallyDrop::new(Puck(task));
ManuallyDrop::into_inner(puck.clone()).into_waker()
}
unsafe fn waker_wake(task: *const ()) {
let task_ptr = NonNull::new(task as *mut Continuation<'static, C>).unwrap();
let task = unsafe { Irc::from_raw(task_ptr) };
if !unsafe { task.is_same_thread() } {
abort();
}
Continuation::wake(task).ok();
}
unsafe fn waker_wake_by_ref(task: *const ()) {
let task_ptr = NonNull::new(task as *mut Continuation<'static, C>).unwrap();
let task = ManuallyDrop::new(unsafe { Irc::from_raw(task_ptr) });
if !unsafe { task.is_same_thread() } {
abort();
}
Continuation::wake((*task).clone()).ok();
}
unsafe fn waker_drop(task: *const ()) {
let task_ptr = NonNull::new(task as *mut Continuation<'static, C>).unwrap();
let task = unsafe { Irc::from_raw(task_ptr) };
if !unsafe { task.is_same_thread() } {
abort();
}
}
}
impl<C: ?Sized + Config> crate::Puck<C> for Puck<C> {
fn result(&mut self) -> Option<ExitStatus> {
self.0.result()
}
fn subject(&self) -> &dyn Any {
self.share().subject()
}
fn sim(&self) -> &Sim<C> {
self.share().sim()
}
fn label(&self) -> Label {
self.share().label()
}
fn time(&self) -> Option<C::Time> {
self.0.time()
}
fn rank(&self) -> C::Rank {
self.share().rank()
}
fn prec(&self) -> Prec {
self.0.prec()
}
fn state(&self) -> State {
self.0.state().borrow().erased()
}
fn location(&self) -> &'static Location<'static> {
self.0.location()
}
fn puck(&self) -> Puck<C> {
self.clone()
}
}
impl<C: ?Sized + Config> AsRef<Continuation<'static, C>> for Puck<C> {
fn as_ref(&self) -> &Continuation<'static, C> {
&self.0
}
}
impl<C: ?Sized + Config> AsIrc<Continuation<'static, C>> for Puck<C> {
fn as_irc(&self) -> Irc<Continuation<'static, C>> {
self.0.clone()
}
}
impl<C: ?Sized + Config> IntoFuture for Puck<C> {
type Output = ExitStatus;
type IntoFuture = crate::ops::Join<C, Self>;
fn into_future(self) -> Self::IntoFuture {
crate::ops::join(self)
}
}
impl<C: ?Sized + Config> fmt::Debug for Puck<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<C: ?Sized + Config> Clone for Puck<C> {
fn clone(&self) -> Self {
Puck(self.0.clone())
}
}
impl<C: ?Sized + Config> PartialEq for Puck<C> {
fn eq(&self, other: &Self) -> bool {
self.is_same(&other.0)
}
}
impl<C: ?Sized + Config> Eq for Puck<C> {}
extern "C" fn abort() -> ! {
panic!("attempted to use a `Waker` from a different thread");
}
#[cfg(test)]
mod tests {
use crate::{
job::Job,
ops::{defer, waker},
simulator::{Sim, simulation},
};
use std::{pin::pin, sync::mpsc::channel, thread};
#[test]
#[ignore = "requires abort test runner"]
fn move_waker_across_thread() {
simulation(async |sim: &Sim| {
let waker = waker().await;
let job = pin!(Job::new(async move {
let handle = thread::spawn(move || {
waker.wake();
});
assert!(handle.join().is_err());
}));
sim.activate(job).await;
})
.expect("unexpected deadlock");
}
#[test]
#[ignore = "requires abort test runner"]
#[should_panic = "attempted to use a `Waker` from a different thread"]
fn move_waker_across_thread_channel() {
let (sx, rx) = channel();
thread::spawn(move || {
simulation(async |_: &Sim| {
let waker = waker().await;
sx.send(waker).unwrap();
loop {
defer().await;
}
})
.expect("unexpected deadlock");
});
let waker = rx.recv().unwrap();
waker.wake();
}
#[test]
#[ignore = "requires abort test runner"]
fn move_waker_across_thread_race() {
let (sx, rx) = channel();
thread::spawn(move || {
simulation(async |_: &Sim| {
let waker = waker().await;
sx.send(waker).unwrap();
})
.expect("unexpected deadlock");
});
let waker = rx.recv().unwrap();
waker.wake();
}
}