retry-0.9.3.0: src/UnliftIO/Retry.hs
{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module : UnliftIO.Retry
-- Copyright : Ozgun Ataman <ozgun.ataman@soostone.com>
-- License : BSD3
--
-- Maintainer : Patrick Brisbin <pbrisbin@gmail.com>
-- Stability : provisional
--
-- Unlifted "Control.Retry".
--
-- @since 0.9.3.0
----------------------------------------------------------------------------
module UnliftIO.Retry
(
-- * Types and Operations
RetryPolicyM (..)
, RetryPolicy
, retryPolicy
, retryPolicyDefault
, natTransformRetryPolicy
, RetryAction (..)
, toRetryAction
, RetryStatus (..)
, defaultRetryStatus
, applyPolicy
, applyAndDelay
-- ** Lenses for 'RetryStatus'
, rsIterNumberL
, rsCumulativeDelayL
, rsPreviousDelayL
-- * Applying Retry Policies
, retrying
, retryingDynamic
, recovering
, recoveringDynamic
, stepping
, recoverAll
, skipAsyncExceptions
, logRetries
, defaultLogMsg
, retryOnError
-- ** Resumable variants
, resumeRetrying
, resumeRetryingDynamic
, resumeRecovering
, resumeRecoveringDynamic
, resumeRecoverAll
-- * Retry Policies
, constantDelay
, exponentialBackoff
, fullJitterBackoff
, fibonacciBackoff
, limitRetries
-- * Policy Transformers
, limitRetriesByDelay
, limitRetriesByCumulativeDelay
, capDelay
-- * Development Helpers
, simulatePolicy
, simulatePolicyPP
) where
-------------------------------------------------------------------------------
import Control.Retry hiding
( recoverAll
, recovering
, recoveringDynamic
, resumeRecovering
, resumeRecoveringDynamic
, resumeRecoverAll
, stepping
)
import qualified Control.Retry as Retry
import Control.Monad.Catch (Handler(..))
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
import Prelude
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- | 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'
recovering
:: MonadUnliftIO m
=> RetryPolicyM m
-- ^ Just use 'retryPolicyDefault' for default settings
-> [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.
-> (RetryStatus -> m a)
-- ^ Action to perform
-> m a
recovering = resumeRecovering defaultRetryStatus
-------------------------------------------------------------------------------
-- | 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.
resumeRecovering
:: MonadUnliftIO m
=> RetryStatus
-> RetryPolicyM m
-- ^ Just use 'retryPolicyDefault' for default settings
-> [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.
-> (RetryStatus -> m a)
-- ^ Action to perform
-> m a
resumeRecovering retryStatus policy hs f = withRunInIO $ \runInIO ->
Retry.resumeRecovering
retryStatus
(transRetryPolicy runInIO policy)
(map ((.) $ transHandler runInIO) hs)
(runInIO . f)
-------------------------------------------------------------------------------
-- | The difference between this and 'recovering' is the same as
-- the difference between 'retryingDynamic' and 'retrying'.
recoveringDynamic
:: MonadUnliftIO m
=> RetryPolicyM m
-- ^ Just use 'retryPolicyDefault' for default settings
-> [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.
-> (RetryStatus -> m a)
-- ^ Action to perform
-> m a
recoveringDynamic = resumeRecoveringDynamic defaultRetryStatus
-------------------------------------------------------------------------------
-- | 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.
resumeRecoveringDynamic
:: MonadUnliftIO m
=> RetryStatus
-> RetryPolicyM m
-- ^ Just use 'retryPolicyDefault' for default settings
-> [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.
-> (RetryStatus -> m a)
-- ^ Action to perform
-> m a
resumeRecoveringDynamic retryStatus policy hs f = withRunInIO $ \runInIO ->
Retry.resumeRecoveringDynamic
retryStatus
(transRetryPolicy runInIO policy)
(map ((.) $ transHandler runInIO) hs)
(runInIO . f)
-------------------------------------------------------------------------------
-- | 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:
--
-- >>> let f _ = putStrLn "Running action" >> error "this is an error"
-- >>> recoverAll retryPolicyDefault f
-- Running action
-- Running action
-- Running action
-- Running action
-- Running action
-- Running action
-- *** Exception: this is an error
recoverAll
:: MonadUnliftIO m
=> RetryPolicyM m
-> (RetryStatus -> m a)
-> m a
recoverAll = resumeRecoverAll defaultRetryStatus
-------------------------------------------------------------------------------
-- | A variant of 'recoverAll' that allows specifying the initial
-- 'RetryStatus' so that a recovering operation may pick up where it left
-- off in regards to its retry policy.
resumeRecoverAll
:: MonadUnliftIO m
=> RetryStatus
-> RetryPolicyM m
-> (RetryStatus -> m a)
-> m a
resumeRecoverAll retryStatus policy f = withRunInIO $ \runInIO ->
Retry.resumeRecoverAll
retryStatus
(transRetryPolicy runInIO policy)
(runInIO . f)
-------------------------------------------------------------------------------
-- | 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.
stepping
:: MonadUnliftIO m
=> RetryPolicyM m
-- ^ Just use 'retryPolicyDefault' for default settings
-> [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.
-> (RetryStatus -> m ())
-- ^ Action to run with updated status upon failure.
-> (RetryStatus -> m a)
-- ^ Main action to perform with current status.
-> RetryStatus
-- ^ Current status of this step
-> m (Maybe a)
stepping policy hs schedule f s = withRunInIO $ \runInIO ->
Retry.stepping
(transRetryPolicy runInIO policy)
(map ((.) $ transHandler runInIO) hs)
(runInIO . schedule)
(runInIO . f)
s
-------------------------------------------------------------------------------
transRetryPolicy :: (forall a. m a -> n a) -> RetryPolicyM m -> RetryPolicyM n
transRetryPolicy f (RetryPolicyM p) = RetryPolicyM $ f . p
-------------------------------------------------------------------------------
transHandler :: (forall b. m b -> n b) -> Handler m a -> Handler n a
transHandler f (Handler h) = Handler $ f . h