refactor: make nextListenerDelay local to Listener module

Right now stateNextListenerDelay is only used in Listener module.
This change is a small refactoring moving nextListenerDelay to Listener to increase cohesion and decrease coupling.
This commit is contained in:
Michał Kłeczek
2026-06-14 18:39:59 +00:00
committed by Wolfgang Walther
parent 2976eb047b
commit fae6253932
2 changed files with 26 additions and 34 deletions
+14 -25
View File
@@ -11,13 +11,11 @@ module PostgREST.AppState
, getMainThreadId , getMainThreadId
, getPgVersion , getPgVersion
, getNextDelay , getNextDelay
, getNextListenerDelay
, getTime , getTime
, getJwtCacheState , getJwtCacheState
, init , init
, initWithPool , initWithPool
, putConfig -- For tests TODO refactoring , putConfig -- For tests TODO refactoring
, putNextListenerDelay
, putSchemaCache , putSchemaCache
, putPgVersion , putPgVersion
, putIsListenerOn , putIsListenerOn
@@ -72,33 +70,31 @@ import Protolude
data AppState = AppState data AppState = AppState
-- | Database connection pool -- | Database connection pool
{ statePool :: SQL.Pool { statePool :: SQL.Pool
-- | Database server version -- | Database server version
, statePgVersion :: IORef PgVersion , statePgVersion :: IORef PgVersion
-- | Schema cache -- | Schema cache
, stateSchemaCache :: IORef (Maybe SchemaCache) , stateSchemaCache :: IORef (Maybe SchemaCache)
-- | The schema cache status -- | The schema cache status
, stateSCacheStatus :: SchemaCacheStatus , stateSCacheStatus :: SchemaCacheStatus
-- | State of the LISTEN channel -- | State of the LISTEN channel
, stateIsListenerOn :: IORef Bool , stateIsListenerOn :: IORef Bool
-- | starts the connection worker with a debounce -- | starts the connection worker with a debounce
, debouncedSCacheLoader :: IO () , debouncedSCacheLoader :: IO ()
-- | Config that can change at runtime -- | Config that can change at runtime
, stateConf :: IORef AppConfig , stateConf :: IORef AppConfig
-- | Time used for verifying JWT expiration -- | Time used for verifying JWT expiration
, stateGetTime :: IO UTCTime , stateGetTime :: IO UTCTime
-- | Used for killing the main thread in case a subthread fails -- | Used for killing the main thread in case a subthread fails
, stateMainThreadId :: ThreadId , stateMainThreadId :: ThreadId
-- | Keeps track of the next delay for db connection retry -- | Keeps track of the next delay for db connection retry
, stateNextDelay :: IORef Int , stateNextDelay :: IORef Int
-- | Keeps track of the next delay for the listener
, stateNextListenerDelay :: IORef Int
-- | Observation handler -- | Observation handler
, stateObserver :: ObservationHandler , stateObserver :: ObservationHandler
-- | JWT Cache -- | JWT Cache
, stateJwtCache :: JwtCache.JwtCacheState , stateJwtCache :: JwtCache.JwtCacheState
, stateLogger :: Logger.LoggerState , stateLogger :: Logger.LoggerState
, stateMetrics :: Metrics.MetricsState , stateMetrics :: Metrics.MetricsState
} }
-- | Schema cache status. -- | Schema cache status.
@@ -131,7 +127,6 @@ initWithPool pool conf loggerState metricsState observer = mdo
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime } <*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
<*> myThreadId <*> myThreadId
<*> newIORef 0 <*> newIORef 0
<*> newIORef 1
<*> pure observer <*> pure observer
<*> JwtCache.init conf observer <*> JwtCache.init conf observer
<*> pure loggerState <*> pure loggerState
@@ -248,12 +243,6 @@ schemaCacheLoader = debouncedSCacheLoader
getNextDelay :: AppState -> IO Int getNextDelay :: AppState -> IO Int
getNextDelay = readIORef . stateNextDelay getNextDelay = readIORef . stateNextDelay
getNextListenerDelay :: AppState -> IO Int
getNextListenerDelay = readIORef . stateNextListenerDelay
putNextListenerDelay :: AppState -> Int -> IO ()
putNextListenerDelay = atomicWriteIORef . stateNextListenerDelay
getConfig :: AppState -> IO AppConfig getConfig :: AppState -> IO AppConfig
getConfig = readIORef . stateConf getConfig = readIORef . stateConf
+12 -9
View File
@@ -18,6 +18,8 @@ import qualified PostgREST.Config as Config
import Control.Arrow ((&&&)) import Control.Arrow ((&&&))
import Data.Bitraversable (bisequence) import Data.Bitraversable (bisequence)
import Data.Either.Combinators (whenRight) import Data.Either.Combinators (whenRight)
import Data.IORef (IORef, newIORef,
readIORef, writeIORef)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.PostgreSQL.LibPQ as LibPQ import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.Session as SQL import qualified Hasql.Session as SQL
@@ -29,13 +31,14 @@ import Protolude
runListener :: AppState -> IO () runListener :: AppState -> IO ()
runListener appState = do runListener appState = do
AppConfig{..} <- getConfig appState AppConfig{..} <- getConfig appState
when configDbChannelEnabled $ when configDbChannelEnabled $ do
void . forkIO . void $ retryingListen appState False nextDelay <- newIORef 1
void . forkIO . void $ retryingListen appState nextDelay False
-- | 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.
-- | This function never returns (but can throw) and return type enforces that. -- | This function never returns (but can throw) and return type enforces that.
retryingListen :: AppState -> Bool -> IO Void retryingListen :: AppState -> IORef Int -> Bool -> IO Void
retryingListen appState hasDbListenerBug = do retryingListen appState nextDelay hasDbListenerBug = do
cfg@AppConfig{..} <- AppState.getConfig appState cfg@AppConfig{..} <- AppState.getConfig appState
let let
dbChannel = toS configDbChannel dbChannel = toS configDbChannel
@@ -48,13 +51,13 @@ retryingListen appState hasDbListenerBug = do
killThread mainThreadId killThread mainThreadId
-- retry the listener -- retry the listener
delay <- AppState.getNextListenerDelay appState delay <- readIORef nextDelay
observer $ DBListenRetry delay observer $ DBListenRetry delay
threadDelay (delay * oneSecondInMicro) threadDelay (delay * oneSecondInMicro)
unless (delay == maxDelay) $ unless (delay == maxDelay) $
AppState.putNextListenerDelay appState (delay * 2) writeIORef nextDelay (delay * 2)
-- loop running the listener -- loop running the listener
retryingListen appState (isDbListenerBug err) retryingListen appState nextDelay (isDbListenerBug err)
-- Execute the listener with error handling -- Execute the listener with error handling
handle onError $ do handle onError $ do
@@ -75,12 +78,12 @@ retryingListen appState hasDbListenerBug = do
AppState.putIsListenerOn appState True AppState.putIsListenerOn appState True
delay <- AppState.getNextListenerDelay appState delay <- readIORef nextDelay
when (delay > 1) $ do -- if we did a retry when (delay > 1) $ do -- if we did a retry
-- assume we lost notifications, refresh the schema cache -- assume we lost notifications, refresh the schema cache
AppState.schemaCacheLoader appState AppState.schemaCacheLoader appState
-- reset the delay -- reset the delay
AppState.putNextListenerDelay appState 1 writeIORef nextDelay 1
observer $ DBListenStart pqHost pqPort pgFullName dbChannel observer $ DBListenStart pqHost pqPort pgFullName dbChannel