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:
committed by
Steve Chavez
parent
0bda2bcdff
commit
4fc47754c2
+24
-22
@@ -10,6 +10,7 @@ Some of its functionality includes:
|
|||||||
- Content Negotiation
|
- Content Negotiation
|
||||||
-}
|
-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
@@ -19,8 +20,9 @@ module PostgREST.App
|
|||||||
, run
|
, run
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import GHC.Conc (ThreadStatus (..), threadStatus)
|
||||||
import GHC.IO.Exception (IOErrorType (..))
|
import GHC.IO.Exception (IOErrorType (..))
|
||||||
|
import GHC.Weak
|
||||||
import System.IO.Error (ioeGetErrorType)
|
import System.IO.Error (ioeGetErrorType)
|
||||||
|
|
||||||
import Control.Monad.Except (liftEither)
|
import Control.Monad.Except (liftEither)
|
||||||
@@ -71,14 +73,14 @@ 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)
|
||||||
|
|
||||||
import Protolude hiding (Handler)
|
import Protolude hiding (Handler)
|
||||||
|
import System.Directory (doesPathExist)
|
||||||
|
|
||||||
run :: AppState -> IO ()
|
run :: AppState -> Weak ThreadId -> IO ()
|
||||||
run appState = do
|
run appState mainThreadIdRef = do
|
||||||
conf <- AppState.getConfig appState
|
conf <- AppState.getConfig appState
|
||||||
|
|
||||||
mainSocketRef <- newIORef Nothing
|
mainSocketRef <- newIORef Nothing
|
||||||
@@ -92,7 +94,7 @@ run appState = do
|
|||||||
ensureSocketClosed =<< readIORef mainSocketRef
|
ensureSocketClosed =<< readIORef mainSocketRef
|
||||||
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 (checkMainAppLive (readIORef mainSocketRef)) (serverSettings conf)
|
Admin.runAdmin appState adminSocket (checkMainAppLive (readIORef mainSocketRef) mainThreadIdRef) (serverSettings conf)
|
||||||
|
|
||||||
Listener.runListener appState
|
Listener.runListener appState
|
||||||
|
|
||||||
@@ -297,21 +299,21 @@ initAdminServerSocket AppConfig{..} =
|
|||||||
configAdminServerUnixSocket configAdminServerUnixSocketMode
|
configAdminServerUnixSocket configAdminServerUnixSocketMode
|
||||||
configAdminServerHost configAdminServerPort
|
configAdminServerHost configAdminServerPort
|
||||||
|
|
||||||
checkMainAppLive :: IO (Maybe NS.Socket) -> IO Bool
|
checkMainAppLive :: IO (Maybe NS.Socket) -> Weak ThreadId -> IO Bool
|
||||||
checkMainAppLive getMainSocket =
|
checkMainAppLive getMainSocket mainThreadIdRef =
|
||||||
getMainSocket >>= maybe (pure False) (fmap isRight . reachMainApp)
|
handle (\(_ :: IOException) -> pure False) $
|
||||||
|
checkMainThread <&&> checkSocket
|
||||||
-- 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
|
where
|
||||||
sendEmpty sock = void $ send sock mempty
|
checkSocket = getMainSocket >>=
|
||||||
addrFamily (NS.SockAddrInet _ _) = NS.AF_INET
|
maybe (pure False)
|
||||||
addrFamily (NS.SockAddrInet6 {}) = NS.AF_INET6
|
(NS.getSocketName >=> \case
|
||||||
addrFamily (NS.SockAddrUnix _) = NS.AF_UNIX
|
-- 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
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ runClientCommand conf CmdReady = Client.ready conf
|
|||||||
runAppCommand :: AppConfig -> RunCommand -> IO ()
|
runAppCommand :: AppConfig -> RunCommand -> IO ()
|
||||||
runAppCommand conf@AppConfig{..} runCmd = do
|
runAppCommand conf@AppConfig{..} runCmd = do
|
||||||
mainThreadId <- myThreadId
|
mainThreadId <- myThreadId
|
||||||
|
mainThreadIdRef <- mkWeakThreadId mainThreadId
|
||||||
-- 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.
|
||||||
@@ -56,7 +57,7 @@ runAppCommand conf@AppConfig{..} runCmd = do
|
|||||||
CmdDumpSchema -> do
|
CmdDumpSchema -> do
|
||||||
when configDbConfig $ AppState.readInDbConfig True appState
|
when configDbConfig $ AppState.readInDbConfig True appState
|
||||||
putStrLn =<< dumpSchema appState
|
putStrLn =<< dumpSchema appState
|
||||||
CmdRun -> App.run appState)
|
CmdRun -> App.run appState mainThreadIdRef)
|
||||||
|
|
||||||
-- | Dump SchemaCache schema to JSON
|
-- | Dump SchemaCache schema to JSON
|
||||||
dumpSchema :: AppState -> IO LBS.ByteString
|
dumpSchema :: AppState -> IO LBS.ByteString
|
||||||
|
|||||||
Reference in New Issue
Block a user