Skip to main content

saluki_io/net/util/middleware/
retry_circuit_breaker.rs

1use std::{
2    fmt,
3    future::Future,
4    pin::Pin,
5    sync::{Arc, Mutex},
6    task::{ready, Context, Poll},
7};
8
9use futures::FutureExt as _;
10use pin_project_lite::pin_project;
11use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
12use tower::{retry::Policy, Layer, Service};
13use tracing::debug;
14
15/// An error from [`RetryCircuitBreaker`].
16#[derive(Debug)]
17pub enum Error<E, R> {
18    /// The inner service returned a readiness error or a final request error that will not be retried.
19    Service(E),
20
21    /// The inner service was called and completed, and `Policy::retry` returned `Some(backoff)`.
22    Retry(R),
23
24    /// The circuit breaker was already in backoff at call time, so the inner service was not called.
25    Open(R),
26
27    /// The cancellation token was cancelled while the inner service was handling the request, so the request was
28    /// abandoned before it completed.
29    Cancelled(R),
30}
31
32impl<E, R> std::error::Error for Error<E, R>
33where
34    E: std::error::Error + 'static,
35    R: fmt::Debug,
36{
37    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38        match self {
39            Self::Service(e) => Some(e),
40            Self::Retry(_) | Self::Open(_) | Self::Cancelled(_) => None,
41        }
42    }
43}
44
45impl<E, R> fmt::Display for Error<E, R>
46where
47    E: fmt::Display,
48{
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Service(e) => write!(f, "service error: {}", e),
52            Self::Retry(_) => write!(f, "request should be retried"),
53            Self::Open(_) => write!(f, "circuit breaker open"),
54            Self::Cancelled(_) => write!(f, "request cancelled"),
55        }
56    }
57}
58
59impl<E, R> PartialEq for Error<E, R>
60where
61    E: PartialEq,
62    R: PartialEq,
63{
64    fn eq(&self, other: &Self) -> bool {
65        match (self, other) {
66            (Self::Service(a), Self::Service(b)) => a == b,
67            (Self::Retry(a), Self::Retry(b)) => a == b,
68            (Self::Open(a), Self::Open(b)) => a == b,
69            (Self::Cancelled(a), Self::Cancelled(b)) => a == b,
70            _ => false,
71        }
72    }
73}
74
75pin_project! {
76    /// Response future for [`RetryCircuitBreaker`].
77    pub struct ResponseFuture<P, F, Request> {
78        state: Arc<Mutex<State<P>>>,
79        #[pin]
80        inner: Option<F>,
81        req: Option<Request>,
82        #[pin]
83        cancelled: Option<WaitForCancellationFutureOwned>,
84    }
85}
86
87impl<P, F, T, E, Request> Future for ResponseFuture<P, F, Request>
88where
89    P: Policy<Request, T, E>,
90    P::Future: Send + 'static,
91    F: Future<Output = Result<T, E>>,
92{
93    type Output = Result<T, Error<E, Request>>;
94
95    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
96        // Our response future exists in two states: the circuit breaker was either closed or open when we created it.
97        //
98        // When the circuit breaker is open while creating the response future, there's no actual response future to
99        // call in this case. We simply store the original request and pass it back while indicating to the caller that
100        // the circuit breaker is open. Simple.
101        //
102        // When the circuit breaker is closed while creating the response future, this means we can proceed, and we
103        // generate a legitimate response future to poll. However, the retry policy may return `None` when trying to
104        // clone the request, which indicates the request actually isn't eligible to be retried at all. Thus, when we
105        // don't have an original request here, we just return the inner service's response as-is. When we _do_ have the
106        // original request, we utilize the retry policy to determine if it can be retried, and if so, potentially
107        // update our circuit breaker state based on what the retry policy tells us.
108        //
109        // When a cancellation token is configured and we have the original request, we also stop waiting for the
110        // inner response once the token is cancelled, and pass the original request back instead. The response is
111        // polled first, so a response that is already available wins over a cancellation.
112
113        let this = self.project();
114        if let Some(inner) = this.inner.as_pin_mut() {
115            let mut result = match inner.poll(cx) {
116                Poll::Ready(result) => result,
117                Poll::Pending => {
118                    if let Some(cancelled) = this.cancelled.as_pin_mut() {
119                        ready!(cancelled.poll(cx));
120
121                        debug!("request cancelled while in flight, returning error");
122                        return Poll::Ready(Err(Error::Cancelled(
123                            this.req.take().expect("response future polled after completion"),
124                        )));
125                    }
126                    return Poll::Pending;
127                }
128            };
129
130            let mut state = this.state.lock().unwrap();
131            match this.req.take() {
132                Some(mut req) => match state.policy.retry(&mut req, &mut result) {
133                    Some(backoff) => {
134                        // The policy has indicated that the request should be retried, so we need to open the circuit
135                        // breaker by setting the backoff future to use. Another request's retry decision may have
136                        // already beat us to the punch, though, so don't overwrite it if it's already set.
137                        if state.backoff.is_none() {
138                            debug!("no existing backoff future present, setting delay backoff");
139                            state.backoff = Some(backoff.boxed());
140                        }
141
142                        Poll::Ready(Err(Error::Retry(req)))
143                    }
144                    None => {
145                        debug!("request completed, no retry indicated");
146                        Poll::Ready(result.map_err(Error::Service))
147                    }
148                },
149                None => {
150                    debug!("request completed, but request not cloneable so returning response as-is");
151                    Poll::Ready(result.map_err(Error::Service))
152                }
153            }
154        } else {
155            debug!("circuit breaker open prior to call, returning error");
156            Poll::Ready(Err(Error::Open(
157                this.req.take().expect("response future polled after completion"),
158            )))
159        }
160    }
161}
162
163struct State<P> {
164    policy: P,
165    backoff: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
166}
167
168impl<P> std::fmt::Debug for State<P>
169where
170    P: std::fmt::Debug,
171{
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        let backoff = if self.backoff.is_some() { "set" } else { "unset" };
174        f.debug_struct("State")
175            .field("policy", &self.policy)
176            .field("backoff", &backoff)
177            .finish()
178    }
179}
180
181/// Wraps a service in a [circuit breaker][circuit_breaker] and signals when a request must be retried at a later time.
182///
183/// [circuit_breaker]: https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern
184pub struct RetryCircuitBreakerLayer<P> {
185    policy: P,
186    cancellation: Option<CancellationToken>,
187}
188
189impl<P> RetryCircuitBreakerLayer<P> {
190    /// Creates a new [`RetryCircuitBreakerLayer`] with the given policy.
191    pub const fn new(policy: P) -> Self {
192        Self {
193            policy,
194            cancellation: None,
195        }
196    }
197
198    /// Sets the cancellation token for in-flight requests.
199    ///
200    /// See [`RetryCircuitBreaker::with_cancellation`] for details.
201    pub fn with_cancellation(mut self, token: CancellationToken) -> Self {
202        self.cancellation = Some(token);
203        self
204    }
205}
206
207impl<P, S> Layer<S> for RetryCircuitBreakerLayer<P>
208where
209    P: Clone,
210{
211    type Service = RetryCircuitBreaker<S, P>;
212
213    fn layer(&self, inner: S) -> Self::Service {
214        let service = RetryCircuitBreaker::new(inner, self.policy.clone());
215        match &self.cancellation {
216            Some(token) => service.with_cancellation(token.clone()),
217            None => service,
218        }
219    }
220}
221
222/// Wraps a service in a [circuit breaker][circuit_breaker] and signals when a request must be retried at a later time.
223///
224/// This circuit breaker implementation is specific to retrying requests. In many cases, a request can fail in two
225/// ways: unrecoverable errors, which shouldn't be retried, and recoverable errors, which should be retried after a
226/// some period of time. When a request can be retried, it may not be advantageous to wait for the given request to
227/// be retried successfully, as the request should perhaps be stored in a queue and retried at a later time,
228/// potentially to avoid applying backpressure to the client.
229///
230/// [`RetryCircuitBreaker`] provides this capability by separating the logic of determining whether or not a request
231/// should be retried from actually performing the retry itself. When a request leads to an unrecoverable error,
232/// that error is immediately passed back to the caller without affecting the circuit breaker state. However, when a
233/// recoverable error is encountered, the circuit breaker will signal to the caller that the request should be
234/// retried, and update its internal state to open the circuit breaker for a configurable period of time. Further
235/// requests to the circuit breaker will be rejected with an error (indicating the open state) until that period of
236/// time has passed.
237///
238/// [circuit_breaker]: https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern
239#[derive(Debug)]
240pub struct RetryCircuitBreaker<S, P> {
241    inner: S,
242    state: Arc<Mutex<State<P>>>,
243    cancellation: Option<CancellationToken>,
244}
245
246impl<S, P> RetryCircuitBreaker<S, P> {
247    /// Creates a new [`RetryCircuitBreaker`].
248    pub fn new(inner: S, policy: P) -> Self {
249        Self {
250            inner,
251            state: Arc::new(Mutex::new(State { policy, backoff: None })),
252            cancellation: None,
253        }
254    }
255
256    /// Sets the cancellation token for in-flight requests.
257    ///
258    /// When `token` is cancelled, every request still waiting on the inner service is abandoned, and its response
259    /// future resolves to [`Error::Cancelled`] with the copy of the request made by the retry policy. The circuit
260    /// breaker state is not affected. A request that the retry policy could not clone has no copy to hand back, so it
261    /// is not abandoned and runs to completion.
262    pub fn with_cancellation(mut self, token: CancellationToken) -> Self {
263        self.cancellation = Some(token);
264        self
265    }
266}
267
268impl<S, P, Request> Service<Request> for RetryCircuitBreaker<S, P>
269where
270    S: Service<Request>,
271    P: Policy<Request, S::Response, S::Error>,
272    P::Future: Send + 'static,
273{
274    type Response = S::Response;
275    type Error = Error<S::Error, Request>;
276    type Future = ResponseFuture<P, S::Future, Request>;
277
278    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
279        {
280            // Check if we're currently in a backoff state.
281            let mut state = self.state.lock().unwrap();
282            if let Some(backoff) = state.backoff.as_mut() {
283                ready!(backoff.as_mut().poll(cx));
284
285                debug!("circuit breaker backoff complete");
286
287                // The backoff future has completed, so we can reset the circuit breaker state.
288                state.backoff = None;
289            }
290        }
291
292        // Check the readiness of the inner service.
293        self.inner.poll_ready(cx).map_err(Error::Service)
294    }
295
296    fn call(&mut self, req: Request) -> Self::Future {
297        let response_state = Arc::clone(&self.state);
298
299        let mut state = self.state.lock().unwrap();
300        if state.backoff.is_some() {
301            ResponseFuture {
302                state: response_state,
303                inner: None,
304                req: Some(req),
305                cancelled: None,
306            }
307        } else {
308            // The circuit breaker is closed, so we can proceed with the request.
309            let cloned_req = state.policy.clone_request(&req);
310            let inner = self.inner.call(req);
311            let cancelled = match (&self.cancellation, &cloned_req) {
312                (Some(token), Some(_)) => Some(token.clone().cancelled_owned()),
313                _ => None,
314            };
315
316            ResponseFuture {
317                state: response_state,
318                inner: Some(inner),
319                req: cloned_req,
320                cancelled,
321            }
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use std::{
329        future::{ready, Ready},
330        time::Duration,
331    };
332
333    use tokio::time::Sleep;
334    use tokio_test::{assert_pending, assert_ready_ok};
335    use tower::{retry::Policy, ServiceExt as _};
336
337    use super::*;
338
339    const BACKOFF_DUR: Duration = Duration::from_secs(1);
340
341    #[derive(Clone, Debug, Eq, PartialEq)]
342    enum BasicRequest {
343        Ok(String),
344        Err(String),
345    }
346
347    impl BasicRequest {
348        fn success<S: AsRef<str>>(value: S) -> Self {
349            Self::Ok(value.as_ref().to_string())
350        }
351
352        fn failure<S: AsRef<str>>(value: S) -> Self {
353            Self::Err(value.as_ref().to_string())
354        }
355
356        fn as_service_response(&self) -> Result<String, Error<String, Self>> {
357            match self {
358                Self::Ok(value) => Ok(value.clone()),
359                Self::Err(value) => Err(Error::Service(value.clone())),
360            }
361        }
362
363        fn as_retry_response(&self) -> Result<String, Error<String, Self>> {
364            Err(Error::Retry(self.clone()))
365        }
366
367        fn as_open_response(&self) -> Result<String, Error<String, Self>> {
368            Err(Error::Open(self.clone()))
369        }
370
371        fn as_cancelled_response(&self) -> Result<String, Error<String, Self>> {
372            Err(Error::Cancelled(self.clone()))
373        }
374    }
375
376    impl PartialEq<Result<String, String>> for BasicRequest {
377        fn eq(&self, other: &Result<String, String>) -> bool {
378            match self {
379                Self::Ok(value) => other.as_ref() == Ok(value),
380                Self::Err(value) => other.as_ref() == Err(value),
381            }
382        }
383    }
384
385    #[derive(Debug)]
386    struct LoopbackService;
387
388    impl Service<BasicRequest> for LoopbackService {
389        type Response = String;
390        type Error = String;
391        type Future = Ready<Result<String, String>>;
392
393        fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
394            Poll::Ready(Ok(()))
395        }
396
397        fn call(&mut self, req: BasicRequest) -> Self::Future {
398            let res = match req {
399                BasicRequest::Ok(value) => Ok(value),
400                BasicRequest::Err(value) => Err(value),
401            };
402            ready(res)
403        }
404    }
405
406    /// Like [`LoopbackService`], but each response takes [`RESPONSE_DELAY`] to arrive.
407    #[derive(Debug)]
408    struct DelayedLoopbackService;
409
410    const RESPONSE_DELAY: Duration = Duration::from_secs(5);
411
412    impl Service<BasicRequest> for DelayedLoopbackService {
413        type Response = String;
414        type Error = String;
415        type Future = Pin<Box<dyn Future<Output = Result<String, String>> + Send>>;
416
417        fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
418            Poll::Ready(Ok(()))
419        }
420
421        fn call(&mut self, req: BasicRequest) -> Self::Future {
422            let response = LoopbackService.call(req);
423            Box::pin(async move {
424                tokio::time::sleep(RESPONSE_DELAY).await;
425                response.await
426            })
427        }
428    }
429
430    #[derive(Clone, Debug)]
431    struct CloneableTestRetryPolicy;
432
433    impl<Req, T, E> Policy<Req, T, E> for CloneableTestRetryPolicy
434    where
435        Req: Clone,
436    {
437        type Future = Sleep;
438
439        fn retry(&mut self, _: &mut Req, res: &mut Result<T, E>) -> Option<Self::Future> {
440            match res {
441                Ok(_) => None,
442                Err(_) => Some(tokio::time::sleep(BACKOFF_DUR)),
443            }
444        }
445
446        fn clone_request(&mut self, req: &Req) -> Option<Req> {
447            Some(req.clone())
448        }
449    }
450
451    #[derive(Debug)]
452    struct NonCloneableTestRetryPolicy;
453
454    impl<Req, T, E> Policy<Req, T, E> for NonCloneableTestRetryPolicy {
455        type Future = Sleep;
456
457        fn retry(&mut self, _: &mut Req, res: &mut Result<T, E>) -> Option<Self::Future> {
458            match res {
459                Ok(_) => None,
460                Err(_) => Some(tokio::time::sleep(BACKOFF_DUR)),
461            }
462        }
463
464        fn clone_request(&mut self, _: &Req) -> Option<Req> {
465            None
466        }
467    }
468
469    #[tokio::test(start_paused = true)]
470    async fn basic() {
471        let good_req = BasicRequest::success("good");
472        let bad_req = BasicRequest::failure("bad");
473
474        let mut circuit_breaker = RetryCircuitBreaker::new(LoopbackService, CloneableTestRetryPolicy);
475
476        // First request should succeed.
477        //
478        // We should see that it called through to the inner service.
479        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
480        let fut = svc.call(good_req.clone());
481        let result = fut.await;
482        assert_eq!(result, good_req.as_service_response());
483
484        // Second request should fail and should be retried.
485        //
486        // We should see that it called through to the inner service
487        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
488        let fut = svc.call(bad_req.clone());
489        let result = fut.await;
490        assert_eq!(result, bad_req.as_retry_response());
491
492        // When trying to make our third request, we should have to wait for the backoff duration before the service
493        // indicates that it's ready for another call.
494        let mut svc_fut = tokio_test::task::spawn(circuit_breaker.ready());
495        assert_pending!(svc_fut.poll());
496
497        // Advance time past the backoff duration, which should make our service ready.
498        tokio::time::advance(BACKOFF_DUR + Duration::from_millis(1)).await;
499        assert!(svc_fut.is_woken());
500        let svc = assert_ready_ok!(svc_fut.poll());
501
502        let fut = svc.call(good_req.clone());
503        let result = fut.await;
504        assert_eq!(result, good_req.as_service_response());
505
506        // Fourth request should succeed unimpeded since the breaker is closed again.
507        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
508        let fut = svc.call(good_req.clone());
509        let result = fut.await;
510        assert_eq!(result, good_req.as_service_response());
511    }
512
513    #[tokio::test]
514    async fn retry_policy_no_clone() {
515        let good_req = BasicRequest::success("good");
516        let bad_req = BasicRequest::failure("bad");
517
518        // First request should succeed.
519        //
520        // We should see that it called through to the inner service.
521        let mut circuit_breaker = RetryCircuitBreaker::new(LoopbackService, NonCloneableTestRetryPolicy);
522        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
523        let fut = svc.call(good_req.clone());
524        let result = fut.await;
525        assert_eq!(result, good_req.as_service_response());
526
527        // Second request should fail and should be a service error, because without being able to clone the request, it
528        // can't be retried anyways.
529        let mut circuit_breaker = RetryCircuitBreaker::new(LoopbackService, NonCloneableTestRetryPolicy);
530        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
531        let fut = svc.call(bad_req.clone());
532        let result = fut.await;
533        assert_eq!(result, bad_req.as_service_response());
534    }
535
536    #[tokio::test(start_paused = true)]
537    async fn concurrent_calls_can_advance() {
538        let good_req = BasicRequest::success("good");
539        let bad_req = BasicRequest::failure("bad");
540
541        let mut circuit_breaker = RetryCircuitBreaker::new(LoopbackService, CloneableTestRetryPolicy);
542
543        // First request should succeed. This is just a warmup.
544        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
545        let fut = svc.call(good_req.clone());
546        let result = fut.await;
547        assert_eq!(result, good_req.as_service_response());
548
549        // Now we'll create two calls -- one that should fail and one that succeed -- but won't poll them until both are
550        // created. This simulates two concurrent calls happening, and what we want to show is that the circuit breaker
551        // should only mark itself as open to _new_ calls after it the state changes to open, and should not affect
552        // running requests.
553        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
554        let bad_fut = svc.call(bad_req.clone());
555
556        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
557        let good_fut = svc.call(good_req.clone());
558
559        let bad_result = bad_fut.await;
560        assert_eq!(bad_result, bad_req.as_retry_response());
561
562        let good_result = good_fut.await;
563        assert_eq!(good_result, good_req.as_service_response());
564
565        // Now we'll go to make a fourth request, and we'll manually check the readiness of the service to ensure that
566        // we're now in a backoff state.
567        let mut svc_fut = tokio_test::task::spawn(circuit_breaker.ready());
568        assert_pending!(svc_fut.poll());
569    }
570
571    #[tokio::test(start_paused = true)]
572    async fn breaker_open_between_ready_and_call() {
573        let good_req = BasicRequest::success("good");
574        let bad_req = BasicRequest::failure("bad");
575
576        let mut circuit_breaker = RetryCircuitBreaker::new(LoopbackService, CloneableTestRetryPolicy);
577
578        // We'll create two calls -- one that should fail and one that succeed -- but order their creation / polling such that the
579        // bad request updates the breaker state to be open _before_ we create the good request. This is to exercise
580        // that even though the service may report itself as ready, an in-flight request that completes and ultimately
581        // changes the breaker state to open should cause subsequent calls to immediately fail.
582        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
583        let bad_fut = svc.call(bad_req.clone());
584
585        // We're just making sure here that the service is ready to accept another call, but we're not creating that
586        // call yet.
587        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
588
589        // Run our bad request first and ensure it reports that the completed request should be retried.
590        let bad_result = bad_fut.await;
591        assert_eq!(bad_result, bad_req.as_retry_response());
592
593        // Now _create_ the good request and ensure that it fails with an open error without reaching the inner service.
594        let good_fut = svc.call(good_req.clone());
595        let good_result = good_fut.await;
596        assert_eq!(good_result, good_req.as_open_response());
597    }
598
599    #[tokio::test(start_paused = true)]
600    async fn cancellation_hands_back_in_flight_request() {
601        let req = BasicRequest::success("in-flight");
602        let token = CancellationToken::new();
603
604        let mut circuit_breaker =
605            RetryCircuitBreaker::new(DelayedLoopbackService, CloneableTestRetryPolicy).with_cancellation(token.clone());
606        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
607        let mut fut = tokio_test::task::spawn(svc.call(req.clone()));
608        assert_pending!(fut.poll());
609
610        token.cancel();
611        assert!(fut.is_woken());
612        assert_eq!(fut.await, req.as_cancelled_response());
613
614        // A cancelled request doesn't open the breaker.
615        let mut svc_fut = tokio_test::task::spawn(circuit_breaker.ready());
616        assert_ready_ok!(svc_fut.poll());
617    }
618
619    #[tokio::test]
620    async fn response_wins_over_cancellation() {
621        let req = BasicRequest::success("good");
622        let token = CancellationToken::new();
623        token.cancel();
624
625        // The response is available on the first poll, as is the cancellation. The response must win, so that a
626        // request the remote end has answered isn't handed back to be sent again.
627        let mut circuit_breaker =
628            RetryCircuitBreaker::new(LoopbackService, CloneableTestRetryPolicy).with_cancellation(token);
629        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
630        assert_eq!(svc.call(req.clone()).await, req.as_service_response());
631    }
632
633    #[tokio::test(start_paused = true)]
634    async fn cancellation_does_not_abandon_uncloned_request() {
635        let req = BasicRequest::success("uncloned");
636        let token = CancellationToken::new();
637
638        let mut circuit_breaker = RetryCircuitBreaker::new(DelayedLoopbackService, NonCloneableTestRetryPolicy)
639            .with_cancellation(token.clone());
640        let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
641        let mut fut = tokio_test::task::spawn(svc.call(req.clone()));
642        assert_pending!(fut.poll());
643
644        // Without a copy of the request, there is nothing to hand back, so the request runs to completion.
645        token.cancel();
646        assert_pending!(fut.poll());
647
648        tokio::time::advance(RESPONSE_DELAY).await;
649        assert_eq!(fut.await, req.as_service_response());
650    }
651
652    #[tokio::test(start_paused = true)]
653    async fn layer_applies_cancellation() {
654        let req = BasicRequest::success("in-flight");
655        let token = CancellationToken::new();
656
657        let mut svc = RetryCircuitBreakerLayer::new(CloneableTestRetryPolicy)
658            .with_cancellation(token.clone())
659            .layer(DelayedLoopbackService);
660        let svc = svc.ready().await.expect("should never fail to be ready");
661        let fut = svc.call(req.clone());
662
663        token.cancel();
664        assert_eq!(fut.await, req.as_cancelled_response());
665    }
666}