packages feed

retry (empty) → 0.1.0.0

raw patch · 4 files changed

+285/−0 lines, 4 filesdep +MonadCatchIO-transformersdep +basedep +data-defaultsetup-changed

Dependencies added: MonadCatchIO-transformers, base, data-default, mtl, syb

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Ozgun Ataman++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Ozgun Ataman nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ retry.cabal view
@@ -0,0 +1,38 @@+name:                retry++description: ++        This package 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 purpose is to make it easier to work with IO and+        especially network IO actions that often experience temporary+        failure and warrant retrying of the original action. For+        example, a database query may time out for a while, in which+        case we should hang back for a bit and retry the query instead+        of simply raising an exception.++version:             0.1.0.0+synopsis:            Retry combinators for monadic actions that may fail+license:             BSD3+license-file:        LICENSE+author:              Ozgun Ataman+maintainer:          ozgun.ataman@soostone.com+copyright:           Ozgun Ataman, Soostone Inc+category:            Control+build-type:          Simple+cabal-version:       >=1.10++library+  exposed-modules:     Control.Retry+  build-depends:       +    base ==4.*, +    MonadCatchIO-transformers >= 0.3,+    mtl >= 0.2,+    data-default,+    syb >= 0.3+  hs-source-dirs:      src+  default-language:    Haskell2010+
+ src/Control/Retry.hs view
@@ -0,0 +1,215 @@+{-# LANGUAGE BangPatterns        #-}+{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Retry+-- Copyright   :  Ozgun Ataman <ozgun.ataman@soostone.com>+-- License     :  BSD3+--+-- Maintainer  :  Ozgun Ataman+-- Stability   :  provisional+--+-- 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.+----------------------------------------------------------------------------+++module Control.Retry+    (+      -- * High Level Operation+      RetrySettings (..)+    , retrying+    , recovering+    , recoverAll++    -- * Utilities+    , delay+    , flatDelay+    , backoffDelay+    ) where++-------------------------------------------------------------------------------+import           Control.Concurrent+import           Control.Exception     (SomeException)+import           Control.Monad.CatchIO+import           Control.Monad.Trans+import           Data.Default+import           Data.Generics+import           Prelude               hiding (catch)+-------------------------------------------------------------------------------+++-- | Settings for retry behavior. Simply using 'def' for default+-- values should work in most cases.+data RetrySettings = RetrySettings {+      numRetries :: Int+    -- ^ Number of retries. Defaults to 5.+    , backoff    :: Bool+    -- ^ Whether to implement exponential backoff in retries. Defaults+    -- to True.+    , baseDelay  :: Int+    -- ^ The base delay in miliseconds. Defaults to 50. Without+    -- 'backoff', this is the delay. With 'backoff', this base delay+    -- will grow by a factor of 2 on each subsequent retry.+    }+++instance Default RetrySettings where+    def = RetrySettings 5 True 50+++-- | Delay thread using backoff delay for the nth retry.+backoffDelay :: (Integral b, MonadIO m) => RetrySettings -> b -> m ()+backoffDelay set@RetrySettings{..} !n = liftIO (threadDelay (2^n * delay set))++-- | Delay thread using flat delay+flatDelay :: MonadIO m => RetrySettings -> t -> m ()+flatDelay set@RetrySettings{..} !n = liftIO (threadDelay $ delay set)++-- | Delay in micro seconds+delay :: RetrySettings -> Int+delay RetrySettings{..} = baseDelay * 1000+++-- | 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 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.+retrying :: MonadIO m+         => RetrySettings+         -> (b -> Bool)+         -- ^ A function to check whether the result should be+         -- retried. If True, we delay and retry the operation.+         -> m b+         -- ^ Action to run+         -> m b+retrying set@RetrySettings{..} chk f = go 0+    where+      go n = do+          res <- f+          case chk res of+            True ->+              case n >= numRetries of+                True -> return res+                False -> do+                    if backoff+                      then backoffDelay set n+                      else flatDelay set n+                    go $! n+1+            False -> return res++++-- | 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+recoverAll :: MonadCatchIO m+         => RetrySettings+         -> m a+         -> m a+recoverAll set f = recovering set [h] f+    where+      h = Handler $ \ (e :: SomeException) -> return True+++-- | Run an action and recover from a raised exception by potentially+-- retrying the action a number of times.+recovering :: forall m a. MonadCatchIO m+           => RetrySettings+           -- ^ Just use 'def' faor default settings+           -> [Handler m Bool]+           -- ^ Should a given exception be retried? Action will be+           -- retried if this returns True.+           -> m a+           -- ^ Action to perform+           -> m a+recovering set@RetrySettings{..} hs f = go 0+    where+      -- | Convert a (e -> m Bool) handler into (e -> m a) so it can+      -- be wired into the 'catches' combinator.+      transHandler :: Int -> Handler m Bool -> Handler m a+      transHandler n (Handler h) = Handler $ \ e -> do+          chk <- h e+          case chk of+            True ->+              case n >= numRetries of+                True -> throw e+                False -> do+                    if backoff+                      then backoffDelay set n+                      else flatDelay set n+                    go $! n+1+            False -> throw e++      -- handle :: forall e. Exception e => Handler m Bool -> Int -> e -> m a+      -- handle (Handler h) n e = do+      --     chk <- h e+      --     case chk of+      --       True ->+      --         case n >= numRetries of+      --           True -> throw e+      --           False -> if backoff then backoffRetry n else flatRetry n+      --       False -> throw e++      go n = f `catches` map (transHandler n) hs++++                              ------------------+                              -- Simple Tests --+                              ------------------++++-- data TestException = TestException deriving (Show, Typeable)+-- data AnotherException = AnotherException deriving (Show, Typeable)++-- instance Exception TestException+-- instance Exception AnotherException+++-- test = retrying def [h1,h2] f+--     where+--       f = putStrLn "Running action" >> throw AnotherException+--       h1 = Handler $ \ (e :: TestException) -> return False+--       h2 = Handler $ \ (e :: AnotherException) -> return True++