on master help sign in
Merge pull request #605 from yesodweb/602-better-async-timeoutthread

Better async exception behavior for timeouts #602

commitb11757ebc6ed5c3e2b882acc8d9149187d9aeffc authorKazu Yamamoto <kazu@iij.ad.jp> dateFri 27 Jan 2017, 12:14 (10 years ago) parentc7a53f9b63ec0e
8 files changed+46−15
warp-tls/Network/Wai/Handler/WarpTLS.hs+2−2 warp-tls/warp-tls.cabal+1−1 warp/ChangeLog.md+6 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+2−1 warp/Network/Wai/Handler/Warp/Run.hs+18−5 warp/Network/Wai/Handler/Warp/Timeout.hs+10−4 warp/Network/Wai/Handler/Warp/Types.hs+6−1 warp/warp.cabal+1−1
warp-tls/Network/Wai/Handler/WarpTLS.hs+2−2
@@ -345,6 +345,7 @@ httpOverTls TLSSettings{..} s bs0 params = do
345345 , connSendAll = sendall
346346 , connSendFile = sendfile
347347 , connClose = close'
348+ , connFree = freeBuffer writeBuf
348349 , connRecv = recv ref
349350 , connRecvBuf = recvBuf ref
350351 , connWriteBuffer = writeBuf
@@ -355,8 +356,7 @@ httpOverTls TLSSettings{..} s bs0 params = do
355356 sendfile fid offset len hook headers =
356357 readSendFile writeBuf bufferSize sendall fid offset len hook headers
357358
358− close' = freeBuffer writeBuf `finally`
359− void (tryIO $ TLS.bye ctx) `finally`
359+ close' = void (tryIO $ TLS.bye ctx) `finally`
360360 TLS.contextClose ctx
361361
362362 -- TLS version of recv with a cache for leftover input data.
warp-tls/warp-tls.cabal+1−1
@@ -21,7 +21,7 @@ Library
2121 Build-Depends: base >= 4 && < 5
2222 , bytestring >= 0.9
2323 , wai >= 3.2 && < 3.3
24− , warp >= 3.2 && < 3.3
24+ , warp >= 3.2.10 && < 3.3
2525 , data-default-class >= 0.0.1
2626 , tls >= 1.3.5
2727 , cryptonite >= 0.12
warp/ChangeLog.md+6
@@ -1,3 +1,9 @@
1+## 3.2.10
2+
3+* Add `connFree` to `Connection`
4+* Close socket connections on timeout triggered
5+* Timeout exceptions extend from `SomeAsyncException`
6+
17## 3.2.9
28
39* Fixing a space leak. [#586] https://github.com/yesodweb/wai/pull/586
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+2−1
@@ -236,7 +236,8 @@ worker :: Context -> S.Settings -> Application -> Responder -> T.Manager -> IO (
236236worker ctx@Context{inputQ,controlQ} set app responder tm = do
237237 sinfo <- newStreamInfo
238238 tcont <- newThreadContinue
239− E.bracket (T.registerKillThread tm) T.cancel $ go sinfo tcont
239+ let timeoutAction = return () -- cannot close the shared connection
240+ E.bracket (T.registerKillThread tm timeoutAction) T.cancel $ go sinfo tcont
240241 where
241242 go sinfo tcont th = do
242243 setThreadContinue tcont True
warp/Network/Wai/Handler/Warp/Run.hs+18−5
@@ -35,6 +35,7 @@ import qualified Network.Wai.Handler.Warp.FdCache as F
3535import qualified Network.Wai.Handler.Warp.FileInfoCache as I
3636import Network.Wai.Handler.Warp.HTTP2 (http2, isHTTP2)
3737import Network.Wai.Handler.Warp.Header
38+import Network.Wai.Handler.Warp.IORef
3839import Network.Wai.Handler.Warp.ReadInt
3940import Network.Wai.Handler.Warp.Recv
4041import Network.Wai.Handler.Warp.Request
@@ -63,7 +64,8 @@ socketConnection s = do
6364 connSendMany = Sock.sendMany s
6465 , connSendAll = sendall
6566 , connSendFile = sendFile s writeBuf bufferSize sendall
66− , connClose = close s >> freeBuffer writeBuf
67+ , connClose = close s
68+ , connFree = freeBuffer writeBuf
6769 , connRecv = receive s bufferPool
6870 , connRecvBuf = receiveBuf s
6971 , connWriteBuffer = writeBuf
@@ -262,6 +264,10 @@ fork :: Settings
262264 -> InternalInfo0
263265 -> IO ()
264266fork set mkConn addr app counter ii0 = settingsFork set $ \ unmask ->
267+ -- Allocate a new IORef indicating whether the connection has been
268+ -- closed, to avoid double-freeing a connection
269+ withClosedRef $ \ref ->
270+
265271 -- Run the connection maker to get a new connection, and ensure
266272 -- that the connection is closed. If the mkConn call throws an
267273 -- exception, we will leak the connection. If the mkConn call is
@@ -272,11 +278,14 @@ fork set mkConn addr app counter ii0 = settingsFork set $ \ unmask ->
272278 -- We grab the connection before registering timeouts since the
273279 -- timeouts will be useless during connection creation, due to the
274280 -- fact that async exceptions are still masked.
275− bracket mkConn closeConn $ \(conn, transport) ->
281+ bracket mkConn (\(conn, _) -> closeConn ref conn `finally` connFree conn)
282+ $ \(conn, transport) ->
276283
277284 -- We need to register a timeout handler for this thread, and
278− -- cancel that handler as soon as we exit.
279− bracket (T.registerKillThread (timeoutManager0 ii0)) T.cancel $ \th ->
285+ -- cancel that handler as soon as we exit. We additionally close
286+ -- the connection immediately in case the child thread catches the
287+ -- async exception or performs some long-running cleanup action.
288+ bracket (T.registerKillThread (timeoutManager0 ii0) (closeConn ref conn)) T.cancel $ \th ->
280289
281290 let ii1 = toInternalInfo1 ii0 th
282291 -- We now have fully registered a connection close handler
@@ -294,7 +303,11 @@ fork set mkConn addr app counter ii0 = settingsFork set $ \ unmask ->
294303 -- bracket with closeConn above ensures the connection is closed.
295304 when goingon $ serveConnection conn ii1 addr transport set app
296305 where
297− closeConn (conn, _transport) = connClose conn
306+ withClosedRef inner = newIORef False >>= inner
307+
308+ closeConn ref conn = do
309+ isClosed <- atomicModifyIORef' ref $ \x -> (True, x)
310+ unless isClosed $ connClose conn
298311
299312 onOpen adr = increase counter >> settingsOnOpen set adr
300313 onClose adr _ = decrease counter >> settingsOnClose set adr
warp/Network/Wai/Handler/Warp/Timeout.hs+10−4
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveDataTypeable #-}
23
34module Network.Wai.Handler.Warp.Timeout (
@@ -97,19 +98,24 @@ register mgr onTimeout = do
9798 return h
9899
99100-- | Registering a timeout action of killing this thread.
100−registerKillThread :: Manager -> IO Handle
101−registerKillThread m = do
101+registerKillThread :: Manager -> TimeoutAction -> IO Handle
102+registerKillThread m onTimeout = do
102103 -- If we hold ThreadId, the stack and data of the thread is leaked.
103104 -- If we hold Weak ThreadId, the stack is released. However, its
104105 -- data is still leaked probably because of a bug of GHC.
105106 -- So, let's just use ThreadId and release ThreadId by
106107 -- overriding the timeout action by "cancel".
107108 tid <- myThreadId
108− register m $ E.throwTo tid TimeoutThread
109+ -- First run the timeout action in case the child thread is masked.
110+ register m $ onTimeout `E.finally` E.throwTo tid TimeoutThread
109111
110112data TimeoutThread = TimeoutThread
111113 deriving Typeable
112−instance E.Exception TimeoutThread
114+instance E.Exception TimeoutThread where
115+#if MIN_VERSION_base(4,7,0)
116+ toException = E.asyncExceptionToException
117+ fromException = E.asyncExceptionFromException
118+#endif
113119instance Show TimeoutThread where
114120 show TimeoutThread = "Thread killed by Warp's timeout reaper"
115121
warp/Network/Wai/Handler/Warp/Types.hs+6−1
@@ -92,8 +92,13 @@ data Connection = Connection {
9292 , connSendAll :: ByteString -> IO ()
9393 -- | The sending function for files in HTTP/1.1.
9494 , connSendFile :: SendFile
95− -- | The connection closing function.
95+ -- | The connection closing function. Warp guarantees it will only be
96+ -- called once. Other functions (like 'connRecv') may be called after
97+ -- 'connClose' is called.
9698 , connClose :: IO ()
99+ -- | Free any buffers allocated. Warp guarantees it will only be
100+ -- called once, and no other functions will be called after it.
101+ , connFree :: IO ()
97102 -- | The connection receiving function. This returns "" for EOF.
98103 , connRecv :: Recv
99104 -- | The connection receiving function. This tries to fill the buffer.
warp/warp.cabal+1−1
@@ -1,5 +1,5 @@
11Name: warp
2−Version: 3.2.9
2+Version: 3.2.10
33Synopsis: A fast, light-weight web server for WAI applications.
44License: MIT
55License-file: LICENSE