warp 3.4.13.1 → 3.4.14
raw patch · 4 files changed
+73/−16 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ChangeLog.md +8/−0
- Network/Wai/Handler/Warp/Counter.hs +11/−4
- Network/Wai/Handler/Warp/Run.hs +53/−11
- warp.cabal +1/−1
ChangeLog.md view
@@ -1,5 +1,13 @@ # ChangeLog for warp +## 3.4.14++* Important bugfix to not deadlock on empty file descriptors if the cause of+ the file descriptor exhaustion is outside of the server's control.+ (i.e. the server does not have any running connections and can't use a file+ descriptor to create the next connection)+ [#1084](https://github.com/yesodweb/wai/pull/1084)+ ## 3.4.13.1 * Bugfix to fall back to "blocking `recv`" when on Windows systems and when
Network/Wai/Handler/Warp/Counter.hs view
@@ -3,6 +3,7 @@ module Network.Wai.Handler.Warp.Counter ( Counter, newCounter,+ HasDecreased (..), waitForZero, increase, decrease,@@ -25,12 +26,18 @@ x <- readTVar var when (x > 0) retry -waitForDecreased :: Counter -> IO ()+data HasDecreased = HasDecreased | NoConnections+ deriving (Eq, Show)++waitForDecreased :: Counter -> IO HasDecreased waitForDecreased (Counter var) = do n0 <- atomically $ readTVar var- atomically $ do- n <- readTVar var- check (n < n0)+ if n0 <= 0+ then pure NoConnections+ else atomically $ do+ n <- readTVar var+ check (n < n0)+ pure HasDecreased increase :: Counter -> IO () increase (Counter var) = atomically $ modifyTVar' var $ \x -> x + 1
Network/Wai/Handler/Warp/Run.hs view
@@ -21,7 +21,7 @@ import qualified Control.Exception as E import qualified Data.ByteString as S import Data.Functor (($>))-import Data.IORef (newIORef, readIORef)+import Data.IORef (newIORef, readIORef, IORef, writeIORef) import Data.Streaming.Network (bindPortTCP) import Foreign.C.Error (Errno (..), eCONNABORTED, eMFILE) import GHC.Conc.Sync (labelThread, myThreadId)@@ -266,8 +266,9 @@ runSettingsConnectionMakerSecure oldSettings getConnMaker app = do settingsBeforeMainLoop oldSettings (ServerState{serverConnectionCounter}, newSettings) <- makeServerState oldSettings- withII newSettings $- acceptConnection newSettings getConnMaker app serverConnectionCounter+ withII newSettings $ \ii ->+ initFdExhaustionRef >>=+ acceptConnection newSettings getConnMaker app serverConnectionCounter ii -- | Running an action with internal info. --@@ -311,8 +312,12 @@ -> Application -> Counter -> InternalInfo+ -> IORef FdExhaustion+ -- ^ This ref will be used to "debounce" the call to 'settingsOnException'+ -- when we hit an 'IOError' with 'eMFILE' in the case that Warp is not+ -- the reason the file descriptors are exhausted. -> IO ()-acceptConnection set getConnMaker app counter ii = do+acceptConnection set getConnMaker app counter ii fdRef = do -- First mask all exceptions in acceptLoop. This is necessary to -- ensure that no async exception is throw between the call to -- acceptNewConnection and the registering of connClose.@@ -345,19 +350,44 @@ acceptNewConnection = do ex <- E.try getConnMaker case ex of- Right x -> return $ Just x+ Right x -> do+ -- Important to mark the exhaustion issue to be resolved+ -- when we get connections again.+ resetFdExhaustion fdRef+ return $ Just x Left e -> do let getErrno (Errno cInt) = cInt isErrno err = ioe_errno e == Just (getErrno err)- if | isErrno eCONNABORTED -> acceptNewConnection+ if | isErrno eCONNABORTED -> do+ -- Important to mark the exhaustion issue to be resolved+ resetFdExhaustion fdRef+ acceptNewConnection+ -- Keep in mind to reset the ref when anything other+ -- than this branch runs | isErrno eMFILE -> do- settingsOnException set Nothing $ E.toException e- waitForDecreased counter- acceptNewConnection+ handleFdExhaustion e+ acceptNewConnection | otherwise -> do- settingsOnException set Nothing $ E.toException e- return Nothing+ -- Maybe not important to mark the exhaustion issue+ -- as resolved here, but just for completeness' sake.+ resetFdExhaustion fdRef+ settingsOnException set Nothing $ E.toException e+ return Nothing + handleFdExhaustion e = do+ fdExhaustion <- readIORef fdRef+ -- If file descriptors are exhausted while Warp has+ -- no current connections, 'settingsOnException' would+ -- get called an enormous amount of times per second.+ when (fdExhaustion /= FdExhausted) $+ settingsOnException set Nothing $ E.toException e+ hasDecreased <- waitForDecreased counter+ -- If we get 'NoConnections', that means the file+ -- descriptor exhaustion is outside of our control.+ -- We flag it so that 'settingsOnException' doesn't get+ -- called until the exhaustion issue is resolved.+ when (hasDecreased == NoConnections) $ setFdExhaustion fdRef+ -- Fork a new worker thread for this connection maker, and ask for a -- function to unmask (i.e., allow async exceptions to be thrown). fork@@ -494,3 +524,15 @@ Nothing -> pure () Just ServerState{serverShuttingDown} -> writeShuttingDown serverShuttingDown True++data FdExhaustion = NoFdIssue | FdExhausted+ deriving (Eq, Show)++initFdExhaustionRef :: IO (IORef FdExhaustion)+initFdExhaustionRef = newIORef NoFdIssue++resetFdExhaustion :: IORef FdExhaustion -> IO ()+resetFdExhaustion = flip writeIORef NoFdIssue++setFdExhaustion :: IORef FdExhaustion -> IO ()+setFdExhaustion = flip writeIORef FdExhausted
warp.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: warp-version: 3.4.13.1+version: 3.4.14 license: MIT license-file: LICENSE maintainer: michael@snoyman.com