extern crate alloc; use std::alloc::{dealloc, Layout}; use std::future::Future; use std::mem::ManuallyDrop; use std::panic::{RefUnwindSafe, UnwindSafe}; use crate::loom_exports::sync::atomic::{self, Ordering}; use super::runnable::Runnable; use super::util::{runnable_exists, RunOnDrop}; use super::Task; use super::{CLOSED, POLLING, REF_INC, REF_MASK}; /// Virtual table for a `Promise`. #[derive(Debug)] struct VTable { poll: unsafe fn(*const ()) -> Stage, drop: unsafe fn(*const ()), } /// Retrieves the output of the task if ready. unsafe fn poll(ptr: *const ()) -> Stage where F: Future + Send + 'static, F::Output: Send + 'static, S: Fn(Runnable, T) + Send + Sync + 'static, T: Clone + Send + Sync + 'static, { let this = &*(ptr as *const Task); // Set the `CLOSED` flag if the task is in the `Completed` phase. // // Ordering: Acquire ordering is necessary to synchronize with the // operation that modified or dropped the future or output. This ensures // that the newest state of the output is visible before it is moved // out, or that the future can be safely dropped when the promised is // dropped if the promise is the last reference to the task. let state = this .state .fetch_update(Ordering::Acquire, Ordering::Relaxed, |s| { if s & (POLLING | CLOSED) == 0 { Some(s | CLOSED) } else { None } }); if let Err(s) = state { if s & CLOSED == CLOSED { // The task is either in the `Wind-down` or `Closed` phase. return Stage::Cancelled; } else { // The task is in the `Polling` phase. return Stage::Pending; }; } let output = this.core.with_mut(|c| ManuallyDrop::take(&mut (*c).output)); Stage::Ready(output) } /// Drops the promise. unsafe fn drop(ptr: *const ()) where F: Future + Send + 'static, F::Output: Send + 'static, S: Fn(Runnable, T) + Send + Sync + 'static, T: Clone + Send + Sync + 'static, { let this = &*(ptr as *const Task); // Decrement the reference count. // // Ordering: Release ordering is necessary to ensure that if the output // was moved out by using `poll`, then the move has completed when the // last reference deallocates the task. let state = this.state.fetch_sub(REF_INC, Ordering::Release); // Deallocate the task if this token was the last reference to the task. if state & REF_MASK == REF_INC && !runnable_exists(state) { // Ensure that the newest state of the future or output is visible // before it is dropped. // // Ordering: Acquire ordering is necessary to synchronize with the // Release ordering in all previous reference count decrements // and/or in the wake count reset (the latter is equivalent to a // reference count decrement for a `Runnable`). atomic::fence(Ordering::Acquire); // Set a drop guard to ensure that the task is deallocated whether // or not the `core` member panics when dropped. let _drop_guard = RunOnDrop::new(|| { dealloc(ptr as *mut u8, Layout::new::>()); }); if state & POLLING == POLLING { this.core.with_mut(|c| ManuallyDrop::drop(&mut (*c).future)); } else if state & CLOSED == 0 { this.core.with_mut(|c| ManuallyDrop::drop(&mut (*c).output)); } // Else the `CLOSED` flag is set but the `POLLING` flag is cleared // so the future was already dropped. } } /// The stage of progress of a promise. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) enum Stage { /// The task has completed. Ready(T), /// The task is still being processed. Pending, /// The task has been cancelled. Cancelled, } impl Stage { /// Maps a `Stage` to `Stage` by applying a function to a contained value. #[allow(unused)] pub(crate) fn map(self, f: F) -> Stage where F: FnOnce(U) -> V, { match self { Stage::Ready(t) => Stage::Ready(f(t)), Stage::Pending => Stage::Pending, Stage::Cancelled => Stage::Cancelled, } } /// Returns `true` if the promise is a [`Stage::Ready`] value. #[allow(unused)] #[inline] pub(crate) fn is_ready(&self) -> bool { matches!(*self, Stage::Ready(_)) } /// Returns `true` if the promise is a [`Stage::Pending`] value. #[allow(unused)] #[inline] pub(crate) fn is_pending(&self) -> bool { matches!(*self, Stage::Pending) } /// Returns `true` if the promise is a [`Stage::Cancelled`] value. #[allow(unused)] #[inline] pub(crate) fn is_cancelled(&self) -> bool { matches!(*self, Stage::Cancelled) } } /// A promise that can poll a task's output of type `U`. /// /// Note that dropping a promise does not cancel the task. #[derive(Debug)] pub(crate) struct Promise { task: *const (), vtable: &'static VTable, } impl Promise { /// Creates a `Promise`. /// /// Safety: this is safe provided that: /// /// - the task pointer points to a live task allocated with the global /// allocator, /// - the reference count has been incremented to account for this new task /// reference. pub(super) unsafe fn new_unchecked(task: *const Task) -> Self where F: Future + Send + 'static, S: Fn(Runnable, T) + Send + Sync + 'static, T: Clone + Send + Sync + 'static, { Self { task: task as *const (), vtable: &VTable:: { poll: poll::, drop: drop::, }, } } /// Retrieves the output of the task if ready. #[allow(unused)] pub(crate) fn poll(&self) -> Stage { unsafe { (self.vtable.poll)(self.task) } } } impl Drop for Promise { fn drop(&mut self) { unsafe { (self.vtable.drop)(self.task) } } } unsafe impl Send for Promise {} impl UnwindSafe for Promise {} impl RefUnwindSafe for Promise {}