Hello community, here is the log from the commit of package ghc-retry for openSUSE:Factory checked in at 2019-12-27 13:56:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-retry (Old) and /work/SRC/openSUSE:Factory/.ghc-retry.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-retry" Fri Dec 27 13:56:56 2019 rev:18 rq:759494 version:0.8.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-retry/ghc-retry.changes 2019-10-18 14:34:55.424006520 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-retry.new.6675/ghc-retry.changes 2019-12-27 13:57:02.292764696 +0100 @@ -1,0 +2,12 @@ +Fri Nov 8 16:14:36 UTC 2019 - Peter Simons <psim...@suse.com> + +- Drop obsolete group attributes. + +------------------------------------------------------------------- +Sat Oct 12 02:02:04 UTC 2019 - psim...@suse.com + +- Update retry to version 0.8.1.0. + 0.8.1.0 + * Add `retryingDynamic` and `recoveringDynamic`. [PR 65](https://github.com/Soostone/retry/pull/65) + +------------------------------------------------------------------- Old: ---- retry-0.8.0.2.tar.gz New: ---- retry-0.8.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-retry.spec ++++++ --- /var/tmp/diff_new_pack.Q4VWAN/_old 2019-12-27 13:57:02.828764955 +0100 +++ /var/tmp/diff_new_pack.Q4VWAN/_new 2019-12-27 13:57:02.828764955 +0100 @@ -19,11 +19,10 @@ %global pkg_name retry %bcond_with tests Name: ghc-%{pkg_name} -Version: 0.8.0.2 +Version: 0.8.1.0 Release: 0 Summary: Retry combinators for monadic actions that may fail License: BSD-3-Clause -Group: Development/Libraries/Haskell URL: https://hackage.haskell.org/package/%{pkg_name} Source0: https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz BuildRequires: ghc-Cabal-devel @@ -53,7 +52,6 @@ %package devel Summary: Haskell %{pkg_name} library development files -Group: Development/Libraries/Haskell Requires: %{name} = %{version}-%{release} Requires: ghc-compiler = %{ghc_version} Requires(post): ghc-compiler = %{ghc_version} ++++++ retry-0.8.0.2.tar.gz -> retry-0.8.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.8.0.2/changelog.md new/retry-0.8.1.0/changelog.md --- old/retry-0.8.0.2/changelog.md 2019-09-27 22:24:28.000000000 +0200 +++ new/retry-0.8.1.0/changelog.md 2019-10-11 17:33:21.000000000 +0200 @@ -1,3 +1,6 @@ +0.8.1.0 +* Add `retryingDynamic` and `recoveringDynamic`. [PR 65](https://github.com/Soostone/retry/pull/65) + 0.8.0.2 * Update docs for default retry policy. [PR 64](https://github.com/Soostone/retry/pull/64) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.8.0.2/retry.cabal new/retry-0.8.1.0/retry.cabal --- old/retry-0.8.0.2/retry.cabal 2019-09-27 22:23:31.000000000 +0200 +++ new/retry-0.8.1.0/retry.cabal 2019-10-11 17:32:32.000000000 +0200 @@ -14,7 +14,7 @@ case we should hang back for a bit and retry the query instead of simply raising an exception. -version: 0.8.0.2 +version: 0.8.1.0 synopsis: Retry combinators for monadic actions that may fail license: BSD3 license-file: LICENSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.8.0.2/src/Control/Retry.hs new/retry-0.8.1.0/src/Control/Retry.hs --- old/retry-0.8.0.2/src/Control/Retry.hs 2019-09-27 17:56:03.000000000 +0200 +++ new/retry-0.8.1.0/src/Control/Retry.hs 2019-10-11 17:31:38.000000000 +0200 @@ -37,6 +37,8 @@ , retryPolicy , retryPolicyDefault , natTransformRetryPolicy + , RetryAction (..) + , toRetryAction , RetryStatus (..) , defaultRetryStatus , applyPolicy @@ -50,7 +52,9 @@ -- * Applying Retry Policies , retrying + , retryingDynamic , recovering + , recoveringDynamic , stepping , recoverAll , skipAsyncExceptions @@ -192,6 +196,30 @@ natTransformRetryPolicy f (RetryPolicyM p) = RetryPolicyM $ \stat -> f (p stat) +-- | Modify the delay of a RetryPolicy. +-- Does not change whether or not a retry is performed. +modifyRetryPolicyDelay :: Functor m => (Int -> Int) -> RetryPolicyM m -> RetryPolicyM m +modifyRetryPolicyDelay f (RetryPolicyM p) = RetryPolicyM $ \stat -> fmap f <$> p stat + + +------------------------------------------------------------------------------- +-- | How to handle a failed action. +data RetryAction + = DontRetry + -- ^ Don't retry (regardless of what the 'RetryPolicy' says). + | ConsultPolicy + -- ^ Retry if the 'RetryPolicy' says so, with the delay specified by the policy. + | ConsultPolicyOverrideDelay Int + -- ^ Retry if the 'RetryPolicy' says so, but override the policy's delay (number of microseconds). + deriving (Read, Show, Eq, Generic) + + +-- | Convert a boolean answer to the question "Should we retry?" into +-- a 'RetryAction'. +toRetryAction :: Bool -> RetryAction +toRetryAction False = DontRetry +toRetryAction True = ConsultPolicy + ------------------------------------------------------------------------------- -- | Datatype with stats about retries made thus far. The constructor -- is deliberately not exported to make additional fields easier to @@ -422,18 +450,56 @@ -> (RetryStatus -> m b) -- ^ Action to run -> m b -retrying policy chk f = go defaultRetryStatus +retrying policy chk f = + retryingDynamic policy (\rs -> fmap toRetryAction . chk rs) f + + +------------------------------------------------------------------------------- +-- | 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 'RetryPolicy's 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. +retryingDynamic + :: MonadIO m + => RetryPolicyM m + -> (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'. + -> (RetryStatus -> m b) + -- ^ Action to run + -> m b +retryingDynamic policy chk f = go defaultRetryStatus where go s = do res <- f s + let consultPolicy policy' = do + rs <- applyAndDelay policy' s + case rs of + Nothing -> return res + Just rs' -> go $! rs' chk' <- chk s res - if chk' - then do - rs <- applyAndDelay policy s - case rs of - Nothing -> return res - Just rs' -> go $! rs' - else return res + case chk' of + DontRetry -> return res + ConsultPolicy -> consultPolicy policy + ConsultPolicyOverrideDelay delay -> + consultPolicy $ modifyRetryPolicyDelay (const delay) policy ------------------------------------------------------------------------------- @@ -518,7 +584,31 @@ -> (RetryStatus -> m a) -- ^ Action to perform -> m a -recovering policy hs f = mask $ \restore -> go restore defaultRetryStatus +recovering policy hs f = + recoveringDynamic policy hs' f + where + hs' = map (fmap toRetryAction .) hs + +-- | The difference between this and 'recovering' is the same as +-- the difference between 'retryingDynamic' and 'retrying'. +recoveringDynamic +#if MIN_VERSION_exceptions(0, 6, 0) + :: (MonadIO m, MonadMask m) +#else + :: (MonadIO m, MonadCatch m) +#endif + => 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 policy hs f = mask $ \restore -> go restore defaultRetryStatus where go restore = loop where @@ -531,14 +621,17 @@ recover e [] = throwM e recover e ((($ s) -> Handler h) : hs') | Just e' <- fromException e = do + let consultPolicy policy' = do + rs <- applyAndDelay policy' s + case rs of + Just rs' -> loop $! rs' + Nothing -> throwM e' chk <- h e' case chk of - True -> do - rs <- applyAndDelay policy s - case rs of - Just rs' -> loop $! rs' - Nothing -> throwM e' - False -> throwM e' + DontRetry -> throwM e' + ConsultPolicy -> consultPolicy policy + ConsultPolicyOverrideDelay delay -> + consultPolicy $ modifyRetryPolicyDelay (const delay) policy | otherwise = recover e hs' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.8.0.2/test/Tests/Control/Retry.hs new/retry-0.8.1.0/test/Tests/Control/Retry.hs --- old/retry-0.8.0.2/test/Tests/Control/Retry.hs 2019-09-27 21:47:34.000000000 +0200 +++ new/retry-0.8.1.0/test/Tests/Control/Retry.hs 2019-10-11 17:31:38.000000000 +0200 @@ -1,3 +1,4 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ScopedTypeVariables #-} module Tests.Control.Retry @@ -42,6 +43,7 @@ , maskingStateTests , capDelayTests , limitRetriesByCumulativeDelayTests + , overridingDelayTests ] @@ -311,6 +313,48 @@ HH.assert (diffUTCTime endTime startTime >= ms') ] + +------------------------------------------------------------------------------- +overridingDelayTests :: TestTree +overridingDelayTests = testGroup "overriding delay" + [ testGroup "actual delays don't exceed specified delays" + [ testProperty "retryingDynamic" $ + testOverride + retryingDynamic + (\delays rs _ -> return $ ConsultPolicyOverrideDelay (delays !! rsIterNumber rs)) + (\_ _ -> liftIO getCurrentTime >>= \time -> tell [time]) + , testProperty "recoveringDynamic" $ + testOverride + recoveringDynamic + (\delays -> [\rs -> Handler (\(_::SomeException) -> return $ ConsultPolicyOverrideDelay (delays !! rsIterNumber rs))]) + (\delays rs -> do + liftIO getCurrentTime >>= \time -> tell [time] + if rsIterNumber rs < length delays + then throwM (userError "booo") + else return () + ) + ] + ] + where + -- Transform a list of timestamps into a list of differences + -- between adjacent timestamps. + diffTimes = compareAdjacent (flip diffUTCTime) + microsToNominalDiffTime = toNominal . picosecondsToDiffTime . (* 1000000) . fromIntegral + toNominal :: DiffTime -> NominalDiffTime + toNominal = realToFrac + -- Generic test case used to test both "retryingDynamic" and "recoveringDynamic" + testOverride retryer handler action = property $ do + retryPolicy' <- forAll $ genPolicyNoLimit (Range.linear 1 1000000) + delays <- forAll $ Gen.list (Range.linear 1 10) (Gen.int (Range.linear 10 1000)) + (_, measuredTimestamps) <- liftIO $ runWriterT $ retryer + -- Stop retrying when we run out of delays + (retryPolicy' <> limitRetries (length delays)) + (handler delays) + (action delays) + let expectedDelays = map microsToNominalDiffTime delays + forM_ (zip (diffTimes measuredTimestamps) expectedDelays) $ + \(actual, expected) -> diff actual (>=) expected + ------------------------------------------------------------------------------- isLeftAnd :: (a -> Bool) -> Either a b -> Bool isLeftAnd f ei = case ei of @@ -320,6 +364,19 @@ testHandlers :: [a -> Handler IO Bool] testHandlers = [const $ Handler (\(_::SomeException) -> return shouldRetry)] +-- | Apply a function to adjacent list items. +-- +-- Ie.: +-- > compareAdjacent f [a0, a1, a2, a3, ..., a(n-2), a(n-1), an] = +-- > [f a0 a1, f a1 a2, f a2 a3, ..., f a(n-2) a(n-1), f a(n-1) an] +-- +-- Not defined for lists of length < 2. +compareAdjacent :: (a -> a -> b) -> [a] -> [b] +compareAdjacent f lst = + reverse . snd $ foldl + (\(a1, accum) a2 -> (a2, f a1 a2 : accum)) + (head lst, []) + (tail lst) data Custom1 = Custom1 deriving (Eq,Show,Read,Ord,Typeable) data Custom2 = Custom2 deriving (Eq,Show,Read,Ord,Typeable) @@ -341,6 +398,29 @@ ------------------------------------------------------------------------------- +-- | Generate an arbitrary 'RetryPolicy' without any limits applied. +genPolicyNoLimit + :: (MonadGen mg, MonadIO mr) + => Range Int + -> mg (RetryPolicyM mr) +genPolicyNoLimit durationRange = + Gen.choice + [ genConstantDelay + , genExponentialBackoff + , genFullJitterBackoff + , genFibonacciBackoff + ] + where + genDuration = Gen.int durationRange + -- Retry policies + genConstantDelay = fmap constantDelay genDuration + genExponentialBackoff = fmap exponentialBackoff genDuration + genFullJitterBackoff = fmap fullJitterBackoff genDuration + genFibonacciBackoff = fmap fibonacciBackoff genDuration + +-- Needed to generate a 'RetryPolicyM' using 'forAll' +instance Show (RetryPolicyM m) where + show = const "RetryPolicyM" -------------------------------------------------------------------------------