Hello community, here is the log from the commit of package ghc-retry for openSUSE:Factory checked in at 2016-01-08 15:22:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-retry (Old) and /work/SRC/openSUSE:Factory/.ghc-retry.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-retry" Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-retry/ghc-retry.changes 2015-11-26 17:03:06.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.ghc-retry.new/ghc-retry.changes 2016-01-08 15:22:47.000000000 +0100 @@ -1,0 +2,17 @@ +Wed Dec 23 16:32:10 UTC 2015 - mimi...@gmail.com + +- update to 0.7.0.1 +* RetryPolicy has become RetryPolicyM, allowing for policy logic to consult the + monad context. +* RetryPolicyM now takes a RetryStatus value. Use the function rsIterNum to + preserve existing behavior of RetryPolicy only receiving the number. +* The monadic action now gets the RetryStatus on each try. Use const if you + don't need it. +* recoverAll explicitly does not handle the standard async exceptions. Users are + encouraged to do the same when using recovering, as catching async exceptions + can be hazardous. +* We no longer re-export (<>) from Monoid. +* Utility functions simulatePolicy and simulatePolicyPP have been added which help + predict how a policy will behave on each iteration. + +------------------------------------------------------------------- Old: ---- retry-0.6.tar.gz New: ---- retry-0.7.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-retry.spec ++++++ --- /var/tmp/diff_new_pack.ewQWge/_old 2016-01-08 15:22:48.000000000 +0100 +++ /var/tmp/diff_new_pack.ewQWge/_new 2016-01-08 15:22:48.000000000 +0100 @@ -20,7 +20,7 @@ %bcond_with tests Name: ghc-retry -Version: 0.6 +Version: 0.7.0.1 Release: 0 Summary: Retry combinators for monadic actions that may fail Group: System/Libraries @@ -35,6 +35,7 @@ # Begin cabal-rpm deps: BuildRequires: ghc-data-default-class-devel BuildRequires: ghc-exceptions-devel +BuildRequires: ghc-random-devel BuildRequires: ghc-transformers-devel %if %{with tests} BuildRequires: ghc-HUnit-devel ++++++ retry-0.6.tar.gz -> retry-0.7.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.6/README.md new/retry-0.7.0.1/README.md --- old/retry-0.6/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/retry-0.7.0.1/README.md 2015-11-15 01:28:27.000000000 +0100 @@ -0,0 +1,36 @@ +# README [![Build Status](https://travis-ci.org/Soostone/retry.svg?branch=master)](https://travis-ci.org/Soostone/retry) [![Coverage Status](https://coveralls.io/repos/Soostone/retry/badge.png?branch=master)](https://coveralls.io/r/Soostone/retry?branch=master) + +retry - combinators for monadic actions that may fail + +## About + +Monadic action combinators that add delayed-retry functionality, +potentially with exponential-backoff, to arbitrary actions. + +The main purpose of this package is to make it easy to work reliably +with IO and similar actions that often fail. Common examples are +database queries and large file uploads. + + +## Documentation + +Please see haddocks for documentation. + +## Changes + +See [changelog.md](changelog.md). + +## Author + +Ozgun Ataman, Soostone Inc + + +## Contributors + +Contributors, please list yourself here. + +- Mitsutoshi Aoe (@maoe) +- John Wiegley +- Michael Snoyman +- Michael Xavier + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.6/changelog.md new/retry-0.7.0.1/changelog.md --- old/retry-0.6/changelog.md 1970-01-01 01:00:00.000000000 +0100 +++ new/retry-0.7.0.1/changelog.md 2015-11-15 01:28:27.000000000 +0100 @@ -0,0 +1,49 @@ +0.7.0.1 +* Officially drop support for GHC < 7.6 due to usage of Generics. + +0.7 +* RetryPolicy has become RetryPolicyM, allowing for policy logic to + consult the monad context. +* RetryPolicyM now takes a RetryStatus value. Use the function + rsIterNum to preserve existing behavior of RetryPolicy only + receiving the number. +* The monadic action now gets the RetryStatus on each try. Use const + if you don't need it. +* recoverAll explicitly does not handle the standard async + exceptions. Users are encouraged to do the same when using + recovering, as catching async exceptions can be hazardous. +* We no longer re-export (<>) from Monoid. +* Utility functions simulatePolicy and simulatePolicyPP have been + added which help predict how a policy will behave on each iteration. + +0.6 + +* Actions are now retried in the original masking state, while + handlers continue to run in `MaskedInterruptible` (@maoe) +* Added several tests confirming exception hierarchy semantics under + `recovering` (@ozataman) + +0.5 + +* Mitsutoshi's backoff work inspired a complete redo of the + RetryPolicy interface, replacing it with a monoidal RetryPolicy. The + result is a much thinner API that actually provides much more power + to the end user. +* Now using microseconds in all premade policies. PLEASE TAKE CARE + WHEN UPGRADING. It was a bad idea to use miliseconds and deviate + from norms in the first place. + +0.4 + +* Transitioned to using Edward Kmett's exceptions package instead of + monad-control. Use 0.3 series if you still need monad-control + support. + +0.3 + +Thanks to John Wiegley and Michael Snoyman for their contributions: + +* Now using monad-control instead of MonadCatchIO, which is widely + agreed to be broken. +* Now using transformers instead of mtl, which was a broader than + needed dependency. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.6/retry.cabal new/retry-0.7.0.1/retry.cabal --- old/retry-0.6/retry.cabal 2015-03-03 20:33:00.000000000 +0100 +++ new/retry-0.7.0.1/retry.cabal 2015-11-15 01:28:27.000000000 +0100 @@ -1,6 +1,6 @@ name: retry -description: +description: This package exposes combinators that can wrap arbitrary monadic actions. They run the action and potentially retry @@ -14,7 +14,7 @@ case we should hang back for a bit and retry the query instead of simply raising an exception. -version: 0.6 +version: 0.7.0.1 synopsis: Retry combinators for monadic actions that may fail license: BSD3 license-file: LICENSE @@ -24,14 +24,18 @@ category: Control build-type: Simple cabal-version: >=1.10 -Homepage: http://github.com/Soostone/retry +homepage: http://github.com/Soostone/retry +extra-source-files: + README.md + changelog.md library exposed-modules: Control.Retry - build-depends: - base ==4.* + build-depends: + base >= 4.6 && < 5 , data-default-class , exceptions >= 0.5 && < 0.9 + , random >= 1 && < 1.2 , transformers < 0.5 hs-source-dirs: src default-language: Haskell2010 @@ -42,18 +46,15 @@ main-is: main.hs hs-source-dirs: test,src ghc-options: -threaded - build-depends: - base ==4.* + build-depends: + base ==4.* , exceptions , transformers , data-default-class + , random , time - , QuickCheck >= 2.7 && < 2.8 + , QuickCheck >= 2.7 && < 2.9 , HUnit >= 1.2.5.2 && < 1.3 , hspec >= 1.9 + , stm default-language: Haskell2010 - - - - - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.6/src/Control/Retry.hs new/retry-0.7.0.1/src/Control/Retry.hs --- old/retry-0.6/src/Control/Retry.hs 2015-03-03 20:33:00.000000000 +0100 +++ new/retry-0.7.0.1/src/Control/Retry.hs 2015-11-15 01:28:27.000000000 +0100 @@ -1,9 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} @@ -30,9 +28,23 @@ module Control.Retry ( - -- * High Level Operation - RetryPolicy (..) + -- * Types and Operations + RetryPolicyM (..) + , RetryPolicy + , retryPolicy + , RetryStatus + -- ** Fields for 'RetryStatus' + , rsIterNumber + , rsCumulativeDelay + , rsPreviousDelay + , defaultRetryStatus + + -- ** Lenses for 'RetryStatus' + , rsIterNumberL + , rsCumulativeDelayL + , rsPreviousDelayL + -- * Applying Retry Policies , retrying , recovering , recoverAll @@ -41,33 +53,51 @@ -- * Retry Policies , constantDelay , exponentialBackoff + , fullJitterBackoff , fibonacciBackoff , limitRetries + + -- * Policy Transformers , limitRetriesByDelay , capDelay - -- * Re-export from Data.Monoid - - , (<>) - + -- * Development Helpers + , simulatePolicy + , simulatePolicyPP ) where ------------------------------------------------------------------------------- +import Control.Applicative +import Control.Arrow import Control.Concurrent +#if MIN_VERSION_base(4, 7, 0) +import Control.Exception (AsyncException, SomeAsyncException) +#else +import Control.Exception (AsyncException) +#endif +import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class +import Control.Monad.Trans.Class +import Control.Monad.Trans.Maybe +import Control.Monad.Trans.State import Data.Default.Class +import Data.Functor.Identity +import Data.Maybe +import GHC.Generics +import System.Random import Data.Monoid import Prelude hiding (catch) ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- --- | 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. +-- | 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 'RetryPolicy' is a 'Monoid'. You can collapse +-- 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: -- @@ -93,30 +123,87 @@ -- -- >> def = constantDelay 50000 <> limitRetries 5 -- --- For anything more complex, just define your own 'RetryPolicy': +-- For anything more complex, just define your own 'RetryPolicyM': +-- +-- >> myPolicy = retryPolicy $ \ rs -> if rsIterNumber n > 10 then Just 1000 else Just 10000 -- --- >> myPolicy = RetryPolicy $ \ n -> if n > 10 then Just 1000 else Just 10000 -newtype RetryPolicy = RetryPolicy { getRetryPolicy :: Int -> Maybe Int } +-- Since 0.7. +newtype RetryPolicyM m = RetryPolicyM { getRetryPolicyM :: RetryStatus -> m (Maybe Int) } + + +-- | Simplified 'RetryPolicyM' without any use of the monadic context in +-- determining policy. Mostly maintains backwards compatitibility with +-- type signatures pre-0.7. +type RetryPolicy = forall m . Monad m => RetryPolicyM m -instance Default RetryPolicy where +instance Monad m => Default (RetryPolicyM m) where def = constantDelay 50000 <> limitRetries 5 -instance Monoid RetryPolicy where - mempty = RetryPolicy $ (const (Just 0)) - (RetryPolicy a) `mappend` (RetryPolicy b) = RetryPolicy $ \ n -> do - a' <- a n - b' <- b n + +instance Monad m => Monoid (RetryPolicyM m) where + mempty = retryPolicy $ const (Just 0) + (RetryPolicyM a) `mappend` (RetryPolicyM b) = RetryPolicyM $ \ n -> runMaybeT $ do + a' <- MaybeT $ a n + b' <- MaybeT $ b n return $! max a' b' ------------------------------------------------------------------------------- +-- | Datatype with stats about retries made thus far. The constructor +-- is deliberately not exported to make additional fields easier to +-- add in a backward-compatible manner. To read or modify fields in +-- RetryStatus, use the accessors or lenses below. Note that if you +-- don't want to use lenses, the exported field names can be used for +-- updates: +-- +-- >> retryStatus { rsIterNumber = newIterNumber } +-- >> retryStatus & rsIterNumberL .~ newIterNumber +data RetryStatus = RetryStatus + { rsIterNumber :: !Int -- ^ Iteration number, where 0 is the first try + , rsCumulativeDelay :: !Int -- ^ Delay incurred so far from retries in microseconds + , rsPreviousDelay :: !(Maybe Int) -- ^ Previous attempt's delay. Will always be Nothing on first run. + } deriving (Show, Eq, Generic) + + +------------------------------------------------------------------------------- +-- | Initial, default retry status. Exported mostly to allow user code +-- to test their handlers and retry policies. Use fields or lenses to update. +defaultRetryStatus :: RetryStatus +defaultRetryStatus = RetryStatus 0 0 Nothing + +------------------------------------------------------------------------------- +rsIterNumberL :: Lens' RetryStatus Int +rsIterNumberL = lens rsIterNumber (\rs x -> rs { rsIterNumber = x }) +{-# INLINE rsIterNumberL #-} + + +------------------------------------------------------------------------------- +rsCumulativeDelayL :: Lens' RetryStatus Int +rsCumulativeDelayL = lens rsCumulativeDelay (\rs x -> rs { rsCumulativeDelay = x }) +{-# INLINE rsCumulativeDelayL #-} + + +------------------------------------------------------------------------------- +rsPreviousDelayL :: Lens' RetryStatus (Maybe Int) +rsPreviousDelayL = lens rsPreviousDelay (\rs x -> rs { rsPreviousDelay = x }) +{-# INLINE rsPreviousDelayL #-} + + +------------------------------------------------------------------------------- +-- | Helper for making simplified policies that don't use the monadic +-- context. +retryPolicy :: (RetryStatus -> Maybe Int) -> RetryPolicy +retryPolicy f = RetryPolicyM $ \ s -> return (f s) + + +------------------------------------------------------------------------------- -- | Retry immediately, but only up to @n@ times. limitRetries :: Int -- ^ Maximum number of retries. -> RetryPolicy -limitRetries i = RetryPolicy $ \ n -> if n >= i then Nothing else (Just 0) +limitRetries i = retryPolicy $ \ RetryStatus { rsIterNumber = n} -> if n >= i then Nothing else (Just 0) ------------------------------------------------------------------------------- @@ -125,10 +212,11 @@ -- and fail. limitRetriesByDelay :: Int - -- ^ Time-delay limit in microseconds. + -- ^ Time-delay limit in microseconds. -> RetryPolicy -> RetryPolicy -limitRetriesByDelay i p = RetryPolicy $ \ n -> getRetryPolicy p n >>= limit +limitRetriesByDelay i p = RetryPolicyM $ \ n -> + (>>= limit) `liftM` getRetryPolicyM p n where limit delay = if delay >= i then Nothing else Just delay @@ -139,16 +227,33 @@ :: Int -- ^ Base delay in microseconds -> RetryPolicy -constantDelay delay = RetryPolicy (const (Just delay)) +constantDelay delay = retryPolicy (const (Just delay)) ------------------------------------------------------------------------------- --- | Grow delay exponentially each iteration. +-- | Grow delay exponentially each iteration. Each delay will +-- increase by a factor of two. exponentialBackoff :: Int - -- ^ Base delay in microseconds + -- ^ First delay in microseconds -> RetryPolicy -exponentialBackoff base = RetryPolicy $ \ n -> Just (2^n * base) +exponentialBackoff base = retryPolicy $ \ RetryStatus { rsIterNumber = n} -> Just (2^n * base) + + +------------------------------------------------------------------------------- +-- | 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) +fullJitterBackoff :: MonadIO m => Int -> RetryPolicyM m +fullJitterBackoff base = RetryPolicyM $ \RetryStatus { rsIterNumber = n } -> do + let d = (2^n * base) `div` 2 + rand <- liftIO $ randomRIO (0, d) + return $ Just $! d + rand ------------------------------------------------------------------------------- @@ -157,7 +262,7 @@ :: Int -- ^ Base delay in microseconds -> RetryPolicy -fibonacciBackoff base = RetryPolicy $ \ n -> Just $ fib (n + 1) (0, base) +fibonacciBackoff base = retryPolicy $ \ RetryStatus { rsIterNumber = n } -> Just $ fib (n + 1) (0, base) where fib 0 (a, _) = a fib !m (!a, !b) = fib (m-1) (b, a + b) @@ -171,11 +276,13 @@ -- between each one. To get termination you need to use one of the -- 'limitRetries' function variants. capDelay - :: Int + :: Monad m + => Int -- ^ A maximum delay in microseconds - -> RetryPolicy - -> RetryPolicy -capDelay limit p = RetryPolicy $ \ n -> min limit `fmap` (getRetryPolicy p) n + -> RetryPolicyM m + -> RetryPolicyM m +capDelay limit p = RetryPolicyM $ \ n -> + (fmap (min limit)) `liftM` (getRetryPolicyM p) n ------------------------------------------------------------------------------- @@ -187,7 +294,7 @@ -- retry it 5 additional times following the initial run: -- -- >>> import Data.Maybe --- >>> let f = putStrLn "Running action" >> return Nothing +-- >>> let f _ = putStrLn "Running action" >> return Nothing -- >>> retrying def (const $ return . isNothing) f -- Running action -- Running action @@ -199,38 +306,46 @@ -- -- Note how the latest failing result is returned after all retries -- have been exhausted. -retrying :: 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 -retrying (RetryPolicy policy) chk f = go 0 - where - go n = do - res <- f - chk' <- chk n res - case chk' of - True -> - case (policy n) of - Just delay -> do - liftIO (threadDelay delay) - go $! n+1 - Nothing -> return res - False -> return res - +retrying :: MonadIO m + => RetryPolicyM m + -> (RetryStatus -> b -> m Bool) + -- ^ An action to check whether the result should be retried. + -- If True, we delay and retry the operation. + -> (RetryStatus -> m b) + -- ^ Action to run + -> m b +retrying (RetryPolicyM policy) chk f = go defaultRetryStatus + where + go s = do + res <- f s + chk' <- chk s res + case chk' of + True -> do + chk <- policy s + case chk of + Just delay -> do + liftIO (threadDelay delay) + go $! RetryStatus { rsIterNumber = rsIterNumber s + 1 + , rsCumulativeDelay = rsCumulativeDelay s + delay + , rsPreviousDelay = Just (maybe 0 (const delay) (rsPreviousDelay s))} + Nothing -> return res + False -> return res ------------------------------------------------------------------------------- -- | Retry ALL exceptions that may be raised. To be used with caution; --- this matches the exception on 'SomeException'. +-- 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 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" +-- >>> let f _ = putStrLn "Running action" >> error "this is an error" -- >>> recoverAll def f -- Running action -- Running action @@ -245,52 +360,71 @@ #else :: (MonadIO m, MonadCatch m) #endif - => RetryPolicy - -> m a + => RetryPolicyM m + -> (RetryStatus -> m a) -> m a -recoverAll set f = recovering set [h] f +recoverAll set f = recovering set handlers f where +#if MIN_VERSION_base(4, 7, 0) + someAsyncH _ = Handler $ \(_ :: SomeAsyncException) -> return False + handlers = [asyncH, someAsyncH, h] +#else + handlers = [asyncH, h] +#endif + asyncH _ = Handler $ \ (_ :: AsyncException) -> return False h _ = Handler $ \ (_ :: SomeException) -> return True ------------------------------------------------------------------------------- -- | Run an action and recover from a raised exception by potentially --- retrying the action a number of times. +-- 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 ues +-- 'recoverAll' recovering #if MIN_VERSION_exceptions(0, 6, 0) :: (MonadIO m, MonadMask m) #else :: (MonadIO m, MonadCatch m) #endif - => RetryPolicy + => RetryPolicyM m -- ^ Just use 'def' for default settings - -> [(Int -> Handler m Bool)] + -> [(RetryStatus -> Handler m Bool)] -- ^ Should a given exception be retried? Action will be - -- retried if this returns True. - -> m a + -- 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 (RetryPolicy policy) hs f = mask $ \restore -> go restore 0 +recovering p@(RetryPolicyM policy) hs f = mask $ \restore -> go restore defaultRetryStatus where go restore = loop where - loop n = do - r <- try $ restore f + loop s = do + r <- try $ restore (f s) case r of Right x -> return x Left e -> recover (e :: SomeException) hs where recover e [] = throwM e - recover e ((($ n) -> Handler h) : hs') + recover e ((($ s) -> Handler h) : hs') | Just e' <- fromException e = do chk <- h e' - if chk - then case policy n of - Just delay -> do - liftIO $ threadDelay delay - loop $! n+1 - Nothing -> throwM e' - else throwM e' + case chk of + True -> do + res <- policy s + case res of + Just delay -> do + liftIO $ threadDelay delay + loop $! RetryStatus { rsIterNumber = rsIterNumber s + 1 + , rsCumulativeDelay = rsCumulativeDelay s + delay + , rsPreviousDelay = Just (maybe 0 (const delay) (rsPreviousDelay s))} + Nothing -> throwM e' + False -> throwM e' | otherwise = recover e hs' @@ -301,17 +435,65 @@ :: (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 + -> (Bool -> String -> m ()) + -- ^ How to report the generated warning message. Boolean is + -- whether it's being retried or crashed. + -> RetryStatus -- ^ Retry number -> Handler m Bool -logRetries f report n = Handler $ \ e -> do +logRetries f report s = Handler $ \ e -> do res <- f e let msg = "[retry:" <> show n <> "] Encountered " <> show e <> ". " <> if res then "Retrying." else "Crashing." - report msg + report res msg return res + where n = rsIterNumber s + + +------------------------------------------------------------------------------- +-- | 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. +simulatePolicy :: Monad m => Int -> RetryPolicyM m -> m [(Int, Maybe Int)] +simulatePolicy n (RetryPolicyM f) = flip evalStateT defaultRetryStatus $ forM [0..n] $ \i -> do + stat <- get + delay <- lift (f stat) + put stat { rsIterNumber = i + 1, rsCumulativeDelay = rsCumulativeDelay stat + fromMaybe 0 delay} + return (i, delay) + + +------------------------------------------------------------------------------- +-- | Run given policy up to N iterations and pretty print results on +-- the console. +simulatePolicyPP :: Int -> RetryPolicyM IO -> IO () +simulatePolicyPP n p = do + ps <- simulatePolicy n p + forM_ ps $ \ (n, res) -> putStrLn $ + show n <> ": " <> maybe "Inhibit" ppTime res + putStrLn $ "Total cumulative delay would be: " <> + (ppTime $ sum $ (mapMaybe snd) ps) + + +------------------------------------------------------------------------------- +ppTime :: (Integral a, Show a) => a -> String +ppTime n | n < 1000 = show n <> "us" + | n < 1000000 = show (fromIntegral n / 1000) <> "ms" + | otherwise = show (fromIntegral n / 1000) <> "ms" + + +------------------------------------------------------------------------------- +-- Lens machinery +------------------------------------------------------------------------------- +-- Unexported type aliases to clean up the documentation +type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t + +type Lens' s a = Lens s s a a + + +------------------------------------------------------------------------------- +lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b +lens sa sbt afb s = sbt s <$> afb (sa s) +{-# INLINE lens #-} ------------------