refactor: is ready Admin logic to AppState
This commit is contained in:
committed by
Steve Chavez
parent
1374178f27
commit
1b584f7e9c
+8
-12
@@ -5,7 +5,6 @@ module PostgREST.Admin
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Hasql.Session as SQL
|
|
||||||
import qualified Network.HTTP.Types.Status as HTTP
|
import qualified Network.HTTP.Types.Status as HTTP
|
||||||
import qualified Network.Wai as Wai
|
import qualified Network.Wai as Wai
|
||||||
import qualified Network.Wai.Handler.Warp as Warp
|
import qualified Network.Wai.Handler.Warp as Warp
|
||||||
@@ -28,28 +27,25 @@ import qualified PostgREST.Config as Config
|
|||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
runAdmin :: AppConfig -> AppState -> Warp.Settings -> IO ()
|
runAdmin :: AppState -> Warp.Settings -> IO ()
|
||||||
runAdmin conf@AppConfig{configAdminServerPort} appState settings =
|
runAdmin appState settings = do
|
||||||
|
AppConfig{configAdminServerPort} <- AppState.getConfig appState
|
||||||
whenJust (AppState.getSocketAdmin appState) $ \adminSocket -> do
|
whenJust (AppState.getSocketAdmin appState) $ \adminSocket -> do
|
||||||
observer $ AdminStartObs configAdminServerPort
|
observer $ AdminStartObs configAdminServerPort
|
||||||
void . forkIO $ Warp.runSettingsSocket settings adminSocket adminApp
|
void . forkIO $ Warp.runSettingsSocket settings adminSocket adminApp
|
||||||
where
|
where
|
||||||
adminApp = admin appState conf
|
adminApp = admin appState
|
||||||
observer = AppState.getObserver appState
|
observer = AppState.getObserver appState
|
||||||
|
|
||||||
-- | PostgREST admin application
|
-- | PostgREST admin application
|
||||||
admin :: AppState.AppState -> AppConfig -> Wai.Application
|
admin :: AppState.AppState -> Wai.Application
|
||||||
admin appState appConfig req respond = do
|
admin appState req respond = do
|
||||||
isMainAppReachable <- isRight <$> reachMainApp (AppState.getSocketREST appState)
|
isMainAppReachable <- isRight <$> reachMainApp (AppState.getSocketREST appState)
|
||||||
isSchemaCacheLoaded <- AppState.getSchemaCacheLoaded appState
|
isLoaded <- AppState.isLoaded appState
|
||||||
isConnectionUp <-
|
|
||||||
if configDbChannelEnabled appConfig
|
|
||||||
then AppState.getIsListenerOn appState
|
|
||||||
else isRight <$> AppState.usePool appState (SQL.sql "SELECT 1")
|
|
||||||
|
|
||||||
case Wai.pathInfo req of
|
case Wai.pathInfo req of
|
||||||
["ready"] ->
|
["ready"] ->
|
||||||
respond $ Wai.responseLBS (if isMainAppReachable && isConnectionUp && isSchemaCacheLoaded then HTTP.status200 else HTTP.status503) [] mempty
|
respond $ Wai.responseLBS (if isMainAppReachable && isLoaded then HTTP.status200 else HTTP.status503) [] mempty
|
||||||
["live"] ->
|
["live"] ->
|
||||||
respond $ Wai.responseLBS (if isMainAppReachable then HTTP.status200 else HTTP.status503) [] mempty
|
respond $ Wai.responseLBS (if isMainAppReachable then HTTP.status200 else HTTP.status503) [] mempty
|
||||||
["config"] -> do
|
["config"] -> do
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ run appState = do
|
|||||||
-- reload schema cache + config on NOTIFY
|
-- reload schema cache + config on NOTIFY
|
||||||
AppState.runListener conf appState
|
AppState.runListener conf appState
|
||||||
|
|
||||||
Admin.runAdmin conf appState (serverSettings conf)
|
Admin.runAdmin appState (serverSettings conf)
|
||||||
|
|
||||||
let app = postgrest configLogLevel appState (AppState.connectionWorker appState)
|
let app = postgrest configLogLevel appState (AppState.connectionWorker appState)
|
||||||
|
|
||||||
|
|||||||
+60
-50
@@ -9,7 +9,6 @@ module PostgREST.AppState
|
|||||||
, destroy
|
, destroy
|
||||||
, getConfig
|
, getConfig
|
||||||
, getSchemaCache
|
, getSchemaCache
|
||||||
, getIsListenerOn
|
|
||||||
, getMainThreadId
|
, getMainThreadId
|
||||||
, getPgVersion
|
, getPgVersion
|
||||||
, getRetryNextIn
|
, getRetryNextIn
|
||||||
@@ -17,7 +16,6 @@ module PostgREST.AppState
|
|||||||
, getJwtCache
|
, getJwtCache
|
||||||
, getSocketREST
|
, getSocketREST
|
||||||
, getSocketAdmin
|
, getSocketAdmin
|
||||||
, getSchemaCacheLoaded
|
|
||||||
, init
|
, init
|
||||||
, initSockets
|
, initSockets
|
||||||
, initWithPool
|
, initWithPool
|
||||||
@@ -28,6 +26,7 @@ module PostgREST.AppState
|
|||||||
, connectionWorker
|
, connectionWorker
|
||||||
, runListener
|
, runListener
|
||||||
, getObserver
|
, getObserver
|
||||||
|
, isLoaded
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -90,14 +89,14 @@ data AppState = AppState
|
|||||||
, statePgVersion :: IORef PgVersion
|
, statePgVersion :: IORef PgVersion
|
||||||
-- | No schema cache at the start. Will be filled in by the connectionWorker
|
-- | No schema cache at the start. Will be filled in by the connectionWorker
|
||||||
, stateSchemaCache :: IORef (Maybe SchemaCache)
|
, stateSchemaCache :: IORef (Maybe SchemaCache)
|
||||||
-- | If schema cache is loaded
|
-- | The schema cache status
|
||||||
, stateSchemaCacheLoaded :: IORef Bool
|
, stateSCacheStatus :: IORef SchemaCacheStatus
|
||||||
|
-- | The connection status
|
||||||
|
, stateConnStatus :: IORef ConnectionStatus
|
||||||
-- | starts the connection worker with a debounce
|
-- | starts the connection worker with a debounce
|
||||||
, debouncedConnectionWorker :: IO ()
|
, debouncedConnectionWorker :: IO ()
|
||||||
-- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker.
|
-- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker.
|
||||||
, stateListener :: MVar ()
|
, stateListener :: MVar ()
|
||||||
-- | State of the LISTEN channel, used for the admin server checks
|
|
||||||
, stateIsListenerOn :: IORef Bool
|
|
||||||
-- | 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
|
||||||
@@ -118,6 +117,20 @@ data AppState = AppState
|
|||||||
, stateMetrics :: Metrics.MetricsState
|
, stateMetrics :: Metrics.MetricsState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- | Schema cache status
|
||||||
|
data SchemaCacheStatus
|
||||||
|
= SCLoaded
|
||||||
|
| SCPending
|
||||||
|
| SCFatalFail
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
-- | Current database connection status
|
||||||
|
data ConnectionStatus
|
||||||
|
= ConnEstablished
|
||||||
|
| ConnPending
|
||||||
|
| ConnFatalFail Text
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
type AppSockets = (NS.Socket, Maybe NS.Socket)
|
type AppSockets = (NS.Socket, Maybe NS.Socket)
|
||||||
|
|
||||||
|
|
||||||
@@ -138,10 +151,10 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
|
|||||||
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
|
||||||
<*> newIORef Nothing
|
<*> newIORef Nothing
|
||||||
<*> newIORef False
|
<*> newIORef SCPending
|
||||||
|
<*> newIORef ConnPending
|
||||||
<*> pure (pure ())
|
<*> pure (pure ())
|
||||||
<*> newEmptyMVar
|
<*> newEmptyMVar
|
||||||
<*> newIORef False
|
|
||||||
<*> newIORef conf
|
<*> newIORef conf
|
||||||
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
<*> myThreadId
|
<*> myThreadId
|
||||||
@@ -286,29 +299,33 @@ waitListener = takeMVar . stateListener
|
|||||||
signalListener :: AppState -> IO ()
|
signalListener :: AppState -> IO ()
|
||||||
signalListener appState = void $ tryPutMVar (stateListener appState) ()
|
signalListener appState = void $ tryPutMVar (stateListener appState) ()
|
||||||
|
|
||||||
getIsListenerOn :: AppState -> IO Bool
|
isConnEstablished :: AppState -> IO Bool
|
||||||
getIsListenerOn = readIORef . stateIsListenerOn
|
isConnEstablished x = do
|
||||||
|
conf <- getConfig x
|
||||||
|
if configDbChannelEnabled conf
|
||||||
|
then do -- if the listener is enabled, we can be sure the connection status is always up to date
|
||||||
|
st <- readIORef $ stateConnStatus x
|
||||||
|
return $ st == ConnEstablished
|
||||||
|
else -- otherwise the only way to check the connection is to make a query
|
||||||
|
isRight <$> usePool x (SQL.sql "SELECT 1")
|
||||||
|
|
||||||
putIsListenerOn :: AppState -> Bool -> IO ()
|
isLoaded :: AppState -> IO Bool
|
||||||
putIsListenerOn = atomicWriteIORef . stateIsListenerOn
|
isLoaded x = do
|
||||||
|
scacheStatus <- readIORef $ stateSCacheStatus x
|
||||||
|
connEstablished <- isConnEstablished x
|
||||||
|
return $ scacheStatus == SCLoaded && connEstablished
|
||||||
|
|
||||||
getSchemaCacheLoaded :: AppState -> IO Bool
|
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
|
||||||
getSchemaCacheLoaded = readIORef . stateSchemaCacheLoaded
|
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
|
||||||
|
|
||||||
putSchemaCacheLoaded :: AppState -> Bool -> IO ()
|
putConnStatus :: AppState -> ConnectionStatus -> IO ()
|
||||||
putSchemaCacheLoaded = atomicWriteIORef . stateSchemaCacheLoaded
|
putConnStatus = atomicWriteIORef . stateConnStatus
|
||||||
|
|
||||||
getObserver :: AppState -> ObservationHandler
|
getObserver :: AppState -> ObservationHandler
|
||||||
getObserver = stateObserver
|
getObserver = stateObserver
|
||||||
|
|
||||||
-- | Schema cache status
|
|
||||||
data SCacheStatus
|
|
||||||
= SCLoaded
|
|
||||||
| SCOnRetry
|
|
||||||
| SCFatalFail
|
|
||||||
|
|
||||||
-- | Load the SchemaCache by using a connection from the pool.
|
-- | Load the SchemaCache by using a connection from the pool.
|
||||||
loadSchemaCache :: AppState -> IO SCacheStatus
|
loadSchemaCache :: AppState -> IO SchemaCacheStatus
|
||||||
loadSchemaCache appState@AppState{stateObserver=observer} = do
|
loadSchemaCache appState@AppState{stateObserver=observer} = do
|
||||||
conf@AppConfig{..} <- getConfig appState
|
conf@AppConfig{..} <- getConfig appState
|
||||||
(resultTime, result) <-
|
(resultTime, result) <-
|
||||||
@@ -323,24 +340,17 @@ loadSchemaCache appState@AppState{stateObserver=observer} = do
|
|||||||
Nothing -> do
|
Nothing -> do
|
||||||
putSchemaCache appState Nothing
|
putSchemaCache appState Nothing
|
||||||
observer $ SchemaCacheNormalErrorObs e
|
observer $ SchemaCacheNormalErrorObs e
|
||||||
putSchemaCacheLoaded appState False
|
putSCacheStatus appState SCPending
|
||||||
return SCOnRetry
|
return SCPending
|
||||||
|
|
||||||
Right sCache -> do
|
Right sCache -> do
|
||||||
putSchemaCache appState $ Just sCache
|
putSchemaCache appState $ Just sCache
|
||||||
observer $ SchemaCacheQueriedObs resultTime
|
observer $ SchemaCacheQueriedObs resultTime
|
||||||
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
||||||
observer $ SchemaCacheLoadedObs t
|
observer $ SchemaCacheLoadedObs t
|
||||||
putSchemaCacheLoaded appState True
|
putSCacheStatus appState SCLoaded
|
||||||
return SCLoaded
|
return SCLoaded
|
||||||
|
|
||||||
-- | Current database connection status data ConnectionStatus
|
|
||||||
data ConnectionStatus
|
|
||||||
= NotConnected
|
|
||||||
| Connected PgVersion
|
|
||||||
| FatalConnectionError Text
|
|
||||||
deriving (Eq)
|
|
||||||
|
|
||||||
-- | The purpose of this worker is to obtain a healthy connection to pg and an
|
-- | The purpose of this worker is to obtain a healthy connection to pg and an
|
||||||
-- up-to-date schema cache(SchemaCache). This method is meant to be called
|
-- up-to-date schema cache(SchemaCache). This method is meant to be called
|
||||||
-- multiple times by the same thread, but does nothing if the previous
|
-- multiple times by the same thread, but does nothing if the previous
|
||||||
@@ -358,20 +368,19 @@ internalConnectionWorker appState@AppState{stateObserver=observer} = work
|
|||||||
work = do
|
work = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
observer DBConnectAttemptObs
|
observer DBConnectAttemptObs
|
||||||
connected <- establishConnection appState
|
connStatus <- establishConnection appState
|
||||||
case connected of
|
case connStatus of
|
||||||
FatalConnectionError reason ->
|
ConnFatalFail reason ->
|
||||||
-- Fatal error when connecting
|
-- Fatal error when connecting
|
||||||
observer (ExitFatalObs reason) >> killThread (getMainThreadId appState)
|
observer (ExitFatalObs reason) >> killThread (getMainThreadId appState)
|
||||||
NotConnected ->
|
ConnPending ->
|
||||||
-- Unreachable because establishConnection will keep trying to connect, unless disable-recovery is turned on
|
|
||||||
unless configDbPoolAutomaticRecovery
|
unless configDbPoolAutomaticRecovery
|
||||||
$ observer ExitDBNoRecoveryObs >> killThread (getMainThreadId appState)
|
$ observer ExitDBNoRecoveryObs >> killThread (getMainThreadId appState)
|
||||||
Connected actualPgVersion -> do
|
ConnEstablished -> do
|
||||||
-- Procede with initialization
|
-- Procede with initialization
|
||||||
putPgVersion appState actualPgVersion
|
|
||||||
when configDbChannelEnabled $
|
when configDbChannelEnabled $
|
||||||
signalListener appState
|
signalListener appState
|
||||||
|
actualPgVersion <- getPgVersion appState
|
||||||
observer (DBConnectedObs $ pgvFullName actualPgVersion)
|
observer (DBConnectedObs $ pgvFullName actualPgVersion)
|
||||||
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
||||||
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
||||||
@@ -381,7 +390,7 @@ internalConnectionWorker appState@AppState{stateObserver=observer} = work
|
|||||||
SCLoaded ->
|
SCLoaded ->
|
||||||
-- do nothing and proceed if the load was successful
|
-- do nothing and proceed if the load was successful
|
||||||
return ()
|
return ()
|
||||||
SCOnRetry ->
|
SCPending ->
|
||||||
-- retry reloading the schema cache
|
-- retry reloading the schema cache
|
||||||
work
|
work
|
||||||
SCFatalFail ->
|
SCFatalFail ->
|
||||||
@@ -415,23 +424,26 @@ establishConnection appState@AppState{stateObserver=observer} =
|
|||||||
observer $ ConnectionPgVersionErrorObs e
|
observer $ ConnectionPgVersionErrorObs e
|
||||||
case checkIsFatal e of
|
case checkIsFatal e of
|
||||||
Just reason ->
|
Just reason ->
|
||||||
return $ FatalConnectionError reason
|
return $ ConnFatalFail reason
|
||||||
Nothing ->
|
Nothing -> do
|
||||||
return NotConnected
|
putConnStatus appState ConnPending
|
||||||
|
return ConnPending
|
||||||
Right version ->
|
Right version ->
|
||||||
if version < minimumPgVersion then
|
if version < minimumPgVersion then
|
||||||
return . FatalConnectionError $
|
return . ConnFatalFail $
|
||||||
"Cannot run in this PostgreSQL version, PostgREST needs at least "
|
"Cannot run in this PostgreSQL version, PostgREST needs at least "
|
||||||
<> pgvName minimumPgVersion
|
<> pgvName minimumPgVersion
|
||||||
else
|
else do
|
||||||
return . Connected $ version
|
putConnStatus appState ConnEstablished
|
||||||
|
putPgVersion appState version
|
||||||
|
return ConnEstablished
|
||||||
|
|
||||||
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool
|
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool
|
||||||
shouldRetry rs isConnSucc = do
|
shouldRetry rs isConnSucc = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
let
|
let
|
||||||
delay = fromMaybe 0 (rsPreviousDelay rs) `div` backoffMicroseconds
|
delay = fromMaybe 0 (rsPreviousDelay rs) `div` backoffMicroseconds
|
||||||
itShould = NotConnected == isConnSucc && configDbPoolAutomaticRecovery
|
itShould = ConnPending == isConnSucc && configDbPoolAutomaticRecovery
|
||||||
when itShould $ observer $ ConnectionRetryObs delay
|
when itShould $ observer $ ConnectionRetryObs delay
|
||||||
when itShould $ putRetryNextIn appState delay
|
when itShould $ putRetryNextIn appState delay
|
||||||
return itShould
|
return itShould
|
||||||
@@ -503,7 +515,6 @@ listener appState@AppState{stateObserver=observer} conf@AppConfig{..} = do
|
|||||||
case dbOrError of
|
case dbOrError of
|
||||||
Right db -> do
|
Right db -> do
|
||||||
observer $ DBListenerStart dbChannel
|
observer $ DBListenerStart dbChannel
|
||||||
putIsListenerOn appState True
|
|
||||||
SQL.listen db $ SQL.toPgIdentifier dbChannel
|
SQL.listen db $ SQL.toPgIdentifier dbChannel
|
||||||
SQL.waitForNotifications handleNotification db
|
SQL.waitForNotifications handleNotification db
|
||||||
|
|
||||||
@@ -517,7 +528,6 @@ listener appState@AppState{stateObserver=observer} conf@AppConfig{..} = do
|
|||||||
handleFinally dbChannel True err = do
|
handleFinally dbChannel True err = do
|
||||||
-- if the thread dies, we try to recover
|
-- if the thread dies, we try to recover
|
||||||
observer $ DBListenerFailRecoverObs True dbChannel err
|
observer $ DBListenerFailRecoverObs True dbChannel err
|
||||||
putIsListenerOn appState False
|
|
||||||
-- assume the pool connection was also lost, call the connection worker
|
-- assume the pool connection was also lost, call the connection worker
|
||||||
connectionWorker appState
|
connectionWorker appState
|
||||||
-- retry the listener
|
-- retry the listener
|
||||||
|
|||||||
Reference in New Issue
Block a user