ribosome-0.3.0.0: lib/Ribosome/Control/Concurrent/Wait.hs
module Ribosome.Control.Concurrent.Wait where
import Control.Exception.Lifted (Exception, SomeException(..), try)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.Default (Default(def))
import Data.Functor ((<&>))
import qualified Text.Show
import Ribosome.System.Time (sleep)
-- |Specifies the maximum number of retries and the interval in seconds for 'waitIO'.
data Retry =
Retry Int Double
deriving Show
instance Default Retry where
def = Retry 30 0.1
-- |Error description for 'waitIO'
data WaitError =
NotStarted
|
ConditionUnmet Text
|
∀ e. Exception e => Thrown e
instance Text.Show.Show WaitError where
show NotStarted =
"NotStarted"
show (ConditionUnmet reason) =
toString $ "ConditionUnmet(" <> reason <> ")"
show (Thrown _) =
"Thrown"
-- |Execute an IO thunk repeatedly until either the supplied condition produces a 'Right' or the maximum number of
-- retries specified in the `Retry` parameter has been reached.
-- Returns the value produced by the condition.
waitIO ::
MonadIO m =>
MonadBaseControl IO m =>
Retry ->
m a ->
(a -> m (Either Text b)) ->
m (Either WaitError b)
waitIO (Retry maxRetry interval) thunk cond =
wait maxRetry (Left NotStarted)
where
wait 0 reason = return reason
wait count _ = do
ea <- try thunk
result <- try $ check ea
case result of
Right (Right a) ->
return $ Right a
Right (Left reason) ->
recurse reason count
Left (SomeException e) ->
recurse (Thrown e) count
recurse reason count = do
sleep interval
wait (count - 1) (Left reason)
check (Right a) =
cond a <&> \case
Right b -> Right b
Left reason -> Left (ConditionUnmet reason)
check (Left (SomeException e)) =
return $ Left (Thrown e)
-- |Calls 'waitIO' with the default configuration of 30 retries every 100ms.
waitIODef ::
MonadIO m =>
MonadBaseControl IO m =>
m a ->
(a -> m (Either Text b)) ->
m (Either WaitError b)
waitIODef =
waitIO def
-- |Same as 'waitIO', but the condition returns 'Bool' and the result is the result of the thunk.
waitIOPred ::
MonadIO m =>
MonadBaseControl IO m =>
Retry ->
m a ->
(a -> m Bool) ->
m (Either WaitError a)
waitIOPred retry thunk pred' =
waitIO retry thunk cond
where
cond a = pred' a <&> \case
True -> Right a
False -> Left "predicate returned False"
-- |Calls 'waitIOPred' with the default configuration of 30 retries every 100ms.
waitIOPredDef ::
MonadIO m =>
MonadBaseControl IO m =>
m a ->
(a -> m Bool) ->
m (Either WaitError a)
waitIOPredDef =
waitIOPred def