module Ecluse.Core.Breaker (
Breaker (..),
initialBreaker,
admit,
recordSuccess,
recordFailure,
BreakerReporter (..),
noBreakerReporter,
reportBreakerChange,
breakerState,
) where
import Data.Time (NominalDiffTime, UTCTime, addUTCTime)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
data Breaker
=
Closed Int
|
Open UTCTime
|
HalfOpen
deriving stock (Breaker -> Breaker -> Bool
(Breaker -> Breaker -> Bool)
-> (Breaker -> Breaker -> Bool) -> Eq Breaker
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Breaker -> Breaker -> Bool
== :: Breaker -> Breaker -> Bool
$c/= :: Breaker -> Breaker -> Bool
/= :: Breaker -> Breaker -> Bool
Eq, Int -> Breaker -> ShowS
[Breaker] -> ShowS
Breaker -> String
(Int -> Breaker -> ShowS)
-> (Breaker -> String) -> ([Breaker] -> ShowS) -> Show Breaker
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Breaker -> ShowS
showsPrec :: Int -> Breaker -> ShowS
$cshow :: Breaker -> String
show :: Breaker -> String
$cshowList :: [Breaker] -> ShowS
showList :: [Breaker] -> ShowS
Show)
initialBreaker :: Breaker
initialBreaker :: Breaker
initialBreaker = Int -> Breaker
Closed Int
0
admit :: UTCTime -> Breaker -> (Bool, Breaker)
admit :: UTCTime -> Breaker -> (Bool, Breaker)
admit UTCTime
now = \case
Open UTCTime
until' | UTCTime
now UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
< UTCTime
until' -> (Bool
False, UTCTime -> Breaker
Open UTCTime
until')
Open UTCTime
_ -> (Bool
True, Breaker
HalfOpen)
Breaker
healthy -> (Bool
True, Breaker
healthy)
recordSuccess :: Breaker -> Breaker
recordSuccess :: Breaker -> Breaker
recordSuccess Closed{} = Breaker
initialBreaker
recordSuccess Open{} = Breaker
initialBreaker
recordSuccess Breaker
HalfOpen = Breaker
initialBreaker
recordFailure :: Int -> NominalDiffTime -> UTCTime -> Breaker -> Breaker
recordFailure :: Int -> NominalDiffTime -> UTCTime -> Breaker -> Breaker
recordFailure Int
threshold NominalDiffTime
cooldown UTCTime
now = \case
Closed Int
n | Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
threshold -> Breaker
tripped
Closed Int
n -> Int -> Breaker
Closed (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
Open{} -> Breaker
tripped
Breaker
HalfOpen -> Breaker
tripped
where
tripped :: Breaker
tripped = UTCTime -> Breaker
Open (NominalDiffTime -> UTCTime -> UTCTime
addUTCTime NominalDiffTime
cooldown UTCTime
now)
newtype BreakerReporter = BreakerReporter (Breaker -> IO ())
noBreakerReporter :: BreakerReporter
noBreakerReporter :: BreakerReporter
noBreakerReporter = (Breaker -> IO ()) -> BreakerReporter
BreakerReporter (IO () -> Breaker -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass)
reportBreakerChange :: BreakerReporter -> Breaker -> Breaker -> IO ()
reportBreakerChange :: BreakerReporter -> Breaker -> Breaker -> IO ()
reportBreakerChange (BreakerReporter Breaker -> IO ()
report) Breaker
old Breaker
new
| Breaker -> BreakerState
breakerState Breaker
old BreakerState -> BreakerState -> Bool
forall a. Eq a => a -> a -> Bool
== Breaker -> BreakerState
breakerState Breaker
new = IO ()
forall (f :: * -> *). Applicative f => f ()
pass
| Bool
otherwise = Breaker -> IO ()
report Breaker
new
breakerState :: Breaker -> Metric.BreakerState
breakerState :: Breaker -> BreakerState
breakerState = \case
Closed{} -> BreakerState
Metric.Closed
Breaker
HalfOpen -> BreakerState
Metric.HalfOpen
Open{} -> BreakerState
Metric.Open