zebra_network/peer_set/unready_service.rs
1//! Services that are busy or newly created.
2//!
3//! The [`UnreadyService`] implementation is adapted from the one in [tower::Balance][tower-balance].
4//!
5//! [tower-balance]: https://github.com/tower-rs/tower/tree/master/tower/src/balance
6
7use std::{
8 future::Future,
9 marker::PhantomData,
10 pin::Pin,
11 task::{Context, Poll},
12};
13
14use futures::{channel::oneshot, ready};
15use tokio::time::Sleep;
16use tower::Service;
17
18use crate::peer_set::set::CancelClientWork;
19
20#[cfg(test)]
21mod tests;
22
23/// A Future that becomes satisfied when an `S`-typed service is ready.
24///
25/// May fail due to cancellation or continuous unreadiness for `REQUEST_TIMEOUT`.
26/// This bound is separate from the response timeout, which starts at request dispatch.
27#[pin_project]
28#[derive(Debug)]
29pub(super) struct UnreadyService<K, S, Req> {
30 /// The key used to lookup `service`.
31 pub(super) key: Option<K>,
32
33 /// A oneshot used to cancel the request the `service` is currently working on, if any.
34 #[pin]
35 pub(super) cancel: oneshot::Receiver<CancelClientWork>,
36
37 /// Limits continuous unreadiness, starting when the service enters the unready set.
38 #[pin]
39 pub(super) readiness_timeout: Sleep,
40
41 /// The `service` that is busy (or newly created).
42 pub(super) service: Option<S>,
43
44 /// Dropping `service` might drop a request.
45 /// This [`PhantomData`] tells the Rust compiler to do a drop check for `Req`.
46 pub(super) _req: PhantomData<Req>,
47}
48
49#[derive(Debug, Eq, PartialEq)]
50pub(super) enum Error<E> {
51 Inner(E),
52 Canceled,
53 CancelHandleDropped(oneshot::Canceled),
54 TimedOut,
55}
56
57impl<K, S: Service<Req>, Req> Future for UnreadyService<K, S, Req> {
58 type Output = Result<(K, S), (K, Error<S::Error>)>;
59
60 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
61 let this = self.project();
62
63 if let Poll::Ready(oneshot_result) = this.cancel.poll(cx) {
64 let key = this.key.take().expect("polled after ready");
65
66 // # Correctness
67 //
68 // Return an error if the service is explicitly canceled,
69 // or its cancel handle is dropped, implicitly cancelling it.
70 match oneshot_result {
71 Ok(CancelClientWork) => return Poll::Ready(Err((key, Error::Canceled))),
72 Err(canceled_error) => {
73 return Poll::Ready(Err((key, Error::CancelHandleDropped(canceled_error))))
74 }
75 }
76 }
77
78 // # Correctness
79 //
80 // The current task must be scheduled for wakeup every time we return
81 // `Poll::Pending`.
82 //
83 // Check readiness before the deadline so a service ready at the deadline is retained.
84 // If it is still unready, always poll the timer, even amid repeated service wakeups.
85 // Both the timer and `cancel.poll` also schedule this task for wakeup.
86 let res = match this
87 .service
88 .as_mut()
89 .expect("polled after ready")
90 .poll_ready(cx)
91 {
92 Poll::Ready(res) => res,
93 Poll::Pending => {
94 ready!(this.readiness_timeout.poll(cx));
95 let key = this.key.take().expect("polled after ready");
96 return Poll::Ready(Err((key, Error::TimedOut)));
97 }
98 };
99
100 let key = this.key.take().expect("polled after ready");
101 let svc = this.service.take().expect("polled after ready");
102
103 match res {
104 Ok(()) => Poll::Ready(Ok((key, svc))),
105 Err(e) => Poll::Ready(Err((key, Error::Inner(e)))),
106 }
107 }
108}