on master help sign in
Merge branch 'bugfix/accept-error-handling' of https://github.com/helium/wai into helium-bugfix/accept-error-handling
commit19a939ea77dab69f1fca8804bceec115c71f1c04 authorMichael Snoyman <michael@snoyman.com> dateTue 24 May 2016, 07:36 (10 years ago) parent247d672fce3103
3 files changed+43−16
warp/Network/Wai/Handler/Warp.hs+17 warp/Network/Wai/Handler/Warp/Run.hs+20−16 warp/Network/Wai/Handler/Warp/Settings.hs+6
warp/Network/Wai/Handler/Warp.hs+17
@@ -70,12 +70,14 @@ module Network.Wai.Handler.Warp (
7070 , setSlowlorisSize
7171 , setHTTP2Disabled
7272 , setLogger
73+ , setGracefulShutdownTimeout
7374 -- ** Getters
7475 , getPort
7576 , getHost
7677 , getOnOpen
7778 , getOnClose
7879 , getOnException
80+ , getGracefulShutdownTimeout
7981 -- ** Exception handler
8082 , defaultOnException
8183 , defaultShouldDisplayException
@@ -244,6 +246,12 @@ getOnClose = settingsOnClose
244246getOnException :: Settings -> Maybe Request -> SomeException -> IO ()
245247getOnException = settingsOnException
246248
249+-- | Get the graceful shutdown timeout
250+--
251+-- Since 3.2.8
252+getGracefulShutdownTimeout :: Settings -> Maybe Int
253+getGracefulShutdownTimeout = settingsGracefulShutdownTimeout
254+
247255-- | A code to install shutdown handler.
248256--
249257-- For instance, this code should set up a UNIX signal
@@ -347,6 +355,15 @@ setLogger :: (Request -> H.Status -> Maybe Integer -> IO ())
347355 -> Settings -> Settings
348356setLogger lgr y = y { settingsLogger = lgr }
349357
358+-- | Set the graceful shutdown timeout. A timeout of `Nothing' will
359+-- wait indefinitely, and a number, if provided, will be treated as seconds
360+-- to wait for requests to finish, before shutting down the server entirely.
361+--
362+-- Since 3.2.8
363+setGracefulShutdownTimeout :: Maybe Int
364+ -> Settings -> Settings
365+setGracefulShutdownTimeout time y = y { settingsGracefulShutdownTimeout = time }
366+
350367-- | Explicitly pause the slowloris timeout.
351368--
352369-- This is useful for cases where you partially consume a request body. For
warp/Network/Wai/Handler/Warp/Run.hs+20−16
@@ -13,7 +13,6 @@ module Network.Wai.Handler.Warp.Run where
1313import Control.Applicative ((<$>))
1414#endif
1515import Control.Arrow (first)
16−import Control.Concurrent (threadDelay)
1716import qualified Control.Concurrent as Conc (yield)
1817import Control.Exception as E
1918import Control.Monad (when, unless, void)
@@ -23,6 +22,8 @@ import Data.Char (chr)
2322import "iproute" Data.IP (toHostAddress, toHostAddress6)
2423import Data.IORef (IORef, newIORef, readIORef, writeIORef)
2524import Data.Streaming.Network (bindPortTCP)
25+import Foreign.C.Error (Errno(..), eCONNABORTED)
26+import GHC.IO.Exception (IOException(..))
2627import Network (sClose, Socket)
2728import Network.Socket (accept, withSocketsDo, SockAddr(SockAddrInet, SockAddrInet6), setSocketOption, SocketOption(..))
2829import qualified Network.Socket.ByteString as Sock
@@ -44,7 +45,7 @@ import qualified Network.Wai.Handler.Warp.Timeout as T
4445import Network.Wai.Handler.Warp.Types
4546import Network.Wai.Internal (ResponseReceived (ResponseReceived))
4647import System.Environment (getEnvironment)
47−import System.IO.Error (isFullErrorType, ioeGetErrorType)
48+import System.Timeout (timeout)
4849
4950#if WINDOWS
5051import Network.Wai.Handler.Warp.Windows
@@ -218,7 +219,7 @@ acceptConnection set getConnMaker app counter ii0 = do
218219 -- ensure that no async exception is throw between the call to
219220 -- acceptNewConnection and the registering of connClose.
220221 void $ mask_ acceptLoop
221− gracefulShutdown counter
222+ gracefulShutdown set counter
222223 where
223224 acceptLoop = do
224225 -- Allow async exceptions before receiving the next connection maker.
@@ -242,17 +243,14 @@ acceptConnection set getConnMaker app counter ii0 = do
242243 ex <- try getConnMaker
243244 case ex of
244245 Right x -> return $ Just x
245− Left e -> do
246− settingsOnException set Nothing $ toException e
247− if isFullErrorType (ioeGetErrorType e) then do
248− -- "resource exhausted (Too many open files)" may
249− -- happen by accept(). Wait a second hoping that
250− -- resource will be available.
251− threadDelay 1000000
252− acceptNewConnection
253− else
254− -- Assuming the listen socket is closed.
255− return Nothing
246+ Left e -> do
247+ let eConnAborted = getErrno eCONNABORTED
248+ getErrno (Errno cInt) = cInt
249+ if ioe_errno e == Just eConnAborted
250+ then acceptNewConnection
251+ else do
252+ settingsOnException set Nothing $ toException e
253+ return Nothing
256254
257255-- Fork a new worker thread for this connection maker, and ask for a
258256-- function to unmask (i.e., allow async exceptions to be thrown).
@@ -498,5 +496,11 @@ setSocketCloseOnExec _ = return ()
498496setSocketCloseOnExec socket = F.setFileCloseOnExec $ fromIntegral $ fdSocket socket
499497#endif
500498
501−gracefulShutdown :: Counter -> IO ()
502−gracefulShutdown counter = waitForZero counter
499+gracefulShutdown :: Settings -> Counter -> IO ()
500+gracefulShutdown set counter =
501+ case settingsGracefulShutdownTimeout set of
502+ Nothing ->
503+ waitForZero counter
504+ (Just seconds) ->
505+ void (timeout (seconds * microsPerSecond) (waitForZero counter))
506+ where microsPerSecond = 1000000
warp/Network/Wai/Handler/Warp/Settings.hs+6
@@ -101,6 +101,11 @@ data Settings = Settings
101101 -- ^ A log function. Default: no action.
102102 --
103103 -- Since 3.X.X.
104+ , settingsGracefulShutdownTimeout :: Maybe Int
105+ -- ^ An optional timeout to limit the time (in seconds) waiting for
106+ -- a graceful shutdown of the web server.
107+ --
108+ -- Since 3.2.8
104109 }
105110
106111-- | Specify usage of the PROXY protocol.
@@ -135,6 +140,7 @@ defaultSettings = Settings
135140 , settingsSlowlorisSize = 2048
136141 , settingsHTTP2Enabled = True
137142 , settingsLogger = \_ _ _ -> return ()
143+ , settingsGracefulShutdownTimeout = Nothing
138144 }
139145
140146-- | Apply the logic provided by 'defaultOnException' to determine if an