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
//! [![pipeline status](https://gitlab.com/mexus/futures-retry/badges/master/pipeline.svg)](https://gitlab.com/mexus/futures-retry/commits/master)
//! [![crates.io](https://img.shields.io/crates/v/futures-retry.svg)](https://crates.io/crates/futures-retry)
//! [![docs.rs](https://docs.rs/futures-retry/badge.svg)](https://docs.rs/futures-retry)
//!
//! [[Release docs]](https://docs.rs/futures-retry/)
//!
//! [[Master docs]](https://mexus.gitlab.io/futures-retry/futures_retry/)
//!
//! A tool that helps you retry your future :) Well, `Future`s and `Stream`s, to be precise.
//!
//! It's quite a common task when you need to repeat some action if you've got an error, be it a
//! connection timeout or some temporary OS error.
//!
//! When you do stuff in a synchronous manner it's quite easy to implement the attempts logic, but
//! when it comes to asynchronous programming you suddenly need to write a fool load of a
//! boilerplate code, introduce state machines and everything.
//!
//! This library aims to make your life easier and let you write more straightword and nice code,
//! concentrating on buisness logic rathen than on handling all the mess.
//!
//! I was inspired to write this library after coming over a [`hyper`
//! issue](https://github.com/hyperium/hyper/issues/1358), and I came to an understanding that the
//! problem is more common than I used to think.
//!
//! For examples have a look in the `examples/` folder in the git repo.
//!
//! Suggestions and critiques are welcome!
//!
//! ```rust
//! extern crate futures_retry;
//! // ...
//! # extern crate tokio;
//! # use tokio::prelude::*;
//! # use tokio::io;
//! # use tokio::net::{TcpListener, TcpStream};
//! # use std::time::Duration;
//! use futures_retry::{RetryPolicy, StreamRetryExt};
//!
//! // In this example we use a free function to handle errors, while in your project you have
//! // more options: for simple cases a simple closure will do, for complex cases you might go
//! // as far as implementing an `ErrorHandler` trait for a custom type with some complex logic.
//! fn handle_error(e: io::Error) -> RetryPolicy<io::Error> {
//!   match e.kind() {
//!     io::ErrorKind::Interrupted => RetryPolicy::Repeat,
//!     io::ErrorKind::PermissionDenied => RetryPolicy::ForwardError(e),
//!     _ => RetryPolicy::WaitRetry(Duration::from_millis(5)),
//!   }
//! }
//!
//! fn serve_connection(stream: TcpStream) -> impl Future<Item = (), Error = ()> + Send {
//!   // ...
//!   # future::ok(())
//! }
//!
//! fn main() {
//!   # let addr = "127.0.0.1:12345".parse().unwrap();
//!   let listener =TcpListener::bind(&addr).unwrap();
//!   let server = listener.incoming()
//!     .retry(handle_error) // Magic happens here
//!     .and_then(|stream| {
//!       tokio::spawn(serve_connection(stream));
//!       Ok(())
//!     })
//!     .for_each(|_| Ok(()))
//!     .map_err(|e| eprintln!("Caught an error {}", e));
//!   # // This nasty hack is required to exit immediately when running the doc tests.
//!   # let server = future::ok(()).select(server).map(|_| ()).map_err(|_| ());
//!   tokio::run(server);
//! }
//! ```
//!
//! ## License
//!
//! Licensed under either of
//!
//!  * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
//!  * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
//!
//! at your option.
//!
//! ### Contribution
//!
//! Unless you explicitly state otherwise, any contribution intentionally submitted
//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
//! additional terms or conditions.

#![deny(missing_docs)]

extern crate futures;
#[cfg(test)]
extern crate tokio;
extern crate tokio_timer;

use std::time::Duration;

mod error_handler;
mod future;
mod stream;

pub use error_handler::ErrorHandler;
pub use future::FutureFactory;
pub use future::FutureRetry;
pub use stream::StreamRetry;
pub use stream::StreamRetryExt;

/// What to do when a future returns an error. Used in `FutureRetry::new` and `StreamRetry::new`.
pub enum RetryPolicy<E> {
    /// Create and poll a new future immediately.
    ///
    /// # Be careful!
    ///
    /// Please be careful when using this variant since it might lead to a high (actually 100%) CPU
    /// usage in case a future instantly resolves into an error every time.
    Repeat,
    /// Wait for a given duration and make another attempt then.
    WaitRetry(Duration),
    /// Don't give it another try, just pass the error further to the user.
    ForwardError(E),
}