{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DeriveDataTypeable #-} -- | Module containing retry functionality, allowing the construction of services that attempt multiple times in case of transient failure. module Glue.Retry( RetryOptions , defaultRetryOptions , retryingService , retryAllowed , retryInitialWaitTimeMs , maximumRetries , retryWaitTimeMultiplier ) where import Glue.Types import Control.Concurrent.Lifted import Control.Exception.Lifted import Control.Monad.Trans.Control -- | Options for determining behaviour of retrying services. data RetryOptions a = RetryOptions { retryAllowed :: a -> Bool, -- ^ Predicate for determining if we can retry a call, can be used to prevent retries on non-idempotent operations. retryInitialWaitTimeMs :: Int, -- ^ Amount of time to wait after the first failure. maximumRetries :: Int, -- ^ The upper bound on how many attempts to make when invoking the service. retryWaitTimeMultiplier :: Double -- ^ How much to multiply 'retryInitialWaitTimeMs' by for each number of times the service has retried. } -- | Defaulted options for retrying 3 times with no wait time. defaultRetryOptions :: RetryOptions a defaultRetryOptions = RetryOptions { retryAllowed = (\_ -> True) , retryInitialWaitTimeMs = 0 , maximumRetries = 3 , retryWaitTimeMultiplier = 0 } -- | Retries a call to a service multiple times, potentially backing off wait times between subsequent calls. retryingService :: (MonadBaseControl IO m) => RetryOptions a -- ^ Instance of 'RetryOptions' to configure the retry functionality. -> BasicService m a b -- ^ The service to perform retries of. -> BasicService m a b retryingService options service = let attempt retryCount request = if (retryAllowed options) request && maxRetries > retryCount then catch (service request) (\(_ :: SomeException) -> (wait (retryCount + 1)) >> (attempt (retryCount + 1) request)) else service request maxRetries = maximumRetries options wait retryCount = threadDelay $ round $ fromIntegral (retryInitialWaitTimeMs options) * ((retryWaitTimeMultiplier options) ^ retryCount) in attempt 0