fix: listener running with exception masked after first failure
This commit is contained in:
committed by
steve-chavez
parent
0a8b836435
commit
16c767134c
@@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. From versio
|
|||||||
|
|
||||||
- Ensure Listener connections are released by @mkleczek in #4614
|
- Ensure Listener connections are released by @mkleczek in #4614
|
||||||
- Fix incorrectly filtering the returned representation for PATCH requests when using `or/and` filters by @laurenceisla in #3707
|
- Fix incorrectly filtering the returned representation for PATCH requests when using `or/and` filters by @laurenceisla in #3707
|
||||||
|
- Fix listener running with exception masked after first failure #4615
|
||||||
|
|
||||||
## [14.3] - 2026-01-03
|
## [14.3] - 2026-01-03
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,16 @@ runListener :: AppState -> IO ()
|
|||||||
runListener appState = do
|
runListener appState = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
when configDbChannelEnabled $
|
when configDbChannelEnabled $
|
||||||
void . forkIO $ retryingListen appState
|
void . forkIO . void $ retryingListen appState
|
||||||
|
|
||||||
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
||||||
retryingListen :: AppState -> IO ()
|
-- | This function never returns (but can throw) and return type enforces that.
|
||||||
|
retryingListen :: AppState -> IO Void
|
||||||
retryingListen appState = do
|
retryingListen appState = do
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
let
|
let
|
||||||
dbChannel = toS configDbChannel
|
dbChannel = toS configDbChannel
|
||||||
handleFinally err = do
|
onError err = do
|
||||||
AppState.putIsListenerOn appState False
|
AppState.putIsListenerOn appState False
|
||||||
observer $ DBListenFail dbChannel (Right err)
|
observer $ DBListenFail dbChannel (Right err)
|
||||||
unless configDbPoolAutomaticRecovery $
|
unless configDbPoolAutomaticRecovery $
|
||||||
@@ -44,10 +45,11 @@ retryingListen appState = do
|
|||||||
threadDelay (delay * oneSecondInMicro)
|
threadDelay (delay * oneSecondInMicro)
|
||||||
unless (delay == maxDelay) $
|
unless (delay == maxDelay) $
|
||||||
AppState.putNextListenerDelay appState (delay * 2)
|
AppState.putNextListenerDelay appState (delay * 2)
|
||||||
|
-- loop running the listener
|
||||||
retryingListen appState
|
retryingListen appState
|
||||||
|
|
||||||
-- forkFinally allows to detect if the thread dies
|
-- Execute the listener with with error handling
|
||||||
void . flip forkFinally handleFinally $ do
|
handle onError $ do
|
||||||
-- Make sure we don't leak connections on errors
|
-- Make sure we don't leak connections on errors
|
||||||
bracket
|
bracket
|
||||||
-- acquire connection
|
-- acquire connection
|
||||||
@@ -68,7 +70,10 @@ retryingListen appState = do
|
|||||||
AppState.putNextListenerDelay appState 1
|
AppState.putNextListenerDelay appState 1
|
||||||
|
|
||||||
observer $ DBListenStart dbChannel
|
observer $ DBListenStart dbChannel
|
||||||
SQL.waitForNotifications handleNotification db
|
|
||||||
|
-- wait for notifications
|
||||||
|
-- this will never return, in case of an error it will throw and be caught by onError
|
||||||
|
forever $ SQL.waitForNotifications handleNotification db
|
||||||
|
|
||||||
Left err -> do
|
Left err -> do
|
||||||
observer $ DBListenFail dbChannel (Left err)
|
observer $ DBListenFail dbChannel (Left err)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ data Observation
|
|||||||
| SchemaCacheLoadedObs Double
|
| SchemaCacheLoadedObs Double
|
||||||
| ConnectionRetryObs Int
|
| ConnectionRetryObs Int
|
||||||
| DBListenStart Text
|
| DBListenStart Text
|
||||||
| DBListenFail Text (Either SQL.ConnectionError (Either SomeException ()))
|
| DBListenFail Text (Either SQL.ConnectionError SomeException)
|
||||||
| DBListenRetry Int
|
| DBListenRetry Int
|
||||||
| DBListenerGotSCacheMsg ByteString
|
| DBListenerGotSCacheMsg ByteString
|
||||||
| DBListenerGotConfigMsg ByteString
|
| DBListenerGotConfigMsg ByteString
|
||||||
@@ -167,9 +167,8 @@ observationMessage = \case
|
|||||||
showListenerConnError :: SQL.ConnectionError -> Text
|
showListenerConnError :: SQL.ConnectionError -> Text
|
||||||
showListenerConnError = maybe "Connection error" (showOnSingleLine '\t' . T.decodeUtf8)
|
showListenerConnError = maybe "Connection error" (showOnSingleLine '\t' . T.decodeUtf8)
|
||||||
|
|
||||||
showListenerException :: Either SomeException () -> Text
|
showListenerException :: SomeException -> Text
|
||||||
showListenerException (Right _) = "Failed getting notifications" -- should not happen as the listener will never finish (hasql-notifications uses `forever` internally) with a Right result
|
showListenerException = showOnSingleLine '\t' . show
|
||||||
showListenerException (Left e) = showOnSingleLine '\t' $ show e
|
|
||||||
|
|
||||||
|
|
||||||
showOnSingleLine :: Char -> Text -> Text
|
showOnSingleLine :: Char -> Text -> Text
|
||||||
|
|||||||
Reference in New Issue
Block a user