refactor: do not open a TCP connection in liveness check

This change introduces a new way to perform liveness check - instead of trying to open a TCP connection to main server, we check if main socket is listening and if main server accept loop thread is alive.

Opening a TCP connection in liveness check was problematic because:
* it used available file descriptors which might have been a problem under load
* made liveness check unreliable when multiple PostgREST instances are available on the same port (eg. using SO_REUSEPORT)
This commit is contained in:
Michał Kłeczek
2026-07-10 12:45:59 -05:00
committed by Steve Chavez
parent 0bda2bcdff
commit 4fc47754c2
2 changed files with 27 additions and 24 deletions
+25 -23
View File
@@ -10,6 +10,7 @@ Some of its functionality includes:
- Content Negotiation
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
@@ -19,8 +20,9 @@ module PostgREST.App
, run
) where
import GHC.Conc (ThreadStatus (..), threadStatus)
import GHC.IO.Exception (IOErrorType (..))
import GHC.Weak
import System.IO.Error (ioeGetErrorType)
import Control.Monad.Except (liftEither)
@@ -71,14 +73,14 @@ import qualified Data.Text as T
import qualified Network.HTTP.Types as HTTP
import Network.HTTP.Types.Header (hVary)
import qualified Network.Socket as NS
import Network.Socket.ByteString (send)
import PostgREST.Unix (createAndBindDomainSocket)
import System.Posix.Types (FileMode)
import Protolude hiding (Handler)
import Protolude hiding (Handler)
import System.Directory (doesPathExist)
run :: AppState -> IO ()
run appState = do
run :: AppState -> Weak ThreadId -> IO ()
run appState mainThreadIdRef = do
conf <- AppState.getConfig appState
mainSocketRef <- newIORef Nothing
@@ -92,7 +94,7 @@ run appState = do
ensureSocketClosed =<< readIORef mainSocketRef
Unix.installSignalHandlers observer closeSockets (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
Admin.runAdmin appState adminSocket (checkMainAppLive (readIORef mainSocketRef)) (serverSettings conf)
Admin.runAdmin appState adminSocket (checkMainAppLive (readIORef mainSocketRef) mainThreadIdRef) (serverSettings conf)
Listener.runListener appState
@@ -297,21 +299,21 @@ initAdminServerSocket AppConfig{..} =
configAdminServerUnixSocket configAdminServerUnixSocketMode
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
checkMainAppLive :: IO (Maybe NS.Socket) -> Weak ThreadId -> IO Bool
checkMainAppLive getMainSocket mainThreadIdRef =
handle (\(_ :: IOException) -> pure False) $
checkMainThread <&&> checkSocket
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
checkSocket = getMainSocket >>=
maybe (pure False)
(NS.getSocketName >=> \case
-- in case of unix socket, check if it still exists
NS.SockAddrUnix fp -> doesPathExist fp
_ -> pure True)
checkMainThread = deRefWeak mainThreadIdRef >>=
maybe (pure False)
(fmap isRunning . threadStatus)
isRunning = \case
ThreadRunning -> True
ThreadBlocked _ -> True
_ -> False
+2 -1
View File
@@ -43,6 +43,7 @@ runClientCommand conf CmdReady = Client.ready conf
runAppCommand :: AppConfig -> RunCommand -> IO ()
runAppCommand conf@AppConfig{..} runCmd = do
mainThreadId <- myThreadId
mainThreadIdRef <- mkWeakThreadId mainThreadId
-- Per https://github.com/PostgREST/postgrest/issues/268, we want to
-- explicitly close the connections to PostgreSQL on shutdown.
-- 'AppState.destroy' takes care of that.
@@ -56,7 +57,7 @@ runAppCommand conf@AppConfig{..} runCmd = do
CmdDumpSchema -> do
when configDbConfig $ AppState.readInDbConfig True appState
putStrLn =<< dumpSchema appState
CmdRun -> App.run appState)
CmdRun -> App.run appState mainThreadIdRef)
-- | Dump SchemaCache schema to JSON
dumpSchema :: AppState -> IO LBS.ByteString