module Ecluse.Pilot.Osv.Retry (
defaultOsvRetryPolicy,
isRetryableHttpException,
isRetryableStatusCode,
withOsvRetry,
transientMessage,
) where
import Control.Monad.Catch (Handler (Handler), MonadMask)
import Control.Retry (
RetryPolicyM,
RetryStatus (rsIterNumber),
capDelay,
fullJitterBackoff,
limitRetries,
recovering,
)
import Katip (KatipContext, Severity (WarningS), logFM, ls)
import Network.HTTP.Client (
HttpException (..),
HttpExceptionContent (..),
responseStatus,
)
import Network.HTTP.Types.Status (statusCode)
defaultOsvRetryPolicy :: (MonadIO m) => RetryPolicyM m
defaultOsvRetryPolicy :: forall (m :: * -> *). MonadIO m => RetryPolicyM m
defaultOsvRetryPolicy = Int -> RetryPolicy
limitRetries Int
5 RetryPolicyM m -> RetryPolicyM m -> RetryPolicyM m
forall a. Semigroup a => a -> a -> a
<> Int -> RetryPolicyM m -> RetryPolicyM m
forall (m :: * -> *).
Monad m =>
Int -> RetryPolicyM m -> RetryPolicyM m
capDelay Int
60_000_000 (Int -> RetryPolicyM m
forall (m :: * -> *). MonadIO m => Int -> RetryPolicyM m
fullJitterBackoff Int
1_000_000)
isRetryableStatusCode :: Int -> Bool
isRetryableStatusCode :: Int -> Bool
isRetryableStatusCode Int
code = Int
code Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
500 Bool -> Bool -> Bool
|| Int
code Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
408 Bool -> Bool -> Bool
|| Int
code Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
429
isRetryableHttpException :: HttpException -> Bool
isRetryableHttpException :: HttpException -> Bool
isRetryableHttpException = \case
InvalidUrlException{} -> Bool
False
HttpExceptionRequest Request
_ HttpExceptionContent
content -> case HttpExceptionContent
content of
StatusCodeException Response ()
response ByteString
_ -> Int -> Bool
isRetryableStatusCode (Status -> Int
statusCode (Response () -> Status
forall body. Response body -> Status
responseStatus Response ()
response))
ConnectionFailure{} -> Bool
True
HttpExceptionContent
ConnectionTimeout -> Bool
True
HttpExceptionContent
ResponseTimeout -> Bool
True
HttpExceptionContent
NoResponseDataReceived -> Bool
True
HttpExceptionContent
ConnectionClosed -> Bool
True
HttpExceptionContent
_ -> Bool
False
withOsvRetry :: (MonadMask m, KatipContext m) => RetryPolicyM m -> m a -> m a
withOsvRetry :: forall (m :: * -> *) a.
(MonadMask m, KatipContext m) =>
RetryPolicyM m -> m a -> m a
withOsvRetry RetryPolicyM m
policy m a
fetch =
RetryPolicyM m
-> [RetryStatus -> Handler m Bool] -> (RetryStatus -> m a) -> m a
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
RetryPolicyM m
-> [RetryStatus -> Handler m Bool] -> (RetryStatus -> m a) -> m a
recovering RetryPolicyM m
policy [RetryStatus -> Handler m Bool
forall (m :: * -> *).
KatipContext m =>
RetryStatus -> Handler m Bool
retryHandler] (m a -> RetryStatus -> m a
forall a b. a -> b -> a
const m a
fetch)
retryHandler :: (KatipContext m) => RetryStatus -> Handler m Bool
retryHandler :: forall (m :: * -> *).
KatipContext m =>
RetryStatus -> Handler m Bool
retryHandler RetryStatus
status = (HttpException -> m Bool) -> Handler m Bool
forall (m :: * -> *) a e. Exception e => (e -> m a) -> Handler m a
Handler ((HttpException -> m Bool) -> Handler m Bool)
-> (HttpException -> m Bool) -> Handler m Bool
forall a b. (a -> b) -> a -> b
$ \HttpException
e ->
if HttpException -> Bool
isRetryableHttpException HttpException
e
then Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
WarningS (String -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (RetryStatus -> HttpException -> String
transientMessage RetryStatus
status HttpException
e)) m () -> m Bool -> m Bool
forall a b. m a -> m b -> m b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Bool -> m Bool
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True
else Bool -> m Bool
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
transientMessage :: RetryStatus -> HttpException -> String
transientMessage :: RetryStatus -> HttpException -> String
transientMessage RetryStatus
status HttpException
err =
String
"osv.dev fetch failed transiently on attempt "
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall b a. (Show a, IsString b) => a -> b
show (Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ RetryStatus -> Int
rsIterNumber RetryStatus
status)
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"; backing off before the next retry. Cause: "
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> HttpException -> String
forall b a. (Show a, IsString b) => a -> b
show HttpException
err