packages feed

warp 1.3.7.4 → 1.3.7.5

raw patch · 3 files changed

+83/−16 lines, 3 filesdep ~base

Dependency ranges changed: base

Files

Network/Wai/Handler/Warp/Run.hs view
@@ -3,7 +3,7 @@  module Network.Wai.Handler.Warp.Run where -import Control.Concurrent (forkIO, threadDelay)+import Control.Concurrent (threadDelay, forkIOWithUnmask) import Control.Exception import Control.Monad (forever, when, unless, void) import Control.Monad.IO.Class (MonadIO, liftIO)@@ -33,6 +33,7 @@ #if WINDOWS import qualified Control.Concurrent.MVar as MV import Network.Socket (withSocketsDo)+import Control.Concurrent (forkIO) #else import System.Posix.IO (FdOption(CloseOnExec), setFdOption) import Network.Socket (fdSocket)@@ -141,23 +142,71 @@         _ -> Just <$> F.initialize (duration * 1000000) #endif     settingsBeforeMainLoop set-    mask $ \restore -> forever $ do++    -- Note that there is a thorough discussion of the exception safety of the+    -- following code at: https://github.com/yesodweb/wai/issues/146+    --+    -- We need to make sure of two things:+    --+    -- 1. Asynchronous exceptions are not blocked entirely in the main loop.+    --    Doing so would make it impossible to kill the Warp thread.+    --+    -- 2. Once a connection maker is received via getConnLoop, the connection+    --    is guaranteed to be closed, even in the presence of async exceptions.+    --+    -- Our approach is explained in the comments below.++    -- First mask all exceptions in the main loop. This is necessary to ensure+    -- that no async exception is throw between the call to getConnLoop and the+    -- registering of connClose.+    mask_ . forever $ do+        -- Allow async exceptions before receiving the next connection maker.         allowInterrupt++        -- getConnLoop will try to receive the next incoming request. It+        -- returns a /connection maker/, not a connection, since in some+        -- circumstances creating a working connection from a raw socket may be+        -- an expensive operation, and this expensive work should not be+        -- performed in the main event loop. An example of something expensive+        -- would be TLS negotiation.         (mkConn, addr) <- getConnLoop-        void . forkIO $ do-            th <- T.registerKillThread tm-            conn <- mkConn++        -- Fork a new worker thread for this connection maker, and ask for a+        -- function to unmask (i.e., allow async exceptions to be thrown).+        void . forkIOWithUnmask $ \unmask ->+            -- Run the connection maker to get a new connection, and ensure+            -- that the connection is closed. If the mkConn call throws an+            -- exception, we will leak the connection. If the mkConn call is+            -- vulnerable to attacks (e.g., Slowloris), we do nothing to+            -- protect the server. It is therefore vital that mkConn is well+            -- vetted.+            --+            -- We grab the connection before registering timeouts since the+            -- timeouts will be useless during connection creation, due to the+            -- fact that async exceptions are still masked.+            bracket mkConn connClose $ \conn ->++            -- We need to register a timeout handler for this thread, and+            -- cancel that handler as soon as we exit.+            bracket (T.registerKillThread tm) T.cancel $ \th -> #if SENDFILEFD-            let cleaner = Cleaner th fc+                let cleaner = Cleaner th fc #else-            let cleaner = Cleaner th+                let cleaner = Cleaner th #endif-            let serve = do-                    onOpen-                    restore $ serveConnection set cleaner port app conn addr-                    cleanup-                cleanup = connClose conn >> T.cancel th >> onClose-            handle onE (serve `onException` cleanup)+                    -- We now have fully registered a connection close handler+                    -- in the case of all exceptions, so it is safe to one+                    -- again allow async exceptions.+                 in unmask .+                    -- Call the user-supplied on exception code if any+                    -- exceptions are thrown.+                    handle onE .++                    -- Call the user-supplied code for connection open and close events+                    bracket_ onOpen onClose $++                    -- Actually serve this connection.+                    serveConnection set cleaner port app conn addr   where     -- FIXME: only IOEception is caught. What about other exceptions?     getConnLoop = getConn `catch` \(e :: IOException) -> do
Network/Wai/Handler/Warp/Timeout.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE UnboxedTuples, MagicHash #-} module Network.Wai.Handler.Warp.Timeout (     Manager   , Handle@@ -12,6 +14,15 @@   , dummyHandle   ) where +import System.Mem.Weak (deRefWeak)+#if MIN_VERSION_base(4,6,0)+import Control.Concurrent (mkWeakThreadId)+#else+import GHC.Weak (Weak (..))+import GHC.Conc.Sync (ThreadId (..))+import GHC.IO (IO (IO))+import GHC.Exts (mkWeak#)+#endif import Control.Concurrent (forkIO, threadDelay, myThreadId, killThread) import qualified Control.Exception as E import Control.Monad (forever, void)@@ -68,8 +79,15 @@  registerKillThread :: Manager -> IO Handle registerKillThread m = do-    tid <- myThreadId-    register m $ killThread tid+    wtid <- myThreadId >>= mkWeakThreadId+    register m $ deRefWeak wtid >>= maybe (return ()) killThread++#if !MIN_VERSION_base(4,6,0)+mkWeakThreadId :: ThreadId -> IO (Weak ThreadId)+mkWeakThreadId t@(ThreadId t#) = IO $ \s ->+   case mkWeak# t# t Nothing s of+      (# s1, w #) -> (# s1, Weak w #)+#endif  tickle, pause, resume, cancel :: Handle -> IO () tickle (Handle _ iactive) = I.writeIORef iactive Active
warp.cabal view
@@ -1,5 +1,5 @@ Name:                warp-Version:             1.3.7.4+Version:             1.3.7.5 Synopsis:            A fast, light-weight web server for WAI applications. License:             MIT License-file:        LICENSE