diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -11,7 +11,6 @@
 with IO and similar actions that often fail. Common examples are
 database queries and large file uploads.
 
-
 ## Documentation
 
 Please see haddocks for documentation.
@@ -24,7 +23,6 @@
 
 Ozgun Ataman, Soostone Inc
 
-
 ## Contributors
 
 Contributors, please list yourself here.
@@ -33,5 +31,5 @@
 - John Wiegley
 - Michael Snoyman
 - Michael Xavier
+- Toralf Wittner
 - Marco Zocca (@ocramz)
-
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,27 @@
+0.9.3.1
+* Resolve warnings in test suite [PR 83](https://github.com/Soostone/retry/pull/83)
+
+0.9.3.0
+* Add `UnliftIO.Retry` [PR 81](https://github.com/Soostone/retry/pull/81)
+
+0.9.2.1
+* Use explicit import for `lift` which allows for mtl-2.3 compatibility [PR 80](https://github.com/Soostone/retry/pull/80)
+
+0.9.2.0
+* Add `retryOnError` [PR 44](https://github.com/Soostone/retry/pull/44)
+
+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.
+
 0.8.1.2
 * Set lower bound on base to >= 4.8
 
diff --git a/retry.cabal b/retry.cabal
--- a/retry.cabal
+++ b/retry.cabal
@@ -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.1.2
+version:             0.9.3.1
 synopsis:            Retry combinators for monadic actions that may fail
 license:             BSD3
 license-file:        LICENSE
@@ -35,12 +35,16 @@
 
 library
   exposed-modules:     Control.Retry
+                       UnliftIO.Retry
   build-depends:
       base                 >= 4.8 && < 5
     , exceptions           >= 0.5
     , ghc-prim
     , random               >= 1
     , transformers
+    , mtl
+    , mtl-compat
+    , unliftio-core        >= 0.1.0.0
   hs-source-dirs:      src
   default-language:    Haskell2010
 
@@ -56,7 +60,9 @@
     hs-source-dirs: test,src
     ghc-options:    -threaded
     other-modules:  Control.Retry
+                    UnliftIO.Retry
                     Tests.Control.Retry
+                    Tests.UnliftIO.Retry
     build-depends:
         base              ==4.*
       , exceptions
@@ -67,10 +73,12 @@
       , tasty
       , tasty-hunit
       , tasty-hedgehog
-      , hedgehog
+      , hedgehog           >= 1.0
       , stm
       , ghc-prim
       , mtl
+      , mtl-compat
+      , unliftio-core
     default-language: Haskell2010
 
     if flag(lib-Werror)
diff --git a/src/Control/Retry.hs b/src/Control/Retry.hs
--- a/src/Control/Retry.hs
+++ b/src/Control/Retry.hs
@@ -3,8 +3,8 @@
 {-# LANGUAGE DeriveGeneric         #-}
 {-# LANGUAGE MagicHash             #-}
 {-# LANGUAGE RankNTypes            #-}
-{-# LANGUAGE RecordWildCards       #-}
 {-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TupleSections         #-}
 {-# LANGUAGE UnboxedTuples         #-}
 {-# LANGUAGE ViewPatterns          #-}
 
@@ -60,6 +60,13 @@
     , skipAsyncExceptions
     , logRetries
     , defaultLogMsg
+    , retryOnError
+    -- ** Resumable variants
+    , resumeRetrying
+    , resumeRetryingDynamic
+    , resumeRecovering
+    , resumeRecoveringDynamic
+    , resumeRecoverAll
 
     -- * Retry Policies
     , constantDelay
@@ -88,8 +95,9 @@
 #endif
 import           Control.Monad
 import           Control.Monad.Catch
-import           Control.Monad.IO.Class
-import           Control.Monad.Trans.Class
+import           Control.Monad.Except
+import           Control.Monad.IO.Class as MIO
+import           Control.Monad.Trans.Class as TC
 import           Control.Monad.Trans.Maybe
 import           Control.Monad.Trans.State
 import           Data.List (foldl')
@@ -130,7 +138,7 @@
 -- One can easily define an exponential backoff policy with a limited
 -- number of retries:
 --
--- >> limitedBackoff = exponentialBackoff 50 <> limitRetries 5
+-- >> limitedBackoff = exponentialBackoff 50000 <> limitRetries 5
 --
 -- Naturally, 'mempty' will retry immediately (delay 0) for an
 -- unlimited number of retries, forming the identity for the 'Monoid'.
@@ -141,7 +149,7 @@
 --
 -- For anything more complex, just define your own 'RetryPolicyM':
 --
--- >> myPolicy = retryPolicy $ \ rs -> if rsIterNumber n > 10 then Just 1000 else Just 10000
+-- >> myPolicy = retryPolicy $ \ rs -> if rsIterNumber rs > 10 then Just 1000 else Just 10000
 --
 -- Since 0.7.
 newtype RetryPolicyM m = RetryPolicyM { getRetryPolicyM :: RetryStatus -> m (Maybe Int) }
@@ -153,7 +161,7 @@
 type RetryPolicy = forall m . Monad m => RetryPolicyM m
 
 -- | Default retry policy
-retryPolicyDefault :: RetryPolicy
+retryPolicyDefault :: (Monad m) => RetryPolicyM m
 retryPolicyDefault = constantDelay 50000 <> limitRetries 5
 
 
@@ -221,15 +229,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 +238,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
 
@@ -284,7 +283,7 @@
 -- | Apply policy and delay by its amount if it results in a retry.
 -- Return updated status.
 applyAndDelay
-    :: MonadIO m
+    :: MIO.MonadIO m
     => RetryPolicyM m
     -> RetryStatus
     -> m (Maybe RetryStatus)
@@ -292,7 +291,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)
@@ -303,7 +302,7 @@
 -------------------------------------------------------------------------------
 -- | Helper for making simplified policies that don't use the monadic
 -- context.
-retryPolicy :: (RetryStatus -> Maybe Int) -> RetryPolicy
+retryPolicy :: (Monad m) => (RetryStatus -> Maybe Int) -> RetryPolicyM m
 retryPolicy f = RetryPolicyM $ \ s -> return (f s)
 
 
@@ -313,7 +312,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 +328,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 +344,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
@@ -355,9 +354,10 @@
 -------------------------------------------------------------------------------
 -- | Implement a constant delay with unlimited retries.
 constantDelay
-    :: Int
+    :: (Monad m)
+    => Int
     -- ^ Base delay in microseconds
-    -> RetryPolicy
+    -> RetryPolicyM m
 constantDelay delay = retryPolicy (const (Just delay))
 
 
@@ -365,9 +365,10 @@
 -- | Grow delay exponentially each iteration.  Each delay will
 -- increase by a factor of two.
 exponentialBackoff
-    :: Int
+    :: (Monad m)
+    => Int
     -- ^ Base delay in microseconds
-    -> RetryPolicy
+    -> RetryPolicyM m
 exponentialBackoff base = retryPolicy $ \ RetryStatus { rsIterNumber = n } ->
   Just $! base `boundedMult` boundedPow 2 n
 
@@ -381,7 +382,7 @@
 --
 -- sleep = temp \/ 2 + random_between(0, temp \/ 2)
 fullJitterBackoff
-    :: MonadIO m
+    :: (MonadIO m)
     => Int
     -- ^ Base delay in microseconds
     -> RetryPolicyM m
@@ -394,9 +395,10 @@
 -------------------------------------------------------------------------------
 -- | Implement Fibonacci backoff.
 fibonacciBackoff
-    :: Int
+    :: (Monad m)
+    => Int
     -- ^ Base delay in microseconds
-    -> RetryPolicy
+    -> RetryPolicyM m
 fibonacciBackoff base = retryPolicy $ \RetryStatus { rsIterNumber = n } ->
   Just $ fib (n + 1) (0, base)
     where
@@ -418,7 +420,7 @@
     -> RetryPolicyM m
     -> RetryPolicyM m
 capDelay limit p = RetryPolicyM $ \ n ->
-  (fmap (min limit)) `liftM` (getRetryPolicyM p) n
+  fmap (min limit) `fmap` getRetryPolicyM p n
 
 
 -------------------------------------------------------------------------------
@@ -450,11 +452,32 @@
           -> (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
+
+
+-------------------------------------------------------------------------------
 -- | Same as 'retrying', but with the ability to override
 -- the delay of the retry policy based on information
 -- obtained after initiation.
@@ -485,7 +508,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
@@ -533,7 +574,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
@@ -566,7 +624,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)
@@ -576,6 +634,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.
@@ -584,11 +666,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
@@ -599,6 +683,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
@@ -608,7 +717,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
@@ -635,7 +744,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
@@ -649,7 +757,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
@@ -712,13 +820,38 @@
 
 
 -------------------------------------------------------------------------------
+retryOnError
+    :: (Functor m, MonadIO m, MonadError e m)
+    => RetryPolicyM m
+    -- ^ Policy
+    -> (RetryStatus -> e -> m Bool)
+    -- ^ Should an error be retried?
+    -> (RetryStatus -> m a)
+    -- ^ Action to perform
+    -> m a
+retryOnError policy chk f = go defaultRetryStatus
+  where
+    go stat = do
+      res <- (Right <$> f stat) `catchError` (\e -> Left . (e, ) <$> chk stat e)
+      case res of
+        Right x -> return x
+        Left (e, True) -> do
+          mstat' <- applyAndDelay policy stat
+          case mstat' of
+            Just stat' -> do
+              go $! stat'
+            Nothing -> throwError e
+        Left (e, False) -> throwError e
+
+
+-------------------------------------------------------------------------------
 -- | 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)
+  delay <- TC.lift (f stat)
   put $! stat
     { rsIterNumber = i + 1
     , rsCumulativeDelay = rsCumulativeDelay stat `boundedPlus` fromMaybe 0 delay
@@ -736,7 +869,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 --git a/src/UnliftIO/Retry.hs b/src/UnliftIO/Retry.hs
new file mode 100644
--- /dev/null
+++ b/src/UnliftIO/Retry.hs
@@ -0,0 +1,268 @@
+{-# LANGUAGE RankNTypes            #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  UnliftIO.Retry
+-- Copyright   :  Ozgun Ataman <ozgun.ataman@soostone.com>
+-- License     :  BSD3
+--
+-- Maintainer  :  Patrick Brisbin <pbrisbin@gmail.com>
+-- Stability   :  provisional
+--
+-- Unlifted "Control.Retry".
+--
+-- @since 0.9.3.0
+----------------------------------------------------------------------------
+
+
+module UnliftIO.Retry
+    (
+      -- * Types and Operations
+      RetryPolicyM (..)
+    , RetryPolicy
+    , retryPolicy
+    , retryPolicyDefault
+    , natTransformRetryPolicy
+    , RetryAction (..)
+    , toRetryAction
+    , RetryStatus (..)
+    , defaultRetryStatus
+    , applyPolicy
+    , applyAndDelay
+
+
+    -- ** Lenses for 'RetryStatus'
+    , rsIterNumberL
+    , rsCumulativeDelayL
+    , rsPreviousDelayL
+
+    -- * Applying Retry Policies
+    , retrying
+    , retryingDynamic
+    , recovering
+    , recoveringDynamic
+    , stepping
+    , recoverAll
+    , skipAsyncExceptions
+    , logRetries
+    , defaultLogMsg
+    , retryOnError
+    -- ** Resumable variants
+    , resumeRetrying
+    , resumeRetryingDynamic
+    , resumeRecovering
+    , resumeRecoveringDynamic
+    , resumeRecoverAll
+
+    -- * Retry Policies
+    , constantDelay
+    , exponentialBackoff
+    , fullJitterBackoff
+    , fibonacciBackoff
+    , limitRetries
+
+    -- * Policy Transformers
+    , limitRetriesByDelay
+    , limitRetriesByCumulativeDelay
+    , capDelay
+
+    -- * Development Helpers
+    , simulatePolicy
+    , simulatePolicyPP
+    ) where
+
+-------------------------------------------------------------------------------
+import           Control.Retry hiding
+    ( recoverAll
+    , recovering
+    , recoveringDynamic
+    , resumeRecovering
+    , resumeRecoveringDynamic
+    , resumeRecoverAll
+    , stepping
+    )
+import qualified Control.Retry as Retry
+import           Control.Monad.Catch (Handler(..))
+import           Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
+import           Prelude
+-------------------------------------------------------------------------------
+
+
+-------------------------------------------------------------------------------
+-- | Run an action and recover from a raised exception by potentially
+-- 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 use
+-- 'recoverAll'
+recovering
+    :: MonadUnliftIO m
+    => 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
+    :: MonadUnliftIO m
+    => 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.
+    -- This action will be consulted first even if the policy
+    -- later blocks it.
+    -> (RetryStatus -> m a)
+    -- ^ Action to perform
+    -> m a
+resumeRecovering retryStatus policy hs f = withRunInIO $ \runInIO ->
+    Retry.resumeRecovering
+        retryStatus
+        (transRetryPolicy runInIO policy)
+        (map ((.) $ transHandler runInIO) hs)
+        (runInIO . f)
+
+
+-------------------------------------------------------------------------------
+-- | The difference between this and 'recovering' is the same as
+--  the difference between 'retryingDynamic' and 'retrying'.
+recoveringDynamic
+    :: MonadUnliftIO m
+    => 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
+    :: MonadUnliftIO m
+    => 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
+    -- '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
+resumeRecoveringDynamic retryStatus policy hs f = withRunInIO $ \runInIO ->
+    Retry.resumeRecoveringDynamic
+        retryStatus
+        (transRetryPolicy runInIO policy)
+        (map ((.) $ transHandler runInIO) hs)
+        (runInIO . f)
+
+
+-------------------------------------------------------------------------------
+-- | Retry ALL exceptions that may be raised. To be used with caution;
+-- 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 not 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"
+-- >>> recoverAll retryPolicyDefault f
+-- Running action
+-- Running action
+-- Running action
+-- Running action
+-- Running action
+-- Running action
+-- *** Exception: this is an error
+recoverAll
+     :: MonadUnliftIO m
+     => RetryPolicyM m
+     -> (RetryStatus -> m a)
+     -> m a
+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
+     :: MonadUnliftIO m
+     => RetryStatus
+     -> RetryPolicyM m
+     -> (RetryStatus -> m a)
+     -> m a
+resumeRecoverAll retryStatus policy f = withRunInIO $ \runInIO ->
+    Retry.resumeRecoverAll
+        retryStatus
+        (transRetryPolicy runInIO policy)
+        (runInIO . f)
+
+-------------------------------------------------------------------------------
+-- | A version of 'recovering' that tries to run the action only a
+-- single time. The control will return immediately upon both success
+-- and failure. Useful for implementing retry logic in distributed
+-- queues and similar external-interfacing systems.
+stepping
+    :: MonadUnliftIO m
+    => 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 ())
+    -- ^ Action to run with updated status upon failure.
+    -> (RetryStatus -> m a)
+    -- ^ Main action to perform with current status.
+    -> RetryStatus
+    -- ^ Current status of this step
+    -> m (Maybe a)
+stepping policy hs schedule f s = withRunInIO $ \runInIO ->
+    Retry.stepping
+        (transRetryPolicy runInIO policy)
+        (map ((.) $ transHandler runInIO) hs)
+        (runInIO . schedule)
+        (runInIO . f)
+        s
+
+
+-------------------------------------------------------------------------------
+transRetryPolicy :: (forall a. m a -> n a) -> RetryPolicyM m -> RetryPolicyM n
+transRetryPolicy f (RetryPolicyM p) = RetryPolicyM $ f . p
+
+
+-------------------------------------------------------------------------------
+transHandler :: (forall b. m b -> n b) -> Handler m a -> Handler n a
+transHandler f (Handler h) = Handler $ f . h
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -7,6 +7,7 @@
 import           Test.Tasty
 -------------------------------------------------------------------------------
 import qualified Tests.Control.Retry
+import qualified Tests.UnliftIO.Retry
 -------------------------------------------------------------------------------
 
 
@@ -19,4 +20,5 @@
 tests :: TestTree
 tests = testGroup "retry"
   [ Tests.Control.Retry.tests
+  , Tests.UnliftIO.Retry.tests
   ]
diff --git a/test/Tests/Control/Retry.hs b/test/Tests/Control/Retry.hs
--- a/test/Tests/Control/Retry.hs
+++ b/test/Tests/Control/Retry.hs
@@ -1,8 +1,18 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {-# LANGUAGE DeriveDataTypeable  #-}
+{-# LANGUAGE LambdaCase  #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE FlexibleContexts #-}
 module Tests.Control.Retry
     ( tests
+
+    -- * Used to test UnliftIO versions of the same functions
+    , recoveringTestsWith
+    , maskingStateTestsWith
+    , quadraticDelayTestsWith
+    , recoveringTest
+    , testHandlers
+    , testHandlersDynamic
     ) where
 
 -------------------------------------------------------------------------------
@@ -10,9 +20,11 @@
 import           Control.Concurrent
 import           Control.Concurrent.STM      as STM
 import qualified Control.Exception           as EX
+import           Control.Monad               as M ( forM_ )
 import           Control.Monad.Catch
+import           Control.Monad.Except
 import           Control.Monad.Identity
-import           Control.Monad.IO.Class
+import           Control.Monad.IO.Class      as MIO
 import           Control.Monad.Writer.Strict
 import           Data.Either
 import           Data.IORef
@@ -27,7 +39,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,17 +58,26 @@
   , capDelayTests
   , limitRetriesByCumulativeDelayTests
   , overridingDelayTests
+  , resumableTests
+  , retryOnErrorTests
   ]
 
 
 -------------------------------------------------------------------------------
 recoveringTests :: TestTree
-recoveringTests = testGroup "recovering"
+recoveringTests = recoveringTestsWith recovering
+
+
+recoveringTestsWith
+    :: Monad m
+    => (RetryPolicyM m -> [RetryStatus -> Handler IO Bool] -> (a -> IO ()) -> IO ())
+    -> TestTree
+recoveringTestsWith recovering' = testGroup "recovering"
   [ testProperty "recovering test without quadratic retry delay" $ property $ do
       startTime <- liftIO getCurrentTime
       timeout <- forAll (Gen.int (Range.linear 0 15))
       retries <- forAll (Gen.int (Range.linear 0 50))
-      res <- liftIO $ try $ recovering
+      res <- liftIO $ try $ recovering'
         (constantDelay timeout <> limitRetries retries)
         testHandlers
         (const $ throwM (userError "booo"))
@@ -81,7 +104,7 @@
 
       , testCase "recovers from custom exceptions" $ do
           f <- mkFailN Custom1 2
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 3)
             [const $ Handler $ \ Custom1 -> return shouldRetry]
             f
@@ -89,7 +112,7 @@
 
       , testCase "fails beyond policy using custom exceptions" $ do
           f <- mkFailN Custom1 3
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 2)
             [const $ Handler $ \ Custom1 -> return shouldRetry]
             f
@@ -104,7 +127,7 @@
 
       , testCase "does not recover from unhandled exceptions" $ do
           f <- mkFailN Custom2 2
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 5)
             [const $ Handler $ \ Custom1 -> return shouldRetry]
             f
@@ -113,7 +136,7 @@
 
       , testCase "recovers in presence of multiple handlers" $ do
           f <- mkFailN Custom2 2
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 5)
             [ const $ Handler $ \ Custom1 -> return shouldRetry
             , const $ Handler $ \ Custom2 -> return shouldRetry ]
@@ -123,7 +146,7 @@
 
       , testCase "general exceptions catch specific ones" $ do
           f <- mkFailN Custom2 2
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 5)
             [ const $ Handler $ \ (_::SomeException) -> return shouldRetry ]
             f
@@ -132,7 +155,7 @@
 
       , testCase "(redundant) even general catchers don't go beyond policy" $ do
           f <- mkFailN Custom2 3
-          res <- try $ recovering
+          res <- try $ recovering'
             (constantDelay 5000 <> limitRetries 2)
             [ const $ Handler $ \ (_::SomeException) -> return shouldRetry ]
             f
@@ -142,7 +165,7 @@
       , testCase "rethrows in presence of failed exception casts" $ do
           f <- mkFailN Custom2 3
           final <- try $ do
-            res <- try $ recovering
+            res <- try $ recovering'
               (constantDelay 5000 <> limitRetries 2)
               [ const $ Handler $ \ (_::SomeException) -> return shouldRetry ]
               f
@@ -223,10 +246,17 @@
 
 -------------------------------------------------------------------------------
 maskingStateTests :: TestTree
-maskingStateTests = testGroup "masking state"
+maskingStateTests = maskingStateTestsWith recovering
+
+
+maskingStateTestsWith
+    :: Monad m
+    => (RetryPolicyM m -> [RetryStatus -> Handler IO Bool] -> (a -> IO b) -> IO ())
+    -> TestTree
+maskingStateTestsWith recovering' = testGroup "masking state"
   [ testCase "shouldn't change masking state in a recovered action" $ do
       maskingState <- EX.getMaskingState
-      final <- try $ recovering retryPolicyDefault testHandlers $ const $ do
+      final <- try $ recovering' retryPolicyDefault testHandlers $ const $ do
         maskingState' <- EX.getMaskingState
         maskingState' @?= maskingState
         fail "Retrying..."
@@ -241,7 +271,7 @@
                 maskingState @?= EX.MaskedInterruptible
                 return shouldRetry
             ]
-      final <- try $ recovering retryPolicyDefault checkMaskingStateHandlers $ const $ fail "Retrying..."
+      final <- try $ recovering' retryPolicyDefault checkMaskingStateHandlers $ const $ fail "Retrying..."
       assertBool
         ("Expected EX.IOException but didn't get one")
         (isLeft (final :: Either EX.IOException ()))
@@ -256,8 +286,8 @@
       cap <- forAll (Gen.int (Range.linear 1 maxBound))
       let policy = capDelay cap (limitRetries retries)
       let delays = runIdentity (simulatePolicy (retries + 1) policy)
-      let Just lastDelay = lookup (retries - 1) delays
-      let Just gaveUp = lookup retries delays
+      let lastDelay = fromMaybe (error "impossible: empty delays") (lookup (retries - 1) delays)
+      let gaveUp = fromMaybe (error "impossible: empty delays") (lookup retries delays)
       let noDelay = 0
       lastDelay === Just noDelay
       gaveUp === Nothing
@@ -297,12 +327,19 @@
 
 -------------------------------------------------------------------------------
 quadraticDelayTests :: TestTree
-quadraticDelayTests = testGroup "quadratic delay"
+quadraticDelayTests = quadraticDelayTestsWith recovering
+
+
+quadraticDelayTestsWith
+    :: Monad m
+    => (RetryPolicyM m -> [RetryStatus -> Handler IO Bool] -> (a -> IO b) -> IO ())
+    -> TestTree
+quadraticDelayTestsWith recovering' = testGroup "quadratic delay"
   [ testProperty "recovering test with quadratic retry delay" $ property $ do
       startTime <- liftIO getCurrentTime
       timeout <- forAll (Gen.int (Range.linear 0 15))
       retries <- forAll (Gen.int (Range.linear 0 8))
-      res <- liftIO $ try $ recovering
+      res <- liftIO $ try $ recovering'
         (exponentialBackoff timeout <> limitRetries retries)
         [const $ Handler (\(_::SomeException) -> return True)]
         (const $ throwM (userError "booo"))
@@ -352,18 +389,143 @@
         (handler delays)
         (action delays)
       let expectedDelays = map microsToNominalDiffTime delays
-      forM_ (zip (diffTimes measuredTimestamps) expectedDelays) $
+      M.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)
+            ()
+      ]
+  ]
+
+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
+
+
+-------------------------------------------------------------------------------
+retryOnErrorTests :: TestTree
+retryOnErrorTests = testGroup "retryOnError"
+  [ testCase "passes in the error type" $ do
+      errCalls <- newTVarIO []
+      let policy = limitRetries 2
+      let shouldWeRetry _retryStat e = do
+            liftIO (atomically (modifyTVar' errCalls (++ [e])))
+            return True
+      let action rs = (throwError ("boom" ++ show (rsIterNumber rs)))
+      res <- runExceptT (retryOnError policy shouldWeRetry action)
+      res @?= (Left "boom2" :: Either String ())
+      calls <- atomically (readTVar errCalls)
+      calls @?= ["boom0", "boom1", "boom2"]
+  ]
+
+-------------------------------------------------------------------------------
+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.:
@@ -400,7 +562,7 @@
 -------------------------------------------------------------------------------
 -- | Generate an arbitrary 'RetryPolicy' without any limits applied.
 genPolicyNoLimit
-    :: (MonadGen mg, MonadIO mr)
+    :: forall mg mr. (MonadGen mg, MIO.MonadIO mr)
     => Range Int
     -> mg (RetryPolicyM mr)
 genPolicyNoLimit durationRange =
@@ -427,11 +589,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
 
diff --git a/test/Tests/UnliftIO/Retry.hs b/test/Tests/UnliftIO/Retry.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/UnliftIO/Retry.hs
@@ -0,0 +1,55 @@
+module Tests.UnliftIO.Retry
+    ( tests
+    ) where
+
+-------------------------------------------------------------------------------
+import           Test.Tasty
+import           Test.Tasty.HUnit            (testCase)
+-------------------------------------------------------------------------------
+import           UnliftIO.Retry
+import           Tests.Control.Retry hiding (tests)
+-------------------------------------------------------------------------------
+
+
+tests :: TestTree
+tests = testGroup "UnliftIO.Retry"
+  [ recoveringTests
+  , maskingStateTests
+  , quadraticDelayTests
+  , resumableTests
+  ]
+
+
+-------------------------------------------------------------------------------
+recoveringTests :: TestTree
+recoveringTests = recoveringTestsWith recovering
+
+
+-------------------------------------------------------------------------------
+maskingStateTests :: TestTree
+maskingStateTests = maskingStateTestsWith recovering
+
+
+-------------------------------------------------------------------------------
+quadraticDelayTests :: TestTree
+quadraticDelayTests = quadraticDelayTestsWith recovering
+
+
+-------------------------------------------------------------------------------
+resumableTests :: TestTree
+resumableTests = testGroup "resumable"
+  [ 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)
+            ()
+      ]
+  ]
