refactor: encapsulate how main application liveness check is done

This change makes Admin module independent from how liveness check is performed. It moves reachMainApp function to App module and makes Admin.runAdmin, instead of action to get the main socket, take an IO action checking liveness as a parameter.

Thanks to this, any change in how liveness check is performed and, more importantly, what data it uses, does not require changing multiple modules (Admin and App).
This commit is contained in:
Michał Kłeczek
2026-07-05 15:22:37 -05:00
committed by Steve Chavez
parent 0bd2821937
commit 06bda07db1
2 changed files with 30 additions and 27 deletions
+8 -25
View File
@@ -8,8 +8,6 @@ import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp as Warp
import Control.Monad.Extra (whenJust) import Control.Monad.Extra (whenJust)
import Network.Socket hiding (addrFamily)
import Network.Socket.ByteString
import PostgREST.AppState (AppState, getConfig) import PostgREST.AppState (AppState, getConfig)
import PostgREST.Config (AppConfig (..)) import PostgREST.Config (AppConfig (..))
@@ -23,15 +21,15 @@ import qualified PostgREST.AppState as AppState
import qualified Network.Socket as NS import qualified Network.Socket as NS
import Protolude import Protolude
runAdmin :: AppState -> Maybe NS.Socket -> IO (Maybe NS.Socket) -> Warp.Settings -> IO () runAdmin :: AppState -> Maybe NS.Socket -> IO Bool -> Warp.Settings -> IO ()
runAdmin appState maybeAdminSocket getSocketREST settings = do runAdmin appState maybeAdminSocket checkMainAppLive settings = do
conf <- getConfig appState conf <- getConfig appState
whenJust maybeAdminSocket $ \adminSocket -> do whenJust maybeAdminSocket $ \adminSocket -> do
address <- resolveSocketToAddress adminSocket address <- resolveSocketToAddress adminSocket
void . forkIO $ handle (onError adminSocket) $ void . forkIO $ handle (onError adminSocket) $
Warp.runSettingsSocket (adminServerSettings conf address) adminSocket adminApp Warp.runSettingsSocket (adminServerSettings conf address) adminSocket adminApp
where where
adminApp = admin appState getSocketREST adminApp = admin appState checkMainAppLive
observer = AppState.getObserver appState observer = AppState.getObserver appState
adminServerSettings config addr= adminServerSettings config addr=
settings settings
@@ -43,19 +41,19 @@ runAdmin appState maybeAdminSocket getSocketREST settings = do
NS.close adminSock -- we close the socket so request doesn't hang NS.close adminSock -- we close the socket so request doesn't hang
-- | PostgREST admin application -- | PostgREST admin application
admin :: AppState.AppState -> IO (Maybe NS.Socket) -> Wai.Application admin :: AppState.AppState -> IO Bool -> Wai.Application
admin appState getSocketREST req respond = do admin appState checkMainAppLive req respond = do
isMainAppReachable <- getSocketREST >>= maybe (pure False) (fmap isRight . reachMainApp) isMainAppLive <- checkMainAppLive
isLoaded <- AppState.isLoaded appState isLoaded <- AppState.isLoaded appState
isPending <- AppState.isPending appState isPending <- AppState.isPending appState
case Wai.pathInfo req of case Wai.pathInfo req of
["live"] -> ["live"] ->
respond $ Wai.responseLBS (if isMainAppReachable then HTTP.status200 else HTTP.status500) [] mempty respond $ Wai.responseLBS (if isMainAppLive then HTTP.status200 else HTTP.status500) [] mempty
["ready"] -> ["ready"] ->
let let
status | isPending = HTTP.status503 status | isPending = HTTP.status503
| not isMainAppReachable = HTTP.status500 | not isMainAppLive = HTTP.status500
| isLoaded = HTTP.status200 | isLoaded = HTTP.status200
| otherwise = HTTP.status500 | otherwise = HTTP.status500
in in
@@ -68,18 +66,3 @@ admin appState getSocketREST req respond = do
respond $ Wai.responseLBS HTTP.status200 [toContentType MTTextPlain] mets -- Content-Type is required for prometheus compliance respond $ Wai.responseLBS HTTP.status200 [toContentType MTTextPlain] mets -- Content-Type is required for prometheus compliance
_ -> _ ->
respond $ Wai.responseLBS HTTP.status404 [] mempty respond $ Wai.responseLBS HTTP.status404 [] mempty
-- Try to connect to the main app socket
-- Note that it doesn't even send a valid HTTP request, we just want to check that the main app is accepting connections
reachMainApp :: Socket -> IO (Either IOException ())
reachMainApp appSock = do
sockAddr <- getSocketName appSock
sock <- socket (addrFamily sockAddr) Stream defaultProtocol
try $ do
connect sock sockAddr
withSocketsDo $ bracket (pure sock) close sendEmpty
where
sendEmpty sock = void $ send sock mempty
addrFamily (SockAddrInet _ _) = AF_INET
addrFamily (SockAddrInet6 {}) = AF_INET6
addrFamily (SockAddrUnix _) = AF_UNIX
+21 -1
View File
@@ -72,6 +72,7 @@ import qualified Data.Text as T
import qualified Network.HTTP.Types as HTTP import qualified Network.HTTP.Types as HTTP
import Network.HTTP.Types.Header (hVary) import Network.HTTP.Types.Header (hVary)
import qualified Network.Socket as NS import qualified Network.Socket as NS
import Network.Socket.ByteString (send)
import PostgREST.Unix (createAndBindDomainSocket) import PostgREST.Unix (createAndBindDomainSocket)
import System.Posix.Types (FileMode) import System.Posix.Types (FileMode)
@@ -89,7 +90,7 @@ run appState = do
readIORef mainSocketRef >>= foldMap NS.close readIORef mainSocketRef >>= foldMap NS.close
Unix.installSignalHandlers observer closeSockets (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState) Unix.installSignalHandlers observer closeSockets (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
Admin.runAdmin appState adminSocket (readIORef mainSocketRef) (serverSettings conf) Admin.runAdmin appState adminSocket (checkMainAppLive (readIORef mainSocketRef)) (serverSettings conf)
Listener.runListener appState Listener.runListener appState
@@ -291,3 +292,22 @@ initAdminServerSocket AppConfig{..} =
initSocket initSocket
configAdminServerUnixSocket configAdminServerUnixSocketMode configAdminServerUnixSocket configAdminServerUnixSocketMode
configAdminServerHost configAdminServerPort configAdminServerHost configAdminServerPort
checkMainAppLive :: IO (Maybe NS.Socket) -> IO Bool
checkMainAppLive getMainSocket =
getMainSocket >>= maybe (pure False) (fmap isRight . reachMainApp)
-- Try to connect to the main app socket
-- Note that it doesn't even send a valid HTTP request, we just want to check that the main app is accepting connections
reachMainApp :: NS.Socket -> IO (Either IOException ())
reachMainApp appSock = do
sockAddr <- NS.getSocketName appSock
sock <- NS.socket (addrFamily sockAddr) NS.Stream NS.defaultProtocol
try $ do
NS.connect sock sockAddr
NS.withSocketsDo $ bracket (pure sock) NS.close sendEmpty
where
sendEmpty sock = void $ send sock mempty
addrFamily (NS.SockAddrInet _ _) = NS.AF_INET
addrFamily (NS.SockAddrInet6 {}) = NS.AF_INET6
addrFamily (NS.SockAddrUnix _) = NS.AF_UNIX