use crate::{EaseOff, Error, ResultWrapper, TimeoutError};
use pin_project::pin_project;
use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::time::Instant;
impl<E> EaseOff<E> {
pub fn try_async<T, Fut>(&mut self, op: Fut) -> TryAsync<'_, E, impl FnOnce() -> Fut>
where
Fut: Future<Output = Result<T, E>>,
{
self.try_async_with(move || op)
}
pub fn try_async_with<T, F, Fut>(&mut self, op: F) -> TryAsync<'_, E, F>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
TryAsync { ease_off: self, op }
}
}
#[must_use = "futures do nothing unless `.await`ed or polled"]
pub struct TryAsync<'a, E, F> {
ease_off: &'a mut EaseOff<E>,
op: F,
}
#[pin_project]
pub struct TryAsyncFuture<'a, E, F, Fut> {
ease_off: Option<&'a mut EaseOff<E>>,
#[pin]
op: LazyOp<F, Fut>,
#[pin]
sleep: Sleep,
}
#[pin_project(project = LazyOpPinned)]
enum LazyOp<F, Fut> {
NotStarted(Option<F>),
Started(#[pin] Fut),
}
#[pin_project(project = SleepPinned)]
enum Sleep {
Unset,
Skipped,
Forever,
#[cfg(feature = "tokio")]
Tokio(#[pin] tokio::time::Sleep),
#[cfg(feature = "async-io-2")]
AsyncIo2(async_io_2::Timer),
}
#[pin_project]
struct Timeout<Fut> {
#[pin]
sleep: Sleep,
#[pin]
future: Fut,
}
impl<'a, T, E, F, Fut> IntoFuture for TryAsync<'a, E, F>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
type Output = ResultWrapper<'a, T, E>;
type IntoFuture = TryAsyncFuture<'a, E, F, Fut>;
fn into_future(self) -> Self::IntoFuture {
TryAsyncFuture {
ease_off: Some(self.ease_off),
sleep: Sleep::Unset,
op: LazyOp::NotStarted(Some(self.op)),
}
}
}
impl<'a, T, E, F, Fut> TryAsync<'a, E, F>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
pub async fn enforce_deadline_with(
self,
make_error: impl FnOnce(Option<E>) -> E,
) -> ResultWrapper<'a, T, E> {
let res = Timeout {
sleep: self.ease_off.deadline.map_or(Sleep::Forever, Sleep::until),
future: (self.op)(),
}
.await
.map_or_else(
|_| {
Err(Error::TimedOut(TimeoutError {
last_error: make_error(self.ease_off.last_error.take()),
}))
},
|res| res.map_err(Error::MaybeRetryable),
);
self.ease_off.wrap_result(res)
}
}
impl<'a, T, E, F, Fut> Future for TryAsyncFuture<'a, E, F, Fut>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
type Output = ResultWrapper<'a, T, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
if this.sleep.is_unset() {
let ease_off = this
.ease_off
.as_deref_mut()
.expect("BUG: this.ease_off already taken");
match ease_off.next_retry_at() {
Ok(Some(retry_at)) => {
this.sleep.set(Sleep::until(retry_at));
}
Ok(None) => {
this.sleep.set(Sleep::Skipped);
}
Err(e) => {
return Poll::Ready(
this.ease_off
.take()
.expect("BUG: this.ease_off already taken")
.wrap_result(Err(e)),
);
}
}
}
ready!(this.sleep.as_mut().poll(cx));
let res = ready!(this.op.poll(cx)).map_err(Error::MaybeRetryable);
Poll::Ready(
this.ease_off
.take()
.expect("BUG: this.ease_off already taken")
.wrap_result(res),
)
}
}
impl<T, E, F, Fut> Future for LazyOp<F, Fut>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
type Output = Result<T, E>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
match self.as_mut().project() {
LazyOpPinned::NotStarted(op) => {
let op = op.take().expect("`op` already taken");
self.set(LazyOp::Started(op()));
}
LazyOpPinned::Started(fut) => {
return fut.poll(cx);
}
}
}
}
}
impl Sleep {
fn until(instant: Instant) -> Self {
#[cfg(feature = "tokio")]
if tokio::runtime::Handle::try_current().is_ok() {
return Self::Tokio(tokio::time::sleep_until(instant.into()));
}
#[cfg(feature = "async-io-2")]
{
Self::AsyncIo2(async_io_2::Timer::at(instant))
}
#[cfg(not(feature = "async-io-2"))]
if cfg!(feature = "tokio") {
panic!("no Tokio runtime available")
} else {
panic!("no async runtime enabled")
}
}
fn is_unset(&self) -> bool {
matches!(self, Sleep::Unset)
}
}
impl Future for Sleep {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
SleepPinned::Unset | SleepPinned::Skipped => Poll::Ready(()),
SleepPinned::Forever => Poll::Pending,
#[cfg(feature = "tokio")]
SleepPinned::Tokio(sleep) => sleep.poll(cx),
#[cfg(feature = "async-io-2")]
SleepPinned::AsyncIo2(sleep) => Pin::new(sleep).poll(cx).map(|_| ()),
}
}
}
impl<Fut: Future> Future for Timeout<Fut> {
type Output = Result<Fut::Output, ()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(ready) = this.future.poll(cx) {
return Poll::Ready(Ok(ready));
}
if let Poll::Ready(()) = this.sleep.poll(cx) {
return Poll::Ready(Err(()));
}
Poll::Pending
}
}