1use crate::{EaseOff, Error, ResultWrapper, TimeoutError};
4
5use pin_project::pin_project;
6use std::future::{Future, IntoFuture};
7use std::marker::PhantomPinned;
8use std::pin::Pin;
9use std::task::{ready, Context, Poll};
10use std::time::Instant;
11
12impl<E> EaseOff<E> {
25 pub fn try_async<T, Fut>(&mut self, op: Fut) -> TryAsync<'_, E, impl FnOnce() -> Fut>
32 where
33 Fut: Future<Output = Result<T, E>>,
34 {
35 self.try_async_with(move || op)
36 }
37
38 pub fn try_async_with<T, F, Fut>(&mut self, op: F) -> TryAsync<'_, E, F>
49 where
50 F: FnOnce() -> Fut,
51 Fut: Future<Output = Result<T, E>>,
52 {
53 TryAsync { ease_off: self, op }
54 }
55}
56
57#[must_use = "futures do nothing unless `.await`ed or polled"]
73pub struct TryAsync<'a, E, F> {
74 ease_off: &'a mut EaseOff<E>,
75 op: F,
76}
77
78#[pin_project]
86pub struct TryAsyncFuture<'a, E, F, Fut> {
87 ease_off: Option<&'a mut EaseOff<E>>,
89 #[pin]
90 op: LazyOp<F, Fut>,
91 #[pin]
92 sleep: Sleep,
93}
94
95#[pin_project(project = LazyOpPinned)]
96enum LazyOp<F, Fut> {
97 NotStarted(Option<F>),
98 Started(#[pin] Fut),
99}
100
101#[pin_project(project = SleepPinned)]
103enum Sleep {
104 Unset,
105 Skipped,
106 Forever(#[pin] PhantomPinned),
108 #[cfg(feature = "tokio")]
109 Tokio(#[pin] tokio::time::Sleep),
110 #[cfg(feature = "async-io-2")]
111 AsyncIo2(async_io_2::Timer),
112}
113
114#[pin_project]
115struct Timeout<Fut> {
116 #[pin]
117 sleep: Sleep,
118 #[pin]
119 future: Fut,
120}
121
122impl<'a, T, E, F, Fut> IntoFuture for TryAsync<'a, E, F>
123where
124 F: FnOnce() -> Fut,
125 Fut: Future<Output = Result<T, E>>,
126{
127 type Output = ResultWrapper<'a, T, E>;
128 type IntoFuture = TryAsyncFuture<'a, E, F, Fut>;
129
130 fn into_future(self) -> Self::IntoFuture {
131 TryAsyncFuture {
132 ease_off: Some(self.ease_off),
133 sleep: Sleep::Unset,
134 op: LazyOp::NotStarted(Some(self.op)),
135 }
136 }
137}
138
139impl<'a, T, E, F, Fut> TryAsync<'a, E, F>
140where
141 F: FnOnce() -> Fut,
142 Fut: Future<Output = Result<T, E>>,
143{
144 pub async fn enforce_deadline_with(
174 self,
175 make_error: impl FnOnce(Option<E>) -> E,
176 ) -> ResultWrapper<'a, T, E> {
177 let res = Timeout {
178 sleep: self
179 .ease_off
180 .deadline
181 .map_or(Sleep::Forever(PhantomPinned), Sleep::until),
182 future: (self.op)(),
183 }
184 .await
185 .map_or_else(
186 |_| {
187 Err(Error::TimedOut(TimeoutError {
188 last_error: make_error(self.ease_off.last_error.take()),
189 }))
190 },
191 |res| res.map_err(Error::MaybeRetryable),
192 );
193
194 self.ease_off.wrap_result(res)
195 }
196}
197
198impl<'a, T, E, F, Fut> Future for TryAsyncFuture<'a, E, F, Fut>
199where
200 F: FnOnce() -> Fut,
201 Fut: Future<Output = Result<T, E>>,
202{
203 type Output = ResultWrapper<'a, T, E>;
204
205 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
206 let mut this = self.project();
207
208 if this.sleep.is_unset() {
209 let ease_off = this
210 .ease_off
211 .as_deref_mut()
212 .expect("BUG: this.ease_off already taken");
213
214 match ease_off.next_retry_at() {
215 Ok(Some(retry_at)) => {
216 this.sleep.set(Sleep::until(retry_at));
217 }
218 Ok(None) => {
219 this.sleep.set(Sleep::Skipped);
220 }
221 Err(e) => {
222 return Poll::Ready(
223 this.ease_off
224 .take()
225 .expect("BUG: this.ease_off already taken")
226 .wrap_result(Err(e)),
227 );
228 }
229 }
230 }
231
232 ready!(this.sleep.as_mut().poll(cx));
233
234 let res = ready!(this.op.poll(cx)).map_err(Error::MaybeRetryable);
235
236 Poll::Ready(
237 this.ease_off
238 .take()
239 .expect("BUG: this.ease_off already taken")
240 .wrap_result(res),
241 )
242 }
243}
244
245impl<T, E, F, Fut> Future for LazyOp<F, Fut>
246where
247 F: FnOnce() -> Fut,
248 Fut: Future<Output = Result<T, E>>,
249{
250 type Output = Result<T, E>;
251
252 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
253 loop {
254 match self.as_mut().project() {
255 LazyOpPinned::NotStarted(op) => {
256 let op = op.take().expect("`op` already taken");
257 self.set(LazyOp::Started(op()));
258 }
259 LazyOpPinned::Started(fut) => {
260 return fut.poll(cx);
261 }
262 }
263 }
264 }
265}
266
267impl Sleep {
268 fn until(instant: Instant) -> Self {
269 #[cfg(feature = "tokio")]
270 if tokio::runtime::Handle::try_current().is_ok() {
271 return Self::Tokio(tokio::time::sleep_until(instant.into()));
272 }
273
274 #[cfg(feature = "async-io-2")]
275 {
276 Self::AsyncIo2(async_io_2::Timer::at(instant))
277 }
278
279 #[cfg(not(feature = "async-io-2"))]
280 if cfg!(feature = "tokio") {
281 panic!("no Tokio runtime available")
282 } else {
283 panic!("no async runtime enabled")
284 }
285 }
286
287 fn is_unset(&self) -> bool {
288 matches!(self, Sleep::Unset)
289 }
290}
291
292impl Future for Sleep {
293 type Output = ();
294
295 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
296 match self.project() {
297 SleepPinned::Unset | SleepPinned::Skipped => Poll::Ready(()),
298 SleepPinned::Forever(..) => Poll::Pending,
299 #[cfg(feature = "tokio")]
300 SleepPinned::Tokio(sleep) => sleep.poll(cx),
301 #[cfg(feature = "async-io-2")]
302 SleepPinned::AsyncIo2(sleep) => Pin::new(sleep).poll(cx).map(|_| ()),
303 }
304 }
305}
306
307impl<Fut: Future> Future for Timeout<Fut> {
308 type Output = Result<Fut::Output, ()>;
309
310 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
311 let this = self.project();
312
313 if let Poll::Ready(ready) = this.future.poll(cx) {
314 return Poll::Ready(Ok(ready));
315 }
316
317 if let Poll::Ready(()) = this.sleep.poll(cx) {
318 return Poll::Ready(Err(()));
319 }
320
321 Poll::Pending
322 }
323}