feat(db): automatic retry of database transactions upon system error
BREAKING CHANGE: transactions need to be retryable, now
This commit is contained in:
parent
1926917dd7
commit
e7a5162ec9
@ -107,6 +107,9 @@ import Web.Cookie
|
|||||||
import Yesod.Core.Types (GHState(..), HandlerData(..), HandlerContents, RunHandlerEnv(rheSite, rheChild))
|
import Yesod.Core.Types (GHState(..), HandlerData(..), HandlerContents, RunHandlerEnv(rheSite, rheChild))
|
||||||
import Database.Persist.Sql (transactionUndo, SqlReadBackend(..))
|
import Database.Persist.Sql (transactionUndo, SqlReadBackend(..))
|
||||||
|
|
||||||
|
import qualified Control.Retry as Retry
|
||||||
|
import GHC.IO.Exception (IOErrorType(OtherError))
|
||||||
|
|
||||||
-- | Convenient Type Synonyms:
|
-- | Convenient Type Synonyms:
|
||||||
type DB = YesodDB UniWorX
|
type DB = YesodDB UniWorX
|
||||||
type Form x = Html -> MForm (HandlerFor UniWorX) (FormResult x, Widget)
|
type Form x = Html -> MForm (HandlerFor UniWorX) (FormResult x, Widget)
|
||||||
@ -4564,10 +4567,37 @@ routeNormalizers =
|
|||||||
return newRoute
|
return newRoute
|
||||||
|
|
||||||
|
|
||||||
|
runSqlPoolRetry :: forall m a backend.
|
||||||
|
( MonadUnliftIO m, BackendCompatible SqlBackend backend
|
||||||
|
, MonadLogger m, MonadMask m
|
||||||
|
)
|
||||||
|
=> ReaderT backend m a
|
||||||
|
-> Pool backend
|
||||||
|
-> m a
|
||||||
|
runSqlPoolRetry action pool = do
|
||||||
|
let policy = Retry.fullJitterBackoff 1e3 & Retry.limitRetriesByCumulativeDelay 10e6
|
||||||
|
handlers = Retry.skipAsyncExceptions `snoc` Retry.logRetries suggestRetry logRetry
|
||||||
|
where suggestRetry :: IOException -> m Bool
|
||||||
|
suggestRetry ioExc = return $
|
||||||
|
ioeGetErrorType ioExc == OtherError
|
||||||
|
&& ioeGetLocation ioExc == "libpq"
|
||||||
|
logRetry :: forall e.
|
||||||
|
Exception e
|
||||||
|
=> Bool -- ^ Will retry
|
||||||
|
-> e
|
||||||
|
-> Retry.RetryStatus
|
||||||
|
-> m ()
|
||||||
|
logRetry shouldRetry@False err status = $logErrorS "runSqlPoolRetry" . pack $ Retry.defaultLogMsg shouldRetry err status
|
||||||
|
logRetry shouldRetry@True err status = $logWarnS "runSqlPoolRetry" . pack $ Retry.defaultLogMsg shouldRetry err status
|
||||||
|
|
||||||
|
Retry.recovering policy handlers $ \Retry.RetryStatus{..} -> do
|
||||||
|
$logDebugS "runSqlPoolRetry" $ "rsIterNumber = " <> tshow rsIterNumber
|
||||||
|
runSqlPool action pool
|
||||||
|
|
||||||
runDBRead :: ReaderT SqlReadBackend Handler a -> Handler a
|
runDBRead :: ReaderT SqlReadBackend Handler a -> Handler a
|
||||||
runDBRead action = do
|
runDBRead action = do
|
||||||
$logDebugS "YesodPersist" "runDBRead"
|
$logDebugS "YesodPersist" "runDBRead"
|
||||||
runSqlPool (withReaderT SqlReadBackend action) =<< appConnPool <$> getYesod
|
runSqlPoolRetry (withReaderT SqlReadBackend action) =<< appConnPool <$> getYesod
|
||||||
|
|
||||||
-- How to run database actions.
|
-- How to run database actions.
|
||||||
instance YesodPersist UniWorX where
|
instance YesodPersist UniWorX where
|
||||||
@ -4580,7 +4610,9 @@ instance YesodPersist UniWorX where
|
|||||||
let action'
|
let action'
|
||||||
| dryRun = action <* transactionUndo
|
| dryRun = action <* transactionUndo
|
||||||
| otherwise = action
|
| otherwise = action
|
||||||
runSqlPool action' =<< appConnPool <$> getYesod
|
|
||||||
|
runSqlPoolRetry action' =<< appConnPool <$> getYesod
|
||||||
|
|
||||||
instance YesodPersistRunner UniWorX where
|
instance YesodPersistRunner UniWorX where
|
||||||
getDBRunner = do
|
getDBRunner = do
|
||||||
(DBRunner{..}, cleanup) <- defaultGetDBRunner appConnPool
|
(DBRunner{..}, cleanup) <- defaultGetDBRunner appConnPool
|
||||||
|
|||||||
@ -66,6 +66,8 @@ import Jobs.HealthReport
|
|||||||
|
|
||||||
import Control.Exception.Base (AsyncException)
|
import Control.Exception.Base (AsyncException)
|
||||||
|
|
||||||
|
import Type.Reflection (typeOf)
|
||||||
|
|
||||||
|
|
||||||
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
data JobQueueException = JInvalid QueuedJobId QueuedJob
|
||||||
| JLocked QueuedJobId InstanceId UTCTime
|
| JLocked QueuedJobId InstanceId UTCTime
|
||||||
@ -364,7 +366,8 @@ handleJobs' wNum = C.mapM_ $ \jctl -> withJobWorkerState wNum JobWorkerBusy $ do
|
|||||||
lift $ foldrM (\resVar -> bool (tryPutTMVar resVar res) $ return True) False (maybe [] NonEmpty.toList resVars)
|
lift $ foldrM (\resVar -> bool (tryPutTMVar resVar res) $ return True) False (maybe [] NonEmpty.toList resVars)
|
||||||
case res of
|
case res of
|
||||||
Just err
|
Just err
|
||||||
| not sentRes -> $logErrorS logIdent $ tshow err
|
| not sentRes -> case err of
|
||||||
|
SomeException err' -> $logErrorS logIdent $ tshow (typeOf err') <> ": " <> pack (displayException err')
|
||||||
_other -> return ()
|
_other -> return ()
|
||||||
where
|
where
|
||||||
logIdent = mkLogIdent wNum
|
logIdent = mkLogIdent wNum
|
||||||
|
|||||||
Reference in New Issue
Block a user