1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
//! Middleware for tokio services that adds automatic retries
//! in case of failure.
//!
//! # Examples
//!
//! ```rust
//! extern crate futures;
//! extern crate tokio_core;
//! extern crate tokio_service;
//! extern crate tokio_retry;
//!
//! use std::io;
//!
//! use futures::{BoxFuture, Future, future};
//! use tokio_core::reactor::Core;
//! use tokio_service::Service;
//! use tokio_retry::Middleware;
//! use tokio_retry::strategy::{ExponentialBackoff, jitter};
//!
//! struct EchoService;
//!
//! impl Service for EchoService {
//! type Request = String;
//! type Response = String;
//! type Error = ();
//! type Future = BoxFuture<String, ()>;
//! fn call(&self, input: String) -> Self::Future {
//! future::ok(input).boxed()
//! }
//! }
//!
//! fn main() {
//! let mut core = Core::new().unwrap();
//!
//! let retry_strategy = || ExponentialBackoff::from_millis(10)
//! .map(jitter)
//! .take(3);
//!
//! let retry_service = Middleware::new(core.handle(), retry_strategy, EchoService);
//! let retry_result = core.run(retry_service.call("hello world!".to_string()));
//!
//! assert_eq!(retry_result, Ok("hello world!".to_string()));
//! }
//! ```
use std::iter::{Iterator, IntoIterator};
use std::time::Duration;
use std::sync::Arc;
use tokio_service::Service;
use tokio_core::reactor::Handle;
use super::{Retry, Error};
use super::action::Action;
/// Represents a retryable request to a service.
pub struct ServiceRequest<S: Service> {
inner: Arc<S>,
request: S::Request
}
impl<S: Service> Action for ServiceRequest<S> where S::Request: Clone {
type Error = S::Error;
type Item = S::Response;
type Future = S::Future;
fn run(&mut self) -> Self::Future {
self.inner.call(self.request.clone())
}
}
/// Middleware that adds retries to a service via a retry strategy.
pub struct Middleware<T, S> {
inner: Arc<S>,
handle: Handle,
strategy: T
}
/// Trait to produce iterators that will be used as retry strategies.
///
/// Can be implemented directly, but the simplest way to instantiate
/// a strategy factory is by leveraging the `impl` for `Fn()`:
///
/// ```rust
/// # use tokio_retry::strategy::ExponentialBackoff;
/// let retry_strategy = || ExponentialBackoff::from_millis(10);
/// ```
pub trait StrategyFactory {
type Iter: Iterator<Item=Duration>;
fn get_strategy(&self) -> Self::Iter;
}
impl<F, I: IntoIterator<Item=Duration>> StrategyFactory for F where F: Fn() -> I {
type Iter = I::IntoIter;
fn get_strategy(&self) -> Self::Iter {
self().into_iter()
}
}
impl<T: StrategyFactory, S> Middleware<T, S> {
pub fn new(handle: Handle, strategy: T, inner: S) -> Middleware<T, S> {
Middleware{
inner: Arc::new(inner),
handle: handle,
strategy: strategy
}
}
}
impl<T: StrategyFactory, S: Service> Service for Middleware<T, S> where S::Request: Clone {
type Request = S::Request;
type Response = S::Response;
type Error = Error<S::Error>;
type Future = Retry<T::Iter, ServiceRequest<S>>;
fn call(&self, request: Self::Request) -> Self::Future {
let action = ServiceRequest{
inner: self.inner.clone(),
request: request
};
Retry::spawn(self.handle.clone(), self.strategy.get_strategy(), action)
}
}