use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex, PoisonError};
use std::task::{Context, Poll, Waker};
use crate::{Error, Result};
#[must_use = "a pending value does nothing unless awaited or taken"]
pub struct Pending<T>(Arc<Mutex<Slot<T>>>);
enum Slot<T> {
Waiting { waker: Option<Waker>, errors: Option<Arc<Mutex<Vec<Error>>>> },
Ready(Result<T>),
Taken,
}
pub struct Resolver<T>(Arc<Mutex<Slot<T>>>);
impl<T> Pending<T> {
pub fn new() -> (Pending<T>, Resolver<T>) {
let slot = Arc::new(Mutex::new(Slot::Waiting { waker: None, errors: None }));
(Pending(slot.clone()), Resolver(slot))
}
pub fn ready(result: Result<T>) -> Pending<T> {
Pending(Arc::new(Mutex::new(Slot::Ready(result))))
}
#[cfg(feature = "gpu")]
pub(crate) fn notify(&self, waker: &Waker) {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
match &mut *slot {
Slot::Waiting { waker: waiting, .. } => *waiting = Some(waker.clone()),
Slot::Ready(_) => {
drop(slot);
waker.wake_by_ref();
}
Slot::Taken => {}
}
}
#[cfg(any(feature = "gpu", test))]
pub(crate) fn report_errors(&self, errors: &Arc<Mutex<Vec<Error>>>) {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
match &mut *slot {
Slot::Waiting { errors: sink, .. } => *sink = Some(errors.clone()),
Slot::Ready(Err(error)) => errors.lock().unwrap_or_else(PoisonError::into_inner).push(error.clone()),
_ => {}
}
}
pub fn take(&self) -> Option<Result<T>> {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
match std::mem::replace(&mut *slot, Slot::Taken) {
Slot::Ready(value) => Some(value),
waiting @ Slot::Waiting { .. } => {
*slot = waiting;
None
}
Slot::Taken => None,
}
}
}
impl<T> Resolver<T> {
pub fn is_cancelled(&self) -> bool {
Arc::strong_count(&self.0) == 1
}
pub fn resolve(self, result: Result<T>) {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
if let Slot::Waiting { errors: Some(errors), .. } = &*slot
&& !self.is_cancelled()
&& let Err(error) = &result
{
errors.lock().unwrap_or_else(PoisonError::into_inner).push(error.clone());
}
let previous = std::mem::replace(&mut *slot, Slot::Ready(result));
drop(slot);
if let Slot::Waiting { waker, .. } = previous
&& let Some(waker) = waker
{
waker.wake();
}
}
}
impl<T> Drop for Resolver<T> {
fn drop(&mut self) {
let waker = {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
let Slot::Waiting { waker, errors } = &mut *slot else { return };
if !self.is_cancelled()
&& let Some(errors) = errors
{
errors.lock().unwrap_or_else(PoisonError::into_inner).push(Error::Cancelled);
}
let waker = waker.take();
*slot = Slot::Ready(Err(Error::Cancelled));
waker
};
if let Some(waker) = waker {
waker.wake();
}
}
}
impl<T> Future for Pending<T> {
type Output = Result<T>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<T>> {
let mut slot = self.0.lock().unwrap_or_else(PoisonError::into_inner);
match std::mem::replace(&mut *slot, Slot::Taken) {
Slot::Ready(value) => Poll::Ready(value),
Slot::Waiting { errors, .. } => {
*slot = Slot::Waiting { waker: Some(context.waker().clone()), errors };
Poll::Pending
}
Slot::Taken => panic!("a pending value polled after it resolved"),
}
}
}
impl<T> std::fmt::Debug for Pending<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Pending")
}
}
impl<T> std::fmt::Debug for Resolver<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Resolver")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::Wake;
struct Notified(AtomicBool);
impl Wake for Notified {
fn wake(self: Arc<Self>) {
self.0.store(true, Ordering::Relaxed);
}
}
#[test]
fn dropping_the_resolver_cancels_and_wakes_the_waiter() {
let (mut pending, resolver) = Pending::<u32>::new();
let notified = Arc::new(Notified(AtomicBool::new(false)));
let waker = Waker::from(notified.clone());
assert!(Pin::new(&mut pending).poll(&mut Context::from_waker(&waker)).is_pending());
drop(resolver);
assert!(notified.0.load(Ordering::Relaxed));
assert_eq!(pending.take(), Some(Err(Error::Cancelled)));
}
#[test]
fn resolving_preserves_the_answer_when_the_resolver_drops() {
let (pending, resolver) = Pending::new();
resolver.resolve(Ok(42));
assert_eq!(pending.take(), Some(Ok(42)));
assert_eq!(pending.take(), None);
}
#[test]
fn job_failures_report_before_the_answer_is_consumed() {
let errors = Arc::new(Mutex::new(Vec::new()));
let (pending, resolver) = Pending::<()>::new();
pending.report_errors(&errors);
resolver.resolve(Err(Error::DeviceLost));
assert_eq!(pending.take(), Some(Err(Error::DeviceLost)));
assert_eq!(*errors.lock().unwrap(), vec![Error::DeviceLost]);
let ready = Pending::<()>::ready(Err(Error::Timeout));
ready.report_errors(&errors);
assert_eq!(*errors.lock().unwrap(), vec![Error::DeviceLost, Error::Timeout]);
}
#[test]
fn abandoned_jobs_do_not_report_failures() {
let errors = Arc::new(Mutex::new(Vec::new()));
let (pending, resolver) = Pending::<()>::new();
pending.report_errors(&errors);
assert!(!resolver.is_cancelled());
drop(pending);
assert!(resolver.is_cancelled());
resolver.resolve(Err(Error::DeviceLost));
assert_eq!(*errors.lock().unwrap(), Vec::<Error>::new());
}
}