HORIZON HASKELLDocslts/ghc-9.10.xc74966e2026-09-27Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · c74966e · 2026-09-27

Moduleretry-0.9.3.1Haskell2010

Control.Retry

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.

  • 4 types
  • 35 values
  • Packageretry-0.9.3.1
  • Exports39
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceRetry.hs

Types and Operations

11 declarations
newtypenewtype RetryPolicyM (m :: Type -> Type)
#

A RetryPolicyM is a function that takes an RetryStatus and possibly returns a delay in microseconds. Iteration numbers start at zero and increase by one on each retry. A *Nothing* return value from the function implies we have reached the retry limit.

Please note that RetryPolicyM is a Monoid. You can collapse multiple strategies into one using mappend or <>. The semantics of this combination are as follows:

  1. If either policy returns Nothing, the combined policy returns Nothing. This can be used to inhibit after a number of retries, for example.

  2. 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 50000 <> limitRetries 5

Naturally, mempty will retry immediately (delay 0) for an unlimited number of retries, forming the identity for the Monoid.

The default retry policy retryPolicyDefault implements a constant 50ms delay, up to 5 times:

> retryPolicyDefault = constantDelay 50000 <> limitRetries 5

For anything more complex, just define your own RetryPolicyM:

> myPolicy = retryPolicy $ \ rs -> if rsIterNumber rs > 10 then Just 1000 else Just 10000

Since 0.7.

Instances2Semigroup, Monoid
valuenatTransformRetryPolicy
  1. :: forall a. m a -> n a
  2. -> RetryPolicyM m
  3. -> RetryPolicyM n
#

Applies a natural transformation to a policy to run a RetryPolicy meant for the monad m in the monad n provided a transformation from m to n is available. A common case is if you have a pure policy, RetryPolicyM Identity and want to use it to govern an IO computation you could write:

  purePolicyInIO :: RetryPolicyM Identity -> RetryPolicyM IO
  purePolicyInIO = natTransformRetryPolicy (pure . runIdentity)
datadata RetryAction
#

How to handle a failed action.

Constructors

Instances5Eq, Read, Show, Generic, Rep
datadata RetryStatus
#

Datatype with stats about retries made thus far.

Constructors

Instances5Eq, Read, Show, Generic, Rep

Lenses for RetryStatus

Applying Retry Policies

10 declarations
valueretrying
  1. :: MonadIO m
  2. => RetryPolicyM m
  3. -> (RetryStatus -> b -> m Bool)

    An action to check whether the result should be retried. If True, we delay and retry the operation.

  4. -> (RetryStatus -> m b)

    Action to run

  5. -> 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:

Example3 expressions
import Data.Maybelet f _ = putStrLn "Running action" >> return Nothingretrying retryPolicyDefault (const $ return . isNothing) fRunning actionRunning actionRunning actionRunning actionRunning actionRunning actionNothing

Note how the latest failing result is returned after all retries have been exhausted.

valueretryingDynamic
  1. :: MonadIO m
  2. => RetryPolicyM m
  3. -> (RetryStatus -> b -> m RetryAction)

    An action to check whether the result should be retried. The returned RetryAction determines how/if a retry is performed. See documentation on RetryAction.

  4. -> (RetryStatus -> m b)

    Action to run

  5. -> m b
#

Same as retrying, but with the ability to override the delay of the retry policy based on information obtained after initiation.

For example, if the action to run is a HTTP request that turns out to fail with a status code 429 ("too many requests"), the response may contain a "Retry-After" HTTP header which specifies the number of seconds the client should wait until performing the next request. This function allows overriding the delay calculated by the given retry policy with the delay extracted from this header value.

In other words, given an arbitrary RetryPolicyM rp, the following invocation will always delay by 1000 microseconds:

retryingDynamic rp (\_ _ -> return $ ConsultPolicyOverrideDelay 1000) f

Note that a RetryPolicys decision to not perform a retry cannot be overridden. Ie. when to stop retrying is always decided by the retry policy, regardless of the returned RetryAction value.

valuerecovering
  1. :: (MonadIO m, MonadMask m)
  2. => RetryPolicyM m

    Just use retryPolicyDefault for default settings

  3. -> [RetryStatus -> Handler m Bool]

    Should a given exception be retried? Action will be retried if this returns True *and* the policy allows it. This action will be consulted first even if the policy later blocks it.

  4. -> (RetryStatus -> m a)

    Action to perform

  5. -> m a
#

