Copyright | Ozgun Ataman <[email protected]> |
---|---|
License | BSD3 |
Maintainer | Ozgun Ataman |
Stability | provisional |
Safe Haskell | Safe-Inferred |
Language | Haskell2010 |
Control.Retry
Description
This module exposes combinators that can wrap arbitrary monadic actions. They run the action and potentially retry running it with some configurable delay for a configurable number of times.
The express purpose of this library is to make it easier to work with IO and especially network IO actions that often experience temporary failure that warrant retrying of the original action. For example, a database query may time out for a while, in which case we should delay a bit and retry the query.
- newtype RetryPolicy = RetryPolicy {
- getRetryPolicy :: Int -> Maybe Int
- retrying :: MonadIO m => RetryPolicy -> (Int -> b -> m Bool) -> m b -> m b
- recovering :: (MonadIO m, MonadMask m) => RetryPolicy -> [Int -> Handler m Bool] -> m a -> m a
- recoverAll :: (MonadIO m, MonadMask m) => RetryPolicy -> m a -> m a
- logRetries :: (Monad m, Show e, Exception e) => (e -> m Bool) -> (String -> m ()) -> Int -> Handler m Bool
- constantDelay :: Int -> RetryPolicy
- exponentialBackoff :: Int -> RetryPolicy
- fibonacciBackoff :: Int -> RetryPolicy
- limitRetries :: Int -> RetryPolicy
- limitRetriesByDelay :: Int -> RetryPolicy -> RetryPolicy
- capDelay :: Int -> RetryPolicy -> RetryPolicy
- (<>) :: Monoid m => m -> m -> m
High Level Operation
newtype RetryPolicy Source
A RetryPolicy
is a function that takes an iteration number and
possibly returns a delay in microseconds. *Nothing* implies we have
reached the retry limit.
Please note that RetryPolicy
is a Monoid
. You can collapse
multiple strategies into one using mappend
or <>
. The semantics
of this combination are as follows:
- If either policy returns
Nothing
, the combined policy returnsNothing
. This can be used toinhibit
after a number of retries, for example. - If both policies return a delay, the larger delay will be used. This is quite natural when combining multiple policies to achieve a certain effect.
Example:
One can easily define an exponential backoff policy with a limited number of retries:
> limitedBackoff = exponentialBackoff 50 <> limitRetries 5
Naturally, mempty
will retry immediately (delay 0) for an
unlimited number of retries, forming the identity for the Monoid
.
The default under def
implements a constant 50ms delay, up to 5 times:
> def = constantDelay 50000 <> limitRetries 5
For anything more complex, just define your own RetryPolicy
:
> myPolicy = RetryPolicy $ \ n -> if n > 10 then Just 1000 else Just 10000
Constructors
RetryPolicy | |
Fields
|
Instances
Arguments
:: MonadIO m | |
=> RetryPolicy | |
-> (Int -> b -> m Bool) | An action to check whether the result should be retried. If True, we delay and retry the operation. |
-> m b | Action to run |
-> m b |
Retry combinator for actions that don't raise exceptions, but
signal in their type the outcome has failed. Examples are the
Maybe
, Either
and EitherT
monads.
Let's write a function that always fails and watch this combinator retry it 5 additional times following the initial run:
>>>
import Data.Maybe
>>>
let f = putStrLn "Running action" >> return Nothing
>>>
retrying def (const $ return . isNothing) f
Running action Running action Running action Running action Running action Running action Nothing
Note how the latest failing result is returned after all retries have been exhausted.
Arguments
:: (MonadIO m, MonadMask m) | |
=> RetryPolicy | Just use |
-> [Int -> Handler m Bool] | Should a given exception be retried? Action will be retried if this returns True. |
-> m a | Action to perform |
-> m a |
Run an action and recover from a raised exception by potentially retrying the action a number of times.
recoverAll :: (MonadIO m, MonadMask m) => RetryPolicy -> m a -> m a Source
Retry ALL exceptions that may be raised. To be used with caution;
this matches the exception on SomeException
.
See how the action below is run once and retried 5 more times before finally failing for good:
>>>
let f = putStrLn "Running action" >> error "this is an error"
>>>
recoverAll def f
Running action Running action Running action Running action Running action Running action *** Exception: this is an error
Arguments
:: (Monad m, Show e, Exception e) | |
=> (e -> m Bool) | Test for whether action is to be retried |
-> (String -> m ()) | How to report the generated warning message. |
-> Int | Retry number |
-> Handler m Bool |
Helper function for constructing handler functions of the form required
by recovering
.
Retry Policies
Arguments
:: Int | Base delay in microseconds |
-> RetryPolicy |
Implement a constant delay with unlimited retries.
Arguments
:: Int | Base delay in microseconds |
-> RetryPolicy |
Grow delay exponentially each iteration.
Arguments
:: Int | Base delay in microseconds |
-> RetryPolicy |
Implement Fibonacci backoff.
Arguments
:: Int | Maximum number of retries. |
-> RetryPolicy |
Retry immediately, but only up to n
times.
Arguments
:: Int | Time-delay limit in microseconds. |
-> RetryPolicy | |
-> RetryPolicy |
Add an upperbound to a policy such that once the given time-delay amount has been reached or exceeded, the policy will stop retrying and fail.
Arguments
:: Int | A maximum delay in microseconds |
-> RetryPolicy | |
-> RetryPolicy |
Set a time-upperbound for any delays that may be directed by the
given policy. This function does not terminate the retrying. The policy
`capDelay maxDelay (exponentialBackoff n)` will never stop retrying. It
will reach a state where it retries forever with a delay of maxDelay
between each one. To get termination you need to use one of the
limitRetries
function variants.