add: use SO_REUSEPORT on platform supporting it

This commit is contained in:
Michał Kłeczek
2026-07-15 11:51:21 -05:00
committed by Steve Chavez
parent de19b04fe4
commit c297d051dc
22 changed files with 118 additions and 11 deletions
+19 -6
View File
@@ -68,7 +68,9 @@ import PostgREST.Version (docsVersion, prettyVersion)
import Control.Monad.Writer
import qualified Data.ByteString.Char8 as BS
import qualified Data.List as L
import Data.Streaming.Network (bindPortTCP)
import Data.Streaming.Network (HostPreference,
bindPortGenEx,
bindPortTCP)
import qualified Data.Text as T
import qualified Network.HTTP.Types as HTTP
import Network.HTTP.Types.Header (hVary, hWarning)
@@ -81,7 +83,7 @@ import System.Directory (doesPathExist)
run :: AppState -> Weak ThreadId -> IO ()
run appState mainThreadIdRef = do
conf <- AppState.getConfig appState
conf@AppConfig{configServerReusePort} <- AppState.getConfig appState
mainSocketRef <- newIORef Nothing
let setMainSocketRef = atomicWriteIORef mainSocketRef . Just
@@ -101,7 +103,10 @@ run appState mainThreadIdRef = do
-- Kick off and wait for the initial SchemaCache load before creating the
-- main API socket.
AppState.schemaCacheLoader appState
AppState.waitForSchemaCacheInit appState
if configServerReusePort then
AppState.waitForSchemaCacheLoaded appState
else
AppState.waitForSchemaCacheInit appState
bracket (initServerSocket conf) NS.close $ \mainSocket -> do
@@ -296,11 +301,11 @@ addRetryHint delay response = do
isServiceUnavailable :: Wai.Response -> Bool
isServiceUnavailable response = Wai.responseStatus response == HTTP.status503
initSocket :: (Applicative f, Traversable f) => Maybe String -> FileMode -> Text -> f Int -> IO (f NS.Socket)
initSocket unixSocket unixSocketMode tcpHost tcpPort =
initSocket :: (Applicative f, Traversable f) => Maybe String -> FileMode -> Text -> f Int -> (Int -> HostPreference -> IO NS.Socket) -> IO (f NS.Socket)
initSocket unixSocket unixSocketMode tcpHost tcpPort bindTCP =
maybe initTCPSocket initDomainSocket unixSocket
where
initTCPSocket = traverse (`bindPortTCP` (fromString $ T.unpack tcpHost)) tcpPort
initTCPSocket = traverse (`bindTCP` (fromString $ T.unpack tcpHost)) tcpPort
-- I'm not using `streaming-commons`' bindPath function here because it's not defined for Windows,
-- but we need to have runtime error if we try to use it in Windows, not compile time error
initDomainSocket = fmap pure . (`createAndBindDomainSocket` unixSocketMode)
@@ -310,12 +315,20 @@ initServerSocket AppConfig{..} =
runIdentity <$> initSocket
configServerUnixSocket configServerUnixSocketMode
configServerHost (pure configServerPort)
(if configServerReusePort then bindPortTCPWithReusePort else bindPortTCP)
initAdminServerSocket :: AppConfig -> IO (Maybe NS.Socket)
initAdminServerSocket AppConfig{..} =
initSocket
configAdminServerUnixSocket configAdminServerUnixSocketMode
configAdminServerHost configAdminServerPort
bindPortTCP
bindPortTCPWithReusePort :: Int -> HostPreference -> IO NS.Socket
bindPortTCPWithReusePort port hostPreference =
bindPortGenEx [(NS.ReusePort, 1)] NS.Stream port hostPreference >>= listenSocket
where
listenSocket sock = NS.listen sock (max 2048 NS.maxListenQueue) $> sock
checkMainAppLive :: IO (Maybe NS.Socket) -> Weak ThreadId -> IO Bool
checkMainAppLive getMainSocket mainThreadIdRef =
+4
View File
@@ -26,6 +26,7 @@ module PostgREST.AppState
, isLoaded
, isPending
, waitForSchemaCacheInit
, waitForSchemaCacheLoaded
) where
import qualified Data.ByteString.Char8 as BS
@@ -387,6 +388,9 @@ isSchemaCacheLoaded = atomically . (pure . fromMaybe False <=< tryReadTMVar) . g
waitForSchemaCacheInit :: AppState -> IO ()
waitForSchemaCacheInit = atomically . void . readTMVar . getSCStatusTMVar . stateSCacheStatus
waitForSchemaCacheLoaded :: AppState -> IO ()
waitForSchemaCacheLoaded = atomically . (check <=< readTMVar) . getSCStatusTMVar . stateSCacheStatus
-- | Reads the in-db config and reads the config file again
-- | We don't retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue.
readInDbConfig :: Bool -> AppState -> IO ()
+4
View File
@@ -117,6 +117,7 @@ data AppConfig = AppConfig
, configServerCorsAllowedOrigins :: [Text]
, configServerHost :: Text
, configServerPort :: Int
, configServerReusePort :: Bool
, configServerTraceHeader :: Maybe (CI.CI BS.ByteString)
, configServerTimingEnabled :: Bool
, configServerUnixSocket :: Maybe FilePath
@@ -204,6 +205,7 @@ toText conf =
,("server-cors-allowed-origins", q . T.intercalate "," . configServerCorsAllowedOrigins)
,("server-host", q . configServerHost)
,("server-port", show . configServerPort)
,("server-reuseport", T.toLower . show . configServerReusePort)
,("server-trace-header", q . T.decodeUtf8 . maybe mempty CI.original . configServerTraceHeader)
,("server-timing-enabled", T.toLower . show . configServerTimingEnabled)
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
@@ -323,6 +325,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> parseCORSAllowedOrigins "server-cors-allowed-origins"
<*> (defaultServerHost <$> optString "server-host")
<*> parseServerPort "server-port"
<*> (fromMaybe False <$> optBool "server-reuseport")
<*> (fmap (CI.mk . encodeUtf8) <$> optString "server-trace-header")
<*> (fromMaybe False <$> optBool "server-timing-enabled")
<*> (fmap T.unpack <$> optString "server-unix-socket")
@@ -787,6 +790,7 @@ exampleConfigFile = S.unlines
, ""
, "server-host = \"!4\""
, "server-port = 3000"
, "server-reuseport = false"
, ""
, "## Allow getting the request-response timing information through the `Server-Timing` header"
, "server-timing-enabled = false"