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#[derive(Debug)]
17pub enum Error<E, R> {
18 Service(E),
20
21 Retry(R),
23
24 Open(R),
26
27 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 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 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 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
181pub struct RetryCircuitBreakerLayer<P> {
185 policy: P,
186 cancellation: Option<CancellationToken>,
187}
188
189impl<P> RetryCircuitBreakerLayer<P> {
190 pub const fn new(policy: P) -> Self {
192 Self {
193 policy,
194 cancellation: None,
195 }
196 }
197
198 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#[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 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 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 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 state.backoff = None;
289 }
290 }
291
292 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 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 #[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 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 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 let mut svc_fut = tokio_test::task::spawn(circuit_breaker.ready());
495 assert_pending!(svc_fut.poll());
496
497 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 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 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 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 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 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 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 let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
583 let bad_fut = svc.call(bad_req.clone());
584
585 let svc = circuit_breaker.ready().await.expect("should never fail to be ready");
588
589 let bad_result = bad_fut.await;
591 assert_eq!(bad_result, bad_req.as_retry_response());
592
593 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 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 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 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}