Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ghc-retry for openSUSE:Factory checked in at 2022-02-11 23:09:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-retry (Old) and /work/SRC/openSUSE:Factory/.ghc-retry.new.1956 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-retry" Fri Feb 11 23:09:33 2022 rev:4 rq:953521 version:0.9.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-retry/ghc-retry.changes 2021-08-25 20:58:50.097111823 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-retry.new.1956/ghc-retry.changes 2022-02-11 23:11:30.471310110 +0100 @@ -1,0 +2,12 @@ +Tue Jan 25 23:27:44 UTC 2022 - Peter Simons <psim...@suse.com> + +- Update retry to version 0.9.1.0. + 0.9.1.0 + * Add resumable retry/recover variants: + * `resumeRetrying` + * `resumeRetryingDynamic` + * `resumeRecovering` + * `resumeRecoveringDynamic` + * `resumeRecoverAll` + +------------------------------------------------------------------- Old: ---- retry-0.9.0.0.tar.gz New: ---- retry-0.9.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-retry.spec ++++++ --- /var/tmp/diff_new_pack.UsSwyg/_old 2022-02-11 23:11:30.903311361 +0100 +++ /var/tmp/diff_new_pack.UsSwyg/_new 2022-02-11 23:11:30.907311372 +0100 @@ -1,7 +1,7 @@ # # spec file for package ghc-retry # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %global pkg_name retry %bcond_with tests Name: ghc-%{pkg_name} -Version: 0.9.0.0 +Version: 0.9.1.0 Release: 0 Summary: Retry combinators for monadic actions that may fail License: BSD-3-Clause ++++++ retry-0.9.0.0.tar.gz -> retry-0.9.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.9.0.0/changelog.md new/retry-0.9.1.0/changelog.md --- old/retry-0.9.0.0/changelog.md 2021-07-06 17:35:52.000000000 +0200 +++ new/retry-0.9.1.0/changelog.md 2022-01-26 00:16:12.000000000 +0100 @@ -1,3 +1,11 @@ +0.9.1.0 +* Add resumable retry/recover variants: + * `resumeRetrying` + * `resumeRetryingDynamic` + * `resumeRecovering` + * `resumeRecoveringDynamic` + * `resumeRecoverAll` + 0.9.0.0 * Replace several uses of RetryPolicy type alias with RetryPolicyM m for better GHC 9 compat. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.9.0.0/retry.cabal new/retry-0.9.1.0/retry.cabal --- old/retry-0.9.0.0/retry.cabal 2021-07-06 17:35:52.000000000 +0200 +++ new/retry-0.9.1.0/retry.cabal 2022-01-26 00:21:01.000000000 +0100 @@ -14,7 +14,7 @@ case we should hang back for a bit and retry the query instead of simply raising an exception. -version: 0.9.0.0 +version: 0.9.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.9.0.0/src/Control/Retry.hs new/retry-0.9.1.0/src/Control/Retry.hs --- old/retry-0.9.0.0/src/Control/Retry.hs 2021-07-06 17:35:52.000000000 +0200 +++ new/retry-0.9.1.0/src/Control/Retry.hs 2022-01-26 00:16:12.000000000 +0100 @@ -3,7 +3,6 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE ViewPatterns #-} @@ -60,6 +59,12 @@ , skipAsyncExceptions , logRetries , defaultLogMsg + -- ** Resumable variants + , resumeRetrying + , resumeRetryingDynamic + , resumeRecovering + , resumeRecoveringDynamic + , resumeRecoverAll -- * Retry Policies , constantDelay @@ -221,15 +226,7 @@ toRetryAction True = ConsultPolicy ------------------------------------------------------------------------------- --- | 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 +-- | Datatype with stats about retries made thus far. data RetryStatus = RetryStatus { rsIterNumber :: !Int -- ^ Iteration number, where 0 is the first try , rsCumulativeDelay :: !Int -- ^ Delay incurred so far from retries in microseconds @@ -238,8 +235,7 @@ ------------------------------------------------------------------------------- --- | Initial, default retry status. Exported mostly to allow user code --- to test their handlers and retry policies. Use fields or lenses to update. +-- | Initial, default retry status. Use fields or lenses to update. defaultRetryStatus :: RetryStatus defaultRetryStatus = RetryStatus 0 0 Nothing @@ -292,7 +288,7 @@ chk <- applyPolicy policy s case chk of Just rs -> do - case (rsPreviousDelay rs) of + case rsPreviousDelay rs of Nothing -> return () Just delay -> liftIO $ threadDelay delay return (Just rs) @@ -313,7 +309,7 @@ :: Int -- ^ Maximum number of retries. -> RetryPolicy -limitRetries i = retryPolicy $ \ RetryStatus { rsIterNumber = n} -> if n >= i then Nothing else (Just 0) +limitRetries i = retryPolicy $ \ RetryStatus { rsIterNumber = n} -> if n >= i then Nothing else Just 0 ------------------------------------------------------------------------------- @@ -329,7 +325,7 @@ -> RetryPolicyM m -> RetryPolicyM m limitRetriesByDelay i p = RetryPolicyM $ \ n -> - (>>= limit) `liftM` getRetryPolicyM p n + (>>= limit) `fmap` getRetryPolicyM p n where limit delay = if delay >= i then Nothing else Just delay @@ -345,7 +341,7 @@ -> RetryPolicyM m -> RetryPolicyM m limitRetriesByCumulativeDelay cumulativeLimit p = RetryPolicyM $ \ stat -> - (>>= limit stat) `liftM` getRetryPolicyM p stat + (>>= limit stat) `fmap` getRetryPolicyM p stat where limit status curDelay | rsCumulativeDelay status `boundedPlus` curDelay > cumulativeLimit = Nothing @@ -421,7 +417,7 @@ -> RetryPolicyM m -> RetryPolicyM m capDelay limit p = RetryPolicyM $ \ n -> - (fmap (min limit)) `liftM` (getRetryPolicyM p) n + fmap (min limit) `fmap` getRetryPolicyM p n ------------------------------------------------------------------------------- @@ -453,8 +449,29 @@ -> (RetryStatus -> m b) -- ^ Action to run -> m b -retrying policy chk f = - retryingDynamic policy (\rs -> fmap toRetryAction . chk rs) f +retrying = resumeRetrying defaultRetryStatus + + +------------------------------------------------------------------------------- +-- | A variant of 'retrying' that allows specifying the initial +-- 'RetryStatus' so that the retrying operation may pick up where it left +-- off in regards to its retry policy. +resumeRetrying + :: MonadIO m + => RetryStatus + -> 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 +resumeRetrying retryStatus policy chk f = + resumeRetryingDynamic + retryStatus + policy + (\rs -> fmap toRetryAction . chk rs) + f ------------------------------------------------------------------------------- @@ -488,7 +505,25 @@ -> (RetryStatus -> m b) -- ^ Action to run -> m b -retryingDynamic policy chk f = go defaultRetryStatus +retryingDynamic = resumeRetryingDynamic defaultRetryStatus + + +------------------------------------------------------------------------------- +-- | A variant of 'retryingDynamic' that allows specifying the initial +-- 'RetryStatus' so that a retrying operation may pick up where it left off +-- in regards to its retry policy. +resumeRetryingDynamic + :: MonadIO m + => RetryStatus + -> 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 +resumeRetryingDynamic retryStatus policy chk f = go retryStatus where go s = do res <- f s @@ -536,7 +571,24 @@ => RetryPolicyM m -> (RetryStatus -> m a) -> m a -recoverAll set f = recovering set handlers f +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 +#if MIN_VERSION_exceptions(0, 6, 0) + :: (MonadIO m, MonadMask m) +#else + :: (MonadIO m, MonadCatch m) +#endif + => RetryStatus + -> RetryPolicyM m + -> (RetryStatus -> m a) + -> m a +resumeRecoverAll retryStatus set f = resumeRecovering retryStatus set handlers f where handlers = skipAsyncExceptions ++ [h] h _ = Handler $ \ (_ :: SomeException) -> return True @@ -569,7 +621,7 @@ -- *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 +-- just plan on catching 'SomeException', you may as well use -- 'recoverAll' recovering #if MIN_VERSION_exceptions(0, 6, 0) @@ -579,6 +631,30 @@ #endif => 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 +#if MIN_VERSION_exceptions(0, 6, 0) + :: (MonadIO m, MonadMask m) +#else + :: (MonadIO m, MonadCatch m) +#endif + => 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. @@ -587,11 +663,13 @@ -> (RetryStatus -> m a) -- ^ Action to perform -> m a -recovering policy hs f = - recoveringDynamic policy hs' f +resumeRecovering retryStatus policy hs f = + resumeRecoveringDynamic retryStatus 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 @@ -602,6 +680,31 @@ #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 = 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 +#if MIN_VERSION_exceptions(0, 6, 0) + :: (MonadIO m, MonadMask m) +#else + :: (MonadIO m, MonadCatch m) +#endif + => 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 @@ -611,7 +714,7 @@ -> (RetryStatus -> m a) -- ^ Action to perform -> m a -recoveringDynamic policy hs f = mask $ \restore -> go restore defaultRetryStatus +resumeRecoveringDynamic retryStatus policy hs f = mask $ \restore -> go restore retryStatus where go restore = loop where @@ -638,7 +741,6 @@ | otherwise = recover e hs' - ------------------------------------------------------------------------------- -- | A version of 'recovering' that tries to run the action only a -- single time. The control will return immediately upon both success @@ -652,7 +754,7 @@ #endif => RetryPolicyM m -- ^ Just use 'retryPolicyDefault' for default settings - -> [(RetryStatus -> Handler m Bool)] + -> [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 @@ -739,7 +841,7 @@ forM_ ps $ \ (iterNo, res) -> putStrLn $ show iterNo <> ": " <> maybe "Inhibit" ppTime res putStrLn $ "Total cumulative delay would be: " <> - (ppTime $ boundedSum $ (mapMaybe snd) ps) + ppTime (boundedSum $ mapMaybe snd ps) ------------------------------------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/retry-0.9.0.0/test/Tests/Control/Retry.hs new/retry-0.9.1.0/test/Tests/Control/Retry.hs --- old/retry-0.9.0.0/test/Tests/Control/Retry.hs 2021-07-06 17:35:53.000000000 +0200 +++ new/retry-0.9.1.0/test/Tests/Control/Retry.hs 2022-01-26 00:16:12.000000000 +0100 @@ -1,5 +1,6 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} module Tests.Control.Retry ( tests @@ -27,7 +28,9 @@ import System.IO.Error import Test.Tasty import Test.Tasty.Hedgehog -import Test.Tasty.HUnit (assertBool, testCase, (@?=)) +import Test.Tasty.HUnit ( assertBool, assertFailure + , testCase, (@=?), (@?=) + ) ------------------------------------------------------------------------------- import Control.Retry ------------------------------------------------------------------------------- @@ -44,6 +47,7 @@ , capDelayTests , limitRetriesByCumulativeDelayTests , overridingDelayTests + , resumableTests ] @@ -355,15 +359,124 @@ forM_ (zip (diffTimes measuredTimestamps) expectedDelays) $ \(actual, expected) -> diff actual (>=) expected + +------------------------------------------------------------------------------- +resumableTests :: TestTree +resumableTests = testGroup "resumable" + [ testGroup "resumeRetrying" + [ testCase "can resume" $ do + retryingTest resumeRetrying (\_ _ -> pure shouldRetry) + ] + , testGroup "resumeRetryingDynamic" + [ testCase "can resume" $ do + retryingTest resumeRetryingDynamic (\_ _ -> pure $ ConsultPolicy) + ] + , testGroup "resumeRecovering" + [ testCase "can resume" $ do + recoveringTest resumeRecovering testHandlers + ] + , testGroup "resumeRecoveringDynamic" + [ testCase "can resume" $ do + recoveringTest resumeRecoveringDynamic testHandlersDynamic + ] + , testGroup "resumeRecoverAll" + [ testCase "can resume" $ do + recoveringTest + (\status policy () action -> resumeRecoverAll status policy action) + () + ] + ] + where + retryingTest + :: (RetryStatus -> RetryPolicyM IO -> p -> (RetryStatus -> IO ()) -> IO ()) + -> p + -> IO () + retryingTest resumableOp isRetryNeeded = do + counterRef <- newIORef (0 :: Int) + + let go policy status = do + atomicWriteIORef counterRef 0 + resumableOp + status + policy + isRetryNeeded + (const $ atomicModifyIORef' counterRef $ \n -> (1 + n, ())) + + let policy = limitRetries 2 + let nextStatus = nextStatusUsingPolicy policy + + go policy defaultRetryStatus + (3 @=?) =<< readIORef counterRef + + go policy =<< nextStatus defaultRetryStatus + (2 @=?) =<< readIORef counterRef + + go policy =<< nextStatus =<< nextStatus defaultRetryStatus + (1 @=?) =<< readIORef counterRef + + recoveringTest + :: (RetryStatus -> RetryPolicyM IO -> handlers -> (RetryStatus -> IO ()) -> IO ()) + -> handlers + -> IO () + recoveringTest resumableOp handlers = do + counterRef <- newIORef (0 :: Int) + + let go policy status = do + action <- do + mkFailUntilIO + (\_ -> atomicModifyIORef' counterRef $ \n -> (1 + n, False)) + Custom1 + try $ resumableOp status policy handlers action + + let policy = limitRetries 2 + let nextStatus = nextStatusUsingPolicy policy + + do + atomicWriteIORef counterRef 0 + res <- go policy defaultRetryStatus + res @?= Left Custom1 + (3 @=?) =<< readIORef counterRef + + do + atomicWriteIORef counterRef 0 + res <- go policy =<< nextStatus defaultRetryStatus + res @?= Left Custom1 + (2 @=?) =<< readIORef counterRef + + do + atomicWriteIORef counterRef 0 + res <- go policy =<< nextStatus =<< nextStatus defaultRetryStatus + res @?= Left Custom1 + (1 @=?) =<< readIORef counterRef + + +------------------------------------------------------------------------------- +nextStatusUsingPolicy :: RetryPolicyM IO -> RetryStatus -> IO RetryStatus +nextStatusUsingPolicy policy status = do + applyPolicy policy status >>= \case + Nothing -> do + assertFailure "applying policy produced no new status" + Just status' -> do + pure status' + + ------------------------------------------------------------------------------- isLeftAnd :: (a -> Bool) -> Either a b -> Bool isLeftAnd f ei = case ei of Left v -> f v _ -> False + +------------------------------------------------------------------------------- testHandlers :: [a -> Handler IO Bool] testHandlers = [const $ Handler (\(_::SomeException) -> return shouldRetry)] + +------------------------------------------------------------------------------- +testHandlersDynamic :: [a -> Handler IO RetryAction] +testHandlersDynamic = + [const $ Handler (\(_::SomeException) -> return ConsultPolicy)] + -- | Apply a function to adjacent list items. -- -- Ie.: @@ -427,11 +540,33 @@ -- | Create an action that will fail exactly N times with the given -- exception and will then return () in any subsequent calls. mkFailN :: (Exception e) => e -> Int -> IO (s -> IO ()) -mkFailN e n = do +mkFailN e n = mkFailUntil (\iter -> iter >= n) e + + +------------------------------------------------------------------------------- +-- | Create an action that will fail with the given exception until the given +-- iteration predicate returns 'True', at which point the action will return +-- '()' in any subsequent calls. +mkFailUntil + :: (Exception e) + => (Int -> Bool) + -> e + -> IO (s -> IO ()) +mkFailUntil p = mkFailUntilIO (pure . p) + + +------------------------------------------------------------------------------- +-- | The same as 'mkFailUntil' but allows doing IO in the predicate. +mkFailUntilIO + :: (Exception e) + => (Int -> IO Bool) + -> e + -> IO (s -> IO ()) +mkFailUntilIO p e = do r <- newIORef 0 return $ const $ do old <- atomicModifyIORef' r $ \ old -> (old+1, old) - case old >= n of + p old >>= \case True -> return () False -> throwM e