use crate::ObservableError;
use std::cell::{Cell, RefCell};
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::task::{Context, Poll, Waker};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TaskStatus {
Created,
WaitingForActivation,
Running,
RanToCompletion,
Faulted,
Canceled,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TaskCanceledError {
pub task_id: Option<u64>,
}
impl fmt::Display for TaskCanceledError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("A task was canceled.")
}
}
impl Error for TaskCanceledError {}
impl TaskCanceledError {
pub fn is_cancellation(error: &ObservableError) -> bool {
error.downcast_ref::<TaskCanceledError>().is_some()
}
}
fn next_task_id() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}
type Continuation = Box<dyn FnOnce()>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ContinuationId(u64);
struct TaskInner<T> {
id: u64,
status: Cell<TaskStatus>,
result: RefCell<Option<T>>,
error: RefCell<Option<ObservableError>>,
continuations: RefCell<Vec<(u64, Continuation)>>,
next_continuation: Cell<u64>,
wakers: RefCell<Vec<Waker>>,
}
pub struct Task<T> {
inner: Rc<TaskInner<T>>,
}
impl<T> Clone for Task<T> {
fn clone(&self) -> Self {
Self { inner: self.inner.clone() }
}
}
impl<T> fmt::Debug for Task<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Task").field("id", &self.inner.id).field("status", &self.inner.status.get()).finish()
}
}
impl<T: 'static> Task<T> {
pub(crate) fn with_status(status: TaskStatus) -> Self {
Self {
inner: Rc::new(TaskInner {
id: next_task_id(),
status: Cell::new(status),
result: RefCell::new(None),
error: RefCell::new(None),
continuations: RefCell::new(Vec::new()),
next_continuation: Cell::new(0),
wakers: RefCell::new(Vec::new()),
}),
}
}
pub fn from_result(result: T) -> Self {
let task = Self::with_status(TaskStatus::WaitingForActivation);
task.try_complete(Ok(result));
task
}
pub fn from_exception(error: ObservableError) -> Self {
let task = Self::with_status(TaskStatus::WaitingForActivation);
task.try_complete(Err(error));
task
}
pub fn from_canceled() -> Self {
let task = Self::with_status(TaskStatus::WaitingForActivation);
task.try_cancel();
task
}
pub fn spawn_local(future: impl Future<Output = Result<T, ObservableError>> + 'static) -> Self {
super::local_executor::spawn_local(future)
}
pub fn id(&self) -> u64 {
self.inner.id
}
pub fn status(&self) -> TaskStatus {
self.inner.status.get()
}
pub fn is_completed(&self) -> bool {
matches!(self.status(), TaskStatus::RanToCompletion | TaskStatus::Faulted | TaskStatus::Canceled)
}
pub fn is_completed_successfully(&self) -> bool {
self.status() == TaskStatus::RanToCompletion
}
pub fn is_faulted(&self) -> bool {
self.status() == TaskStatus::Faulted
}
pub fn is_canceled(&self) -> bool {
self.status() == TaskStatus::Canceled
}
pub fn exception(&self) -> Option<ObservableError> {
self.inner.error.borrow().clone()
}
pub fn result(&self) -> Result<T, ObservableError>
where
T: Clone,
{
match self.status() {
TaskStatus::RanToCompletion => Ok(self.inner.result.borrow().clone().expect("a completed task holds its result")),
TaskStatus::Faulted => Err(self.exception().expect("a faulted task holds its error")),
TaskStatus::Canceled => Err(Rc::new(TaskCanceledError { task_id: Some(self.id()) })),
_ => panic!("the result of a task that has not ended was asked for; await it or use continue_with"),
}
}
pub fn continue_with(&self, continuation: impl FnOnce(&Task<T>) + 'static) -> Option<ContinuationId> {
let task = self.clone();
if self.is_completed() {
continuation(&task);
return None;
}
let id = self.inner.next_continuation.get();
self.inner.next_continuation.set(id + 1);
self.inner.continuations.borrow_mut().push((id, Box::new(move || continuation(&task))));
Some(ContinuationId(id))
}
pub fn remove_continuation(&self, id: ContinuationId) -> bool {
let removed = {
let mut continuations = self.inner.continuations.borrow_mut();
let position = continuations.iter().position(|(entry, _)| *entry == id.0);
position.map(|position| continuations.remove(position))
};
removed.is_some()
}
pub(crate) fn try_complete(&self, outcome: Result<T, ObservableError>) -> bool {
if self.is_completed() {
return false;
}
match outcome {
Ok(result) => {
*self.inner.result.borrow_mut() = Some(result);
self.inner.status.set(TaskStatus::RanToCompletion);
}
Err(error) if TaskCanceledError::is_cancellation(&error) => {
self.inner.status.set(TaskStatus::Canceled);
}
Err(error) => {
*self.inner.error.borrow_mut() = Some(error);
self.inner.status.set(TaskStatus::Faulted);
}
}
self.finish();
true
}
pub(crate) fn try_cancel(&self) -> bool {
if self.is_completed() {
return false;
}
self.inner.status.set(TaskStatus::Canceled);
self.finish();
true
}
fn finish(&self) {
let continuations = std::mem::take(&mut *self.inner.continuations.borrow_mut());
for (_, continuation) in continuations {
continuation();
}
let wakers = std::mem::take(&mut *self.inner.wakers.borrow_mut());
for waker in wakers {
waker.wake();
}
}
}
impl<T: Clone + 'static> Future for Task<T> {
type Output = Result<T, ObservableError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.is_completed() {
return Poll::Ready(self.result());
}
let mut wakers = self.inner.wakers.borrow_mut();
if !wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
wakers.push(cx.waker().clone());
}
Poll::Pending
}
}
pub struct TaskCompletionSource<T> {
task: Task<T>,
}
impl<T: 'static> Default for TaskCompletionSource<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Clone for TaskCompletionSource<T> {
fn clone(&self) -> Self {
Self { task: self.task.clone() }
}
}
impl<T: 'static> TaskCompletionSource<T> {
pub fn new() -> Self {
Self { task: Task::with_status(TaskStatus::WaitingForActivation) }
}
pub fn task(&self) -> Task<T> {
self.task.clone()
}
pub fn set_result(&self, result: T) {
assert!(self.try_set_result(result), "An attempt was made to transition a task to a final state when it had already completed.");
}
pub fn try_set_result(&self, result: T) -> bool {
self.task.try_complete(Ok(result))
}
pub fn set_exception(&self, error: ObservableError) {
assert!(self.try_set_exception(error), "An attempt was made to transition a task to a final state when it had already completed.");
}
pub fn try_set_exception(&self, error: ObservableError) -> bool {
self.task.try_complete(Err(error))
}
pub fn set_canceled(&self) {
assert!(self.try_set_canceled(), "An attempt was made to transition a task to a final state when it had already completed.");
}
pub fn try_set_canceled(&self) -> bool {
self.task.try_cancel()
}
}