Run an action and recover from a raised exception by potentially retrying the action a number of times. Note that if you're going to use a handler for SomeException, you should add explicit cases *earlier* in the list of handlers to reject AsyncException and SomeAsyncException, as catching these can cause thread and program hangs. recoverAll already does this for you so if you just plan on catching SomeException, you may as well use recoverAll

valuerecoveringDynamic
  1. :: (MonadIO m, MonadMask m)
  2. => RetryPolicyM m

    Just use retryPolicyDefault for default settings

  3. -> [RetryStatus -> Handler m RetryAction]

    Should a given exception be retried? Action will be retried if this returns either ConsultPolicy or ConsultPolicyOverrideDelay *and* the policy allows it. This action will be consulted first even if the policy later blocks it.

  4. -> (RetryStatus -> m a)

    Action to perform

  5. -> m a
#

The difference between this and recovering is the same as the difference between retryingDynamic and retrying.

valuestepping
  1. :: (MonadIO m, MonadMask m)
  2. => RetryPolicyM m

    Just use retryPolicyDefault for default settings

  3. -> [RetryStatus -> Handler m Bool]

    Should a given exception be retried? Action will be retried if this returns True *and* the policy allows it. This action will be consulted first even if the policy later blocks it.

  4. -> (RetryStatus -> m ())

    Action to run with updated status upon failure.

  5. -> (RetryStatus -> m a)

    Main action to perform with current status.

  6. -> RetryStatus

    Current status of this step

  7. -> m (Maybe a)
#

A version of recovering that tries to run the action only a single time. The control will return immediately upon both success and failure. Useful for implementing retry logic in distributed queues and similar external-interfacing systems.

valuerecoverAll
  1. :: (MonadIO m, MonadMask m)
  2. => RetryPolicyM m
  3. -> RetryStatus -> m a
  4. -> m a
#

Retry ALL exceptions that may be raised. To be used with caution; this matches the exception on SomeException. Note that this handler explicitly does not handle AsyncException nor SomeAsyncException (for versions of base >= 4.7). It is not a good idea to catch async exceptions as it can result in hanging threads and programs. Note that if you just throw an exception to this thread that does not descend from SomeException, recoverAll will not catch it.

See how the action below is run once and retried 5 more times before finally failing for good:

Example2 expressions
let f _ = putStrLn "Running action" >> error "this is an error"recoverAll retryPolicyDefault fRunning actionRunning actionRunning actionRunning actionRunning actionRunning action*** Exception: this is an error

Resumable variants

valueresumeRecovering
  1. :: (MonadIO m, MonadMask m)
  2. => RetryStatus
  3. -> RetryPolicyM m

    Just use retryPolicyDefault for default settings

  4. -> [RetryStatus -> Handler m Bool]

    Should a given exception be retried? Action will be retried if this returns True *and* the policy allows it. This action will be consulted first even if the policy later blocks it.

  5. -> (RetryStatus -> m a)

    Action to perform

  6. -> m a
#

A variant of recovering that allows specifying the initial RetryStatus so that a recovering operation may pick up where it left off in regards to its retry policy.

valueresumeRecoveringDynamic
  1. :: (MonadIO m, MonadMask m)
  2. => RetryStatus
  3. -> RetryPolicyM m

    Just use retryPolicyDefault for default settings

  4. -> [RetryStatus -> Handler m RetryAction]

    Should a given exception be retried? Action will be retried if this returns either ConsultPolicy or ConsultPolicyOverrideDelay *and* the policy allows it. This action will be consulted first even if the policy later blocks it.

  5. -> (RetryStatus -> m a)

    Action to perform

  6. -> m a
#

A variant of recoveringDynamic that allows specifying the initial RetryStatus so that a recovering operation may pick up where it left off in regards to its retry policy.

Retry Policies

5 declarations
valuefullJitterBackoff
  1. :: MonadIO m
  2. => Int

    Base delay in microseconds

  3. -> RetryPolicyM m
#

FullJitter exponential backoff as explained in AWS Architecture Blog article.

http://www.awsarchitectureblog.com/2015/03/backoff.html

temp = min(cap, base * 2 ** attempt)

sleep = temp / 2 + random_between(0, temp / 2)

Policy Transformers

3 declarations
valuecapDelay
  1. :: Monad m
  2. => Int

    A maximum delay in microseconds

  3. -> RetryPolicyM m
  4. -> RetryPolicyM m
#

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.

Development Helpers

2 declarations
valuesimulatePolicy :: Monad m => Int -> RetryPolicyM m -> m [(Int, Maybe Int)]
#

Run given policy up to N iterations and gather results. In the pair, the Int is the iteration number and the Maybe Int is the delay in microseconds.