@@ -2,48 +2,70 @@ module PostgREST.Admin
22 ( runAdmin
33 ) where
44
5+ import Control.Monad.Extra (whenJust )
56import qualified Data.Aeson as JSON
7+ import Foreign.C.Error (Errno (.. ), eMFILE )
8+ import GHC.IO.Exception
69import qualified Network.HTTP.Types.Status as HTTP
10+ import Network.Socket hiding (addrFamily )
11+ import Network.Socket.ByteString
712import qualified Network.Wai as Wai
813import qualified Network.Wai.Handler.Warp as Warp
914
10- import Control.Monad.Extra (whenJust )
11- import Network.Socket hiding (addrFamily )
12- import Network.Socket.ByteString
13-
1415import PostgREST.AppState (AppState , getConfig )
1516import PostgREST.Config (AppConfig (.. ))
17+ import PostgREST.Debounce (makeDebouncer )
1618import PostgREST.MediaType (MediaType (.. ), toContentType )
1719import PostgREST.Metrics (metricsToText )
1820import PostgREST.Network (resolveSocketToAddress )
1921import PostgREST.Observation (Observation (.. ))
2022
2123import qualified PostgREST.AppState as AppState
2224
23- import qualified Network.Socket as NS
24- import Protolude
25+ import Protolude
2526
26- runAdmin :: AppState -> Maybe NS. Socket -> IO (Maybe NS. Socket ) -> Warp. Settings -> IO ()
27+ runAdmin :: AppState -> Maybe Socket -> IO (Maybe Socket ) -> Warp. Settings -> IO ()
2728runAdmin appState maybeAdminSocket getSocketREST settings = do
2829 conf <- getConfig appState
2930 whenJust maybeAdminSocket $ \ adminSocket -> do
3031 address <- resolveSocketToAddress adminSocket
32+ -- log EMFILE at most once every 10s
33+ logEMFILE <- makeDebouncer $ observer AdminServerAcceptEMFILEFailure *> threadDelay 10_000_000
34+ let
35+ safeAccept sock =
36+ -- TODO use catchNoPropagate and rethrowIO once
37+ -- we stop supporting GHC versions < 9.12.1
38+ accept sock `catch`
39+ \ e ->
40+ -- Keep accepting on eMFILE
41+ if isEMFILE e then do
42+ logEMFILE
43+ -- Backoff for 1ms so that we don't enter busy accept loop
44+ -- this should let the system recover fd's once the pressure is gone
45+ threadDelay 1_000
46+ safeAccept sock
47+ else
48+ throwIO e
49+ adminServerSettings config addr =
50+ settings
51+ & Warp. setAccept safeAccept
52+ & Warp. setBeforeMainLoop (observer $ AdminStartObs addr)
53+ & maybe identity Warp. setPort (configAdminServerPort config)
54+
3155 void . forkIO $ handle (onError adminSocket) $
3256 Warp. runSettingsSocket (adminServerSettings conf address) adminSocket adminApp
3357 where
3458 adminApp = admin appState getSocketREST
3559 observer = AppState. getObserver appState
36- adminServerSettings config addr=
37- settings
38- & Warp. setBeforeMainLoop (observer $ AdminStartObs addr)
39- & maybe identity Warp. setPort (configAdminServerPort config)
60+
61+ isEMFILE = (Just eMFILE == ) . fmap Errno . ioe_errno
4062
4163 onError adminSock ex = do
4264 observer $ AdminServerCrashedObs ex
43- NS. close adminSock -- we close the socket so request doesn't hang
65+ close adminSock -- we close the socket so request doesn't hang
4466
4567-- | PostgREST admin application
46- admin :: AppState. AppState -> IO (Maybe NS. Socket ) -> Wai. Application
68+ admin :: AppState. AppState -> IO (Maybe Socket ) -> Wai. Application
4769admin appState getSocketREST req respond = do
4870 isMainAppReachable <- getSocketREST >>= maybe (pure False ) (fmap isRight . reachMainApp)
4971 isLoaded <- AppState. isLoaded appState
0 commit comments