module Foundation.DB ( runDBRead, runDBRead' , runSqlPoolRetry, runSqlPoolRetry' , dbPoolPressured , runDBInternal, runDBInternal' ) where import Import.NoFoundation hiding (runDB, getDBRunner) import Foundation.Type import qualified Control.Retry as Retry import GHC.IO.Exception (IOErrorType(OtherError)) import Database.Persist.Sql (SqlReadBackend(..)) import Database.Persist.Sql.Raw.QQ (executeQQ) import qualified Utils.Pool as Custom runSqlPoolRetry :: forall m a backend c. ( HasCallStack , MonadUnliftIO m, BackendCompatible SqlBackend backend , MonadLogger m, MonadMask m ) => ReaderT backend m a -> Custom.Pool' m DBConnLabel c backend -> m a runSqlPoolRetry action pool = runSqlPoolRetry' action pool callStack runSqlPoolRetry' :: forall m a backend c. ( MonadUnliftIO m, BackendCompatible SqlBackend backend , MonadLogger m, MonadMask m ) => ReaderT backend m a -> Custom.Pool' m DBConnLabel c backend -> CallStack -> m a runSqlPoolRetry' action pool lbl = 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 customRunSqlPool' action pool lbl runDBRead :: HasCallStack => ReaderT SqlReadBackend (HandlerFor UniWorX) a -> (HandlerFor UniWorX) a runDBRead = runDBRead' callStack runDBRead' :: CallStack -> ReaderT SqlReadBackend (HandlerFor UniWorX) a -> (HandlerFor UniWorX) a runDBRead' lbl action = do $logDebugS "YesodPersist" "runDBRead" flip (runSqlPoolRetry' . withReaderT SqlReadBackend $ [executeQQ|SET TRANSACTION READ ONLY|] *> action) lbl . appConnPool =<< getYesod runDBInternal :: HasCallStack => ReaderT SqlBackend (HandlerFor UniWorX) a -> HandlerFor UniWorX a runDBInternal = runDBInternal' callStack runDBInternal' :: CallStack -> ReaderT SqlBackend (HandlerFor UniWorX) a -> HandlerFor UniWorX a runDBInternal' lbl action = do $logDebugS "YesodPersist" "runDBInternal" flip (runSqlPoolRetry' action) lbl . appConnPool =<< getYesod dbPoolPressured :: ( MonadHandler m , HandlerSite m ~ UniWorX ) => m Bool dbPoolPressured = do connPool <- getsYesod @_ @(Custom.Pool' IO _ _ _) appConnPool case Custom.getPoolMaxAvailable connPool of Nothing -> return False Just lim -> atomically $ (>= lim) <$> Custom.getPoolInUseCount connPool