module Ecluse.Core.Supervision (
superviseLoop,
SupervisionPolicy (..),
FaultDisposition (..),
BackoffSchedule (..),
backoffMicros,
) where
import Katip (KatipContext, Severity (ErrorS), logFM, ls)
import UnliftIO (MonadUnliftIO)
import UnliftIO.Concurrent (threadDelay)
import UnliftIO.Exception (throwIO, tryAny)
import Ecluse.Core.Text (displayExceptionT)
data FaultDisposition
=
Transient
|
Permanent
deriving stock (FaultDisposition -> FaultDisposition -> Bool
(FaultDisposition -> FaultDisposition -> Bool)
-> (FaultDisposition -> FaultDisposition -> Bool)
-> Eq FaultDisposition
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FaultDisposition -> FaultDisposition -> Bool
== :: FaultDisposition -> FaultDisposition -> Bool
$c/= :: FaultDisposition -> FaultDisposition -> Bool
/= :: FaultDisposition -> FaultDisposition -> Bool
Eq, Int -> FaultDisposition -> ShowS
[FaultDisposition] -> ShowS
FaultDisposition -> String
(Int -> FaultDisposition -> ShowS)
-> (FaultDisposition -> String)
-> ([FaultDisposition] -> ShowS)
-> Show FaultDisposition
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FaultDisposition -> ShowS
showsPrec :: Int -> FaultDisposition -> ShowS
$cshow :: FaultDisposition -> String
show :: FaultDisposition -> String
$cshowList :: [FaultDisposition] -> ShowS
showList :: [FaultDisposition] -> ShowS
Show)
data BackoffSchedule = BackoffSchedule
{ BackoffSchedule -> Int
bsBaseMicros :: Int
, BackoffSchedule -> Int
bsCapMicros :: Int
}
deriving stock (BackoffSchedule -> BackoffSchedule -> Bool
(BackoffSchedule -> BackoffSchedule -> Bool)
-> (BackoffSchedule -> BackoffSchedule -> Bool)
-> Eq BackoffSchedule
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BackoffSchedule -> BackoffSchedule -> Bool
== :: BackoffSchedule -> BackoffSchedule -> Bool
$c/= :: BackoffSchedule -> BackoffSchedule -> Bool
/= :: BackoffSchedule -> BackoffSchedule -> Bool
Eq, Int -> BackoffSchedule -> ShowS
[BackoffSchedule] -> ShowS
BackoffSchedule -> String
(Int -> BackoffSchedule -> ShowS)
-> (BackoffSchedule -> String)
-> ([BackoffSchedule] -> ShowS)
-> Show BackoffSchedule
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BackoffSchedule -> ShowS
showsPrec :: Int -> BackoffSchedule -> ShowS
$cshow :: BackoffSchedule -> String
show :: BackoffSchedule -> String
$cshowList :: [BackoffSchedule] -> ShowS
showList :: [BackoffSchedule] -> ShowS
Show)
backoffMicros :: BackoffSchedule -> Int -> Int
backoffMicros :: BackoffSchedule -> Int -> Int
backoffMicros BackoffSchedule
schedule Int
consecutiveFailures =
Int -> Int -> Int
forall a. Ord a => a -> a -> a
min (BackoffSchedule -> Int
bsCapMicros BackoffSchedule
schedule) (BackoffSchedule -> Int
bsBaseMicros BackoffSchedule
schedule Int -> Int -> Int
forall a. Num a => a -> a -> a
* (Int
2 Int -> Int -> Int
forall a b. (Num a, Integral b) => a -> b -> a
^ Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
consecutiveFailures Int
backoffShiftClamp))
backoffShiftClamp :: Int
backoffShiftClamp :: Int
backoffShiftClamp = Int
12
data SupervisionPolicy = SupervisionPolicy
{ SupervisionPolicy -> Text
spLabel :: Text
, SupervisionPolicy -> SomeException -> FaultDisposition
spClassify :: SomeException -> FaultDisposition
, SupervisionPolicy -> BackoffSchedule
spBackoff :: BackoffSchedule
}
superviseLoop :: (MonadUnliftIO m, KatipContext m) => SupervisionPolicy -> m () -> m Void
superviseLoop :: forall (m :: * -> *).
(MonadUnliftIO m, KatipContext m) =>
SupervisionPolicy -> m () -> m Void
superviseLoop SupervisionPolicy
policy m ()
step = Int -> m Void
forall {b}. Int -> m b
go Int
0
where
go :: Int -> m b
go Int
consecutiveFaults =
m () -> m (Either SomeException ())
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either SomeException a)
tryAny m ()
step m (Either SomeException ())
-> (Either SomeException () -> m b) -> m b
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right () -> Int -> m b
go Int
0
Left SomeException
fault -> case SupervisionPolicy -> SomeException -> FaultDisposition
spClassify SupervisionPolicy
policy SomeException
fault of
FaultDisposition
Permanent -> do
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
ErrorS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (SupervisionPolicy -> Text
spLabel SupervisionPolicy
policy Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": permanent fault, failing up: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> SomeException -> Text
forall e. Exception e => e -> Text
displayExceptionT SomeException
fault))
SomeException -> m b
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO SomeException
fault
FaultDisposition
Transient -> do
let delay :: Int
delay = BackoffSchedule -> Int -> Int
backoffMicros (SupervisionPolicy -> BackoffSchedule
spBackoff SupervisionPolicy
policy) Int
consecutiveFaults
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
ErrorS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (SupervisionPolicy -> Text
spLabel SupervisionPolicy
policy Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": iteration faulted (retrying in " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
delay Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"µs): " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> SomeException -> Text
forall e. Exception e => e -> Text
displayExceptionT SomeException
fault))
Int -> m ()
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay Int
delay
Int -> m b
go (Int
consecutiveFaults Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)