use crate::{
Active, Dispatch, ExitStatus,
config::Config,
continuation::{Continuation, Label, Puck as ContPuck, Share, erased::State, token},
error,
fsm::*,
ptr::{AsIrc, IntrusivelyCounted, Irc, IrcBox, IrcBoxed, Lease, LeasedMut},
simulator::{Prec, Sim},
};
use core::{
any::Any,
cell::{RefCell, RefMut},
fmt,
future::{Future, IntoFuture, Pending},
marker::PhantomData,
panic::Location,
pin::Pin,
task::{Context, Poll},
};
pub type Job<C, F, S = Unchecked> = BrandedJob<'static, C, F, S>;
#[pin_project::pin_project(PinnedDrop, !Unpin)]
pub struct BrandedJob<'brand, C: ?Sized + Config, F: Future, S> {
#[pin]
cont: Continuation<'brand, C>,
#[pin]
state: RefCell<Inner<F, S>>,
}
impl<C: ?Sized + Config, F: Future> Job<C, F> {
#[track_caller]
pub fn new<'p, A>(actions: A) -> Lease<'p, Self>
where
A: IntoFuture<IntoFuture = F>,
{
Job::build().with_actions(actions).finish()
}
}
impl Job<(), Pending<()>> {
pub const fn build() -> Builder {
Builder::new()
}
}
impl<C: ?Sized + Config, F: Future, S: Settle<F::Output>> Job<C, F, S> {
pub(crate) fn boot<'p>(
this: Pin<LeasedMut<'p, Self>>,
share: Pin<&'p Share<C>>,
) -> Puck<'p, C, F, S> {
let mut this = Irc::new(this);
unsafe {
let vptr = Irc::into_raw(this);
this = Irc::from_raw(vptr);
this.cont.set_vptr(vptr);
}
this.get_pin_mut().unwrap().brand(move |job, once| {
let born = job.token(once).into_born().unwrap();
unsafe { job.bind(born, share.get_ref()) };
});
Puck(this, PhantomData)
}
}
impl<'brand, C: ?Sized + Config, F: Future, S> BrandedJob<'brand, C, F, S> {
pub fn set_prec(&self, prec: Prec) {
self.cont.set_prec(prec);
}
pub fn state(&self) -> State {
self.cont.state().borrow().erased()
}
pub fn result(&self) -> Result<F::Output, error::NotDone> {
self.brand(|job, once| Ok(job.inner_result(job.token(once).into_done()?).1))
}
pub fn abort(self: Pin<&Self>) {
self.brand(|job, once| {
job.inner_abort(job.token(once));
});
}
pub(crate) fn abort_on_drop(self: Pin<&Self>) {
self.cont.clear_vptr();
self.abort();
}
pub fn token(&self, once: Ephemeral<'brand>) -> token::State<'brand> {
self.cont.token(once)
}
pub fn finalizer(self: Pin<&mut Self>, born: &token::Born<'brand>) -> &mut S {
let _ = born;
match self.inner_state_mut().project() {
InnerProject::Pending(_, settle) => settle,
_ => unsafe { core::hint::unreachable_unchecked() },
}
}
fn inner_abort(self: Pin<&Self>, state: token::State<'brand>) -> token::Gone<'brand> {
use scopeguard::{ScopeGuard, guard};
use token::State::*;
match state {
Gone(gone) => return gone,
Done(done) => return self.inner_result(done).0,
_ => {}
};
let to_gone = |state: token::State<'brand>| -> token::Gone<'brand> {
let sm = self.cont.state();
let rc = Err(crate::Failure);
let gone = match state {
Born(born) => sm.transition(born, rc),
Idle(idle) => sm.transition(idle, rc),
Next(next) => sm.transition(self.cont.deschedule(next), rc),
Busy(busy) => sm.transition(self.cont.deactivate(busy), rc),
_ => unsafe { core::hint::unreachable_unchecked() },
};
self.cont.wake_pending();
gone
};
let guard = guard(state, |state| {
to_gone(state);
});
let _span = self.cont.enter_span();
self.inner_state().as_mut().abort();
to_gone(ScopeGuard::into_inner(guard))
}
fn inner_result(&self, done: token::Done<'brand>) -> (token::Gone<'brand>, F::Output) {
let task = &self.cont;
let exit_status = task.branded_result(&done);
let output = self
.state
.borrow_mut()
.result()
.expect("Job::result: Result missing in Done state");
let _span = task.enter_span();
let gone = task.state().transition(done, exit_status);
task.wake_pending();
(gone, output)
}
fn inner_state(self: Pin<&Self>) -> Pin<RefMut<'_, Inner<F, S>>> {
unsafe { Pin::new_unchecked(self.get_ref().state.borrow_mut()) }
}
fn inner_state_mut(self: Pin<&mut Self>) -> Pin<&mut Inner<F, S>> {
unsafe { self.map_unchecked_mut(|job| job.state.get_mut()) }
}
pub(crate) unsafe fn bind(
self: Pin<&mut Self>,
born: token::Born<'brand>,
share: &Share<C>,
) -> token::Idle<'brand> {
unsafe { self.project().cont.bind(born, share) }
}
}
impl<'b, C, F, S> Stateful for BrandedJob<'b, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
type Brand = &'b ();
unsafe fn enter(&self) {
unsafe {
self.cont.enter();
}
}
unsafe fn leave(&self) {
unsafe {
self.cont.leave();
}
}
}
impl<'b, C, F, S> Rebrand<'b> for BrandedJob<'b, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
type Kind<'a> = BrandedJob<'a, C, F, S>;
}
impl<C, F, S> fmt::Debug for BrandedJob<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::any::type_name;
f.debug_struct("Job")
.field("cont", &self.cont)
.field("future_type", &type_name::<F>())
.field("settle_type", &type_name::<S>())
.finish()
}
}
impl<C, F, S> Dispatch for BrandedJob<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
S: Settle<F::Output>,
{
fn poll(self: Pin<&Self>, cx: &mut Context<'_>) -> Poll<ExitStatus> {
let mut inner = self.inner_state();
inner
.as_mut()
.poll(cx)
.map(|value| inner.as_mut().ready(value))
}
}
impl<C, F, S> Active<C> for Job<C, F, S>
where
C: ?Sized + Config,
F: Future,
S: Settle<F::Output>,
{
type Output = F::Output;
type Puck<'p>
= Puck<'p, C, F, S>
where
Self: 'p;
fn bind<'p>(this: Pin<LeasedMut<'p, Self>>, sctx: &'p Share<C>) -> Self::Puck<'p> {
let mut irc = Irc::new(this);
unsafe {
let vptr = Irc::into_raw(irc);
irc = Irc::from_raw(vptr);
irc.cont.set_vptr(vptr);
}
irc.get_pin_mut().unwrap().brand(move |mut job, once| {
let born_token = job
.token(once)
.into_born()
.expect("Job::bind called on non-Born job");
unsafe {
job.as_mut().bind(born_token, sctx);
}
});
Puck(irc, PhantomData)
}
}
unsafe impl<C, F, S> IntrusivelyCounted for BrandedJob<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn irc_box(&self) -> &IrcBox<dyn IrcBoxed> {
self.cont.irc_box()
}
}
impl<'brand, C, F, S> AsRef<Continuation<'brand, C>> for BrandedJob<'brand, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn as_ref(&self) -> &Continuation<'brand, C> {
&self.cont
}
}
#[pin_project::pinned_drop]
impl<C: ?Sized + Config, F: Future, T> PinnedDrop for BrandedJob<'_, C, F, T> {
fn drop(self: Pin<&mut Self>) {
self.into_ref().abort_on_drop();
}
}
pub struct Puck<'p, C: ?Sized + Config, F: Future, S = Unchecked>(
Irc<Job<C, F, S>>,
PhantomDrop<Pin<&'p mut Job<C, F, S>>>,
);
impl<C: ?Sized + Config, F: Future, S> Puck<'_, C, F, S> {
pub fn share(&self) -> &Share<C> {
self.0
.cont
.share()
.expect("Puck::share called on unbound Job")
}
pub fn abort(self) {
self.0.get_pin().abort();
}
}
impl<C, F, S> fmt::Debug for Puck<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Puck").field(&self.0).finish()
}
}
impl<C, F, S> IntoFuture for Puck<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
type Output = F::Output;
type IntoFuture = crate::ops::Join<C, Self>;
#[inline]
fn into_future(self) -> Self::IntoFuture {
crate::ops::join(self)
}
}
impl<C, F, S> crate::Puck<C> for Puck<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn result(&mut self) -> Option<Self::Output> {
self.0.result().ok()
}
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.cont.time()
}
fn rank(&self) -> C::Rank {
self.share().rank()
}
fn prec(&self) -> Prec {
self.0.cont.prec()
}
fn state(&self) -> State {
self.0.cont.state().borrow().erased()
}
fn location(&self) -> &'static Location<'static> {
self.0.cont.location()
}
fn puck(&self) -> ContPuck<C> {
ContPuck::checked(self.as_irc()).expect("Puck::puck called on unbound Job")
}
}
impl<C, F, S> From<Puck<'_, C, F, S>> for Irc<Job<C, F, S>>
where
C: ?Sized + Config,
F: Future,
{
fn from(value: Puck<'_, C, F, S>) -> Self {
value.0
}
}
impl<C, F, S> AsRef<Continuation<'static, C>> for Puck<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn as_ref(&self) -> &Continuation<'static, C> {
&self.0.cont
}
}
impl<C, F, S> AsIrc<Continuation<'static, C>> for Puck<'_, C, F, S>
where
C: ?Sized + Config,
F: Future,
{
fn as_irc(&self) -> Irc<Continuation<'static, C>> {
Irc::map(self.0.clone(), |inner| &inner.cont)
}
}
#[pin_project::pin_project(
project = InnerProject,
project_replace = InnerOwn,
)]
enum Inner<F: Future, S> {
Pending(#[pin] F, S),
Ready(Option<F::Output>),
}
impl<F: Future, S> Inner<F, S> {
const fn new(actions: F, settle: S) -> Self {
Inner::Pending(actions, settle)
}
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
match self.project() {
InnerProject::Pending(future, _) => future.poll(cx),
_ => panic!("attempted to poll a terminated job"),
}
}
fn abort(mut self: Pin<&mut Self>) {
self.set(Inner::Ready(None));
}
fn result(&mut self) -> Option<F::Output> {
if let Inner::Ready(result) = self {
result.take()
} else {
None
}
}
fn ready(mut self: Pin<&mut Self>, result: F::Output) -> ExitStatus
where
S: Settle<F::Output>,
{
match self.as_mut().project_replace(Inner::Ready(Some(result))) {
InnerOwn::Pending(_, settle) => match self.project() {
InnerProject::Ready(Some(result)) => settle.settle(result),
_ => unsafe { core::hint::unreachable_unchecked() },
},
_ => panic!("terminating an already terminated job"),
}
}
}
pub trait Settle<R> {
fn settle(self, result: &mut R) -> ExitStatus;
}
impl<R, F> Settle<R> for F
where
F: FnOnce(&mut R) -> ExitStatus,
{
fn settle(self, result: &mut R) -> ExitStatus {
self(result)
}
}
pub struct Unchecked;
impl<R> Settle<R> for Unchecked {
fn settle(self, _result: &mut R) -> ExitStatus {
Ok(crate::Success)
}
}
pub struct Checked;
impl Settle<bool> for Checked {
fn settle(self, result: &mut bool) -> ExitStatus {
if *result {
Ok(crate::Success)
} else {
Err(crate::Failure)
}
}
}
impl<R, T> Settle<Result<R, T>> for Checked {
fn settle(self, result: &mut Result<R, T>) -> ExitStatus {
if result.is_ok() {
Ok(crate::Success)
} else {
Err(crate::Failure)
}
}
}
impl<T> Settle<Option<T>> for Checked {
fn settle(self, result: &mut Option<T>) -> ExitStatus {
if result.is_some() {
Ok(crate::Success)
} else {
Err(crate::Failure)
}
}
}
pub struct Builder<const R: bool = false, F = (), S = Unchecked> {
future: F,
location: Option<&'static Location<'static>>,
finalizer: S,
precedence: Prec,
}
impl Builder {
pub const fn new() -> Self {
Builder {
future: (),
location: None,
finalizer: Unchecked,
precedence: Prec::new(),
}
}
pub const fn root() -> Builder<true> {
Builder {
future: (),
location: None,
finalizer: Unchecked,
precedence: Prec::new(),
}
}
}
impl<const R: bool, S> Builder<R, (), S> {
#[track_caller]
pub fn with_actions<F: IntoFuture>(self, future: F) -> Builder<R, F, S> {
let Builder {
location,
finalizer,
precedence,
..
} = self;
Builder {
future,
location: Some(location.unwrap_or(Location::caller())),
finalizer,
precedence,
}
}
}
impl<const R: bool, F> Builder<R, F> {
pub fn with_finalizer<S>(self, finalizer: S) -> Builder<R, F, S> {
let Builder {
future,
location,
precedence,
..
} = self;
Builder {
future,
location,
finalizer,
precedence,
}
}
pub fn checked(self) -> Builder<R, F, Checked> {
self.with_finalizer(Checked)
}
}
impl<const R: bool, F, S> Builder<R, F, S> {
pub const fn with_precedence(mut self, precedence: Prec) -> Self {
self.precedence = precedence;
self
}
pub const fn with_location(mut self, location: &'static Location<'static>) -> Self {
self.location = Some(location);
self
}
}
impl<const R: bool, F: IntoFuture, S: Settle<F::Output>> Builder<R, F, S> {
pub fn finish<'p, C>(self) -> Lease<'p, Job<C, F::IntoFuture, S>>
where
C: ?Sized + Config,
{
let location = self.location.unwrap();
#[cfg(feature = "tracing")]
let _span = if R {
tracing::Span::current()
} else {
tracing::error_span!("Job", line = location.line()).or_current()
}
.entered();
Lease::new(Job {
cont: Continuation::new(self.precedence, location),
state: RefCell::new(Inner::new(self.future.into_future(), self.finalizer)),
})
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
type PhantomDrop<T> = PhantomData<PhantomDropInner<T>>;
struct PhantomDropInner<T: ?Sized>(T);
impl<T: ?Sized> Drop for PhantomDropInner<T> {
fn drop(&mut self) {}
}