refactor: encapsulate application termination
This change makes AppState and Listener modules independent from the way how application is terminated. It removes stateMainThreadId from AppState and introduces stateKillApp. It also removes exported function AppState.getMainThreadId replacing it with more general killApp.
This commit is contained in:
committed by
Steve Chavez
parent
06bda07db1
commit
56df1dc532
+21
-21
@@ -8,13 +8,13 @@ module PostgREST.AppState
|
|||||||
, destroy
|
, destroy
|
||||||
, getConfig
|
, getConfig
|
||||||
, getSchemaCache
|
, getSchemaCache
|
||||||
, getMainThreadId
|
|
||||||
, getPgVersion
|
, getPgVersion
|
||||||
, getNextDelay
|
, getNextDelay
|
||||||
, getTime
|
, getTime
|
||||||
, getJwtCacheState
|
, getJwtCacheState
|
||||||
, init
|
, init
|
||||||
, initWithPool
|
, initWithPool
|
||||||
|
, killApp
|
||||||
, putConfig -- For tests TODO refactoring
|
, putConfig -- For tests TODO refactoring
|
||||||
, putSchemaCache
|
, putSchemaCache
|
||||||
, putPgVersion
|
, putPgVersion
|
||||||
@@ -90,7 +90,7 @@ data AppState = AppState
|
|||||||
-- | 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
|
, stateKillApp :: IO ()
|
||||||
-- | 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
|
||||||
-- | Observation handler
|
-- | Observation handler
|
||||||
@@ -109,8 +109,8 @@ newtype SchemaCacheStatus = SchemaCacheStatus
|
|||||||
{ getSCStatusTMVar :: TMVar Bool
|
{ getSCStatusTMVar :: TMVar Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
init :: AppConfig -> IO AppState
|
init :: AppConfig -> IO () -> IO AppState
|
||||||
init conf@AppConfig{configLogLevel, configDbPoolSize} = do
|
init conf@AppConfig{configLogLevel, configDbPoolSize} appKiller = do
|
||||||
loggerState <- Logger.init
|
loggerState <- Logger.init
|
||||||
metricsState <- Metrics.init configDbPoolSize
|
metricsState <- Metrics.init configDbPoolSize
|
||||||
let observer = liftA2 (>>) (Logger.observationLogger loggerState configLogLevel) (Metrics.observationMetrics metricsState)
|
let observer = liftA2 (>>) (Logger.observationLogger loggerState configLogLevel) (Metrics.observationMetrics metricsState)
|
||||||
@@ -118,10 +118,10 @@ init conf@AppConfig{configLogLevel, configDbPoolSize} = do
|
|||||||
observer $ AppStartObs prettyVersion
|
observer $ AppStartObs prettyVersion
|
||||||
|
|
||||||
pool <- initPool conf observer
|
pool <- initPool conf observer
|
||||||
initWithPool pool conf loggerState metricsState observer
|
initWithPool pool conf loggerState metricsState observer appKiller
|
||||||
|
|
||||||
initWithPool :: SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
|
initWithPool :: SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO () -> IO AppState
|
||||||
initWithPool pool conf loggerState metricsState observer = mdo
|
initWithPool pool conf loggerState metricsState observer appKiller = mdo
|
||||||
|
|
||||||
appState <- AppState pool
|
appState <- AppState pool
|
||||||
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
|
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
|
||||||
@@ -131,7 +131,7 @@ initWithPool pool conf loggerState metricsState observer = mdo
|
|||||||
<*> makeDebouncer (retryingSchemaCacheLoad appState *> threadDelay 100000) -- 100ms cooldown
|
<*> makeDebouncer (retryingSchemaCacheLoad appState *> threadDelay 100000) -- 100ms cooldown
|
||||||
<*> newIORef conf
|
<*> newIORef conf
|
||||||
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
<*> myThreadId
|
<*> pure appKiller
|
||||||
<*> newIORef 0
|
<*> newIORef 0
|
||||||
<*> pure observer
|
<*> pure observer
|
||||||
<*> JwtCache.init conf observer
|
<*> JwtCache.init conf observer
|
||||||
@@ -156,7 +156,7 @@ initPool cfg@AppConfig{..} observer = do
|
|||||||
|
|
||||||
-- | Run an action with a database connection.
|
-- | Run an action with a database connection.
|
||||||
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
||||||
usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} sess = do
|
usePool appState@AppState{stateObserver=observer, ..} sess = do
|
||||||
observer PoolRequest
|
observer PoolRequest
|
||||||
|
|
||||||
res <- SQL.use statePool sess
|
res <- SQL.use statePool sess
|
||||||
@@ -170,7 +170,7 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
|
|||||||
let failureMessage = BS.unpack $ fromMaybe mempty e in
|
let failureMessage = BS.unpack $ fromMaybe mempty e in
|
||||||
when (("FATAL: password authentication failed" `isInfixOf` failureMessage) || ("no password supplied" `isInfixOf` failureMessage)) $ do
|
when (("FATAL: password authentication failed" `isInfixOf` failureMessage) || ("no password supplied" `isInfixOf` failureMessage)) $ do
|
||||||
observer $ ExitDBFatalError ServerAuthError err
|
observer $ ExitDBFatalError ServerAuthError err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
err@(SQL.SessionUsageError (SQL.QueryError tpl _ (SQL.ResultError resultErr))) ->
|
err@(SQL.SessionUsageError (SQL.QueryError tpl _ (SQL.ResultError resultErr))) ->
|
||||||
handleResultError err tpl resultErr
|
handleResultError err tpl resultErr
|
||||||
err@(SQL.SessionUsageError (SQL.PipelineError (SQL.ResultError resultErr))) ->
|
err@(SQL.SessionUsageError (SQL.PipelineError (SQL.ResultError resultErr))) ->
|
||||||
@@ -188,32 +188,32 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
|
|||||||
case resultErr of
|
case resultErr of
|
||||||
SQL.UnexpectedResult{} -> do
|
SQL.UnexpectedResult{} -> do
|
||||||
observer $ ExitDBFatalError ServerPgrstBug err
|
observer $ ExitDBFatalError ServerPgrstBug err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
SQL.RowError{} -> do
|
SQL.RowError{} -> do
|
||||||
observer $ ExitDBFatalError ServerPgrstBug err
|
observer $ ExitDBFatalError ServerPgrstBug err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
SQL.UnexpectedAmountOfRows{} -> do
|
SQL.UnexpectedAmountOfRows{} -> do
|
||||||
observer $ ExitDBFatalError ServerPgrstBug err
|
observer $ ExitDBFatalError ServerPgrstBug err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
-- Check for a syntax error (42601 is the pg code) only for queries that don't have `WITH pgrst_source` as prefix.
|
-- Check for a syntax error (42601 is the pg code) only for queries that don't have `WITH pgrst_source` as prefix.
|
||||||
-- This would mean the error is on our schema cache queries, so we treat it as fatal.
|
-- This would mean the error is on our schema cache queries, so we treat it as fatal.
|
||||||
-- TODO have a better way to mark this as a schema cache query
|
-- TODO have a better way to mark this as a schema cache query
|
||||||
SQL.ServerError "42601" _ _ _ _ ->
|
SQL.ServerError "42601" _ _ _ _ ->
|
||||||
unless ("WITH pgrst_source" `BS.isPrefixOf` tpl) $ do
|
unless ("WITH pgrst_source" `BS.isPrefixOf` tpl) $ do
|
||||||
observer $ ExitDBFatalError ServerPgrstBug err
|
observer $ ExitDBFatalError ServerPgrstBug err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
-- Check for a "prepared statement <name> already exists" error (Code 42P05: duplicate_prepared_statement).
|
-- Check for a "prepared statement <name> already exists" error (Code 42P05: duplicate_prepared_statement).
|
||||||
-- This would mean that a connection pooler in transaction mode is being used
|
-- This would mean that a connection pooler in transaction mode is being used
|
||||||
-- while prepared statements are enabled in the PostgREST configuration,
|
-- while prepared statements are enabled in the PostgREST configuration,
|
||||||
-- both of which are incompatible with each other.
|
-- both of which are incompatible with each other.
|
||||||
SQL.ServerError "42P05" _ _ _ _ -> do
|
SQL.ServerError "42P05" _ _ _ _ -> do
|
||||||
observer $ ExitDBFatalError ServerError42P05 err
|
observer $ ExitDBFatalError ServerError42P05 err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
-- Check for a "transaction blocks not allowed in statement pooling mode" error (Code 08P01: protocol_violation).
|
-- Check for a "transaction blocks not allowed in statement pooling mode" error (Code 08P01: protocol_violation).
|
||||||
-- This would mean that a connection pooler in statement mode is being used which is not supported in PostgREST.
|
-- This would mean that a connection pooler in statement mode is being used which is not supported in PostgREST.
|
||||||
SQL.ServerError "08P01" "transaction blocks not allowed in statement pooling mode" _ _ _ -> do
|
SQL.ServerError "08P01" "transaction blocks not allowed in statement pooling mode" _ _ _ -> do
|
||||||
observer $ ExitDBFatalError ServerError08P01 err
|
observer $ ExitDBFatalError ServerError08P01 err
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
SQL.ServerError{} ->
|
SQL.ServerError{} ->
|
||||||
when (Error.status (Error.PgError False err) >= HTTP.status500) $
|
when (Error.status (Error.PgError False err) >= HTTP.status500) $
|
||||||
observer $ QueryErrorCodeHighObs err
|
observer $ QueryErrorCodeHighObs err
|
||||||
@@ -261,8 +261,8 @@ getTime = stateGetTime
|
|||||||
getJwtCacheState :: AppState -> JwtCacheState
|
getJwtCacheState :: AppState -> JwtCacheState
|
||||||
getJwtCacheState = stateJwtCache
|
getJwtCacheState = stateJwtCache
|
||||||
|
|
||||||
getMainThreadId :: AppState -> ThreadId
|
killApp :: AppState -> IO ()
|
||||||
getMainThreadId = stateMainThreadId
|
killApp = stateKillApp
|
||||||
|
|
||||||
isConnEstablished :: AppState -> IO Bool
|
isConnEstablished :: AppState -> IO Bool
|
||||||
isConnEstablished appState = do
|
isConnEstablished appState = do
|
||||||
@@ -298,7 +298,7 @@ getObserver = stateObserver
|
|||||||
-- + Because connections cache the pg catalog(see #2620)
|
-- + Because connections cache the pg catalog(see #2620)
|
||||||
-- + For rapid recovery. Otherwise, the pool idle or lifetime timeout would have to be reached for new healthy connections to be acquired.
|
-- + For rapid recovery. Otherwise, the pool idle or lifetime timeout would have to be reached for new healthy connections to be acquired.
|
||||||
retryingSchemaCacheLoad :: AppState -> IO ()
|
retryingSchemaCacheLoad :: AppState -> IO ()
|
||||||
retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} =
|
retryingSchemaCacheLoad appState@AppState{stateObserver=observer} =
|
||||||
void $ retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do
|
void $ retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do
|
||||||
when (rsIterNumber > 0) $ do
|
when (rsIterNumber > 0) $ do
|
||||||
let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs
|
let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs
|
||||||
@@ -316,12 +316,12 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
|
|||||||
observer $ QueryPgVersionError e
|
observer $ QueryPgVersionError e
|
||||||
unless configDbPoolAutomaticRecovery $ do
|
unless configDbPoolAutomaticRecovery $ do
|
||||||
observer ExitDBNoRecoveryObs
|
observer ExitDBNoRecoveryObs
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
return Nothing
|
return Nothing
|
||||||
Right actualPgVersion ->
|
Right actualPgVersion ->
|
||||||
if actualPgVersion < minimumPgVersion then do
|
if actualPgVersion < minimumPgVersion then do
|
||||||
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
||||||
killThread mainThreadId
|
killApp appState
|
||||||
return Nothing
|
return Nothing
|
||||||
else do
|
else do
|
||||||
observer $ DBConnectedObs $ pgvFullName actualPgVersion
|
observer $ DBConnectedObs $ pgvFullName actualPgVersion
|
||||||
|
|||||||
@@ -42,11 +42,12 @@ runClientCommand conf CmdReady = Client.ready conf
|
|||||||
-- | Run postgrest with command
|
-- | Run postgrest with command
|
||||||
runAppCommand :: AppConfig -> RunCommand -> IO ()
|
runAppCommand :: AppConfig -> RunCommand -> IO ()
|
||||||
runAppCommand conf@AppConfig{..} runCmd = do
|
runAppCommand conf@AppConfig{..} runCmd = do
|
||||||
|
mainThreadId <- myThreadId
|
||||||
-- Per https://github.com/PostgREST/postgrest/issues/268, we want to
|
-- Per https://github.com/PostgREST/postgrest/issues/268, we want to
|
||||||
-- explicitly close the connections to PostgreSQL on shutdown.
|
-- explicitly close the connections to PostgreSQL on shutdown.
|
||||||
-- 'AppState.destroy' takes care of that.
|
-- 'AppState.destroy' takes care of that.
|
||||||
bracket
|
bracket
|
||||||
(AppState.init conf)
|
(AppState.init conf (killThread mainThreadId))
|
||||||
AppState.destroy
|
AppState.destroy
|
||||||
(\appState -> case runCmd of
|
(\appState -> case runCmd of
|
||||||
CmdDumpConfig -> do
|
CmdDumpConfig -> do
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ retryingListen appState nextDelay hasDbListenerBug = do
|
|||||||
when (isDbListenerBug err) $
|
when (isDbListenerBug err) $
|
||||||
observer DBListenBugCallQueryFix
|
observer DBListenBugCallQueryFix
|
||||||
unless configDbPoolAutomaticRecovery $
|
unless configDbPoolAutomaticRecovery $
|
||||||
killThread mainThreadId
|
AppState.killApp appState
|
||||||
|
|
||||||
-- retry the listener
|
-- retry the listener
|
||||||
delay <- readIORef nextDelay
|
delay <- readIORef nextDelay
|
||||||
@@ -96,7 +96,6 @@ retryingListen appState nextDelay hasDbListenerBug = do
|
|||||||
exitFailure
|
exitFailure
|
||||||
where
|
where
|
||||||
observer = AppState.getObserver appState
|
observer = AppState.getObserver appState
|
||||||
mainThreadId = AppState.getMainThreadId appState
|
|
||||||
oneSecondInMicro = 1_000_000
|
oneSecondInMicro = 1_000_000
|
||||||
maxDelay = 32
|
maxDelay = 32
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ main = do
|
|||||||
-- duplicate poolChan as a starting point
|
-- duplicate poolChan as a starting point
|
||||||
obsChan <- dupChan poolChan
|
obsChan <- dupChan poolChan
|
||||||
stateObsChan <- newObsChan obsChan
|
stateObsChan <- newObsChan obsChan
|
||||||
appState <- AppState.initWithPool pool config loggerState metricsState (Metrics.observationMetrics metricsState <> writeChan obsChan)
|
appState <- AppState.initWithPool pool config loggerState metricsState (Metrics.observationMetrics metricsState <> writeChan obsChan) mempty
|
||||||
AppState.putPgVersion appState actualPgVersion
|
AppState.putPgVersion appState actualPgVersion
|
||||||
AppState.putSchemaCache appState (Just sCache)
|
AppState.putSchemaCache appState (Just sCache)
|
||||||
return (SpecState appState metricsState stateObsChan, postgrest appState (pure ()))
|
return (SpecState appState metricsState stateObsChan, postgrest appState (pure ()))
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ main = do
|
|||||||
|
|
||||||
let
|
let
|
||||||
initApp sCache config = do
|
initApp sCache config = do
|
||||||
appState <- AppState.initWithPool pool config loggerState metricsState (Metrics.observationMetrics metricsState)
|
appState <- AppState.initWithPool pool config loggerState metricsState (Metrics.observationMetrics metricsState) mempty
|
||||||
AppState.putPgVersion appState actualPgVersion
|
AppState.putPgVersion appState actualPgVersion
|
||||||
AppState.putSchemaCache appState (Just sCache)
|
AppState.putSchemaCache appState (Just sCache)
|
||||||
return ((), postgrest appState (pure ()))
|
return ((), postgrest appState (pure ()))
|
||||||
|
|||||||
Reference in New Issue
Block a user