on master help sign in
Merging the branch to improve performance of HTTP/2.
commite28a458cf4a403f770fba8e43afbaa3202986cda authorKazu Yamamoto <kazu@iij.ad.jp> dateThu 24 Dec 2015, 16:34 (11 years ago) parent1239eff45170d1
6 files changed+284−238
warp/Network/Wai/Handler/Warp/HTTP2.hs+1−1 warp/Network/Wai/Handler/Warp/HTTP2/Manager.hs+18−7 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+21−15 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+191−188 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+33−14 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+20−13
warp/Network/Wai/Handler/Warp/HTTP2.hs+1−1
@@ -45,7 +45,7 @@ http2 conn ii addr transport settings readN app = do
4545 -- Sender
4646 -- frameSender is the main thread because it ensures to send
4747 -- a goway frame.
48− frameSender ctx conn ii settings `E.finally` do
48+ frameSender ctx conn ii settings mgr `E.finally` do
4949 clearContext ctx
5050 stop mgr
5151 killThread tid
warp/Network/Wai/Handler/Warp/HTTP2/Manager.hs+18−7
@@ -9,7 +9,8 @@ module Network.Wai.Handler.Warp.HTTP2.Manager (
99 , setAction
1010 , stop
1111 , spawnAction
12− , replaceWithAction
12+ , addMyId
13+ , deleteMyId
1314 ) where
1415
1516#if __GLASGOW_HASKELL__ < 709
@@ -29,7 +30,7 @@ import qualified Network.Wai.Handler.Warp.Timeout as T
2930
3031type Action = T.Manager -> IO ()
3132
32−data Command = Stop | Spawn | Replace ThreadId
33+data Command = Stop | Spawn | Add ThreadId | Delete ThreadId
3334
3435data Manager = Manager (TQueue Command) (IORef Action)
3536
@@ -48,9 +49,12 @@ start set = do
4849 go q !tset0 ref timmgr = do
4950 x <- atomically $ readTQueue q
5051 case x of
51− Stop -> kill tset0 >> T.killManager timmgr
52− Spawn -> next tset0
53− Replace oldtid -> next $ del oldtid tset0
52+ Stop -> kill tset0 >> T.killManager timmgr
53+ Spawn -> next tset0
54+ Add newtid -> let !tset = add newtid tset0
55+ in go q tset ref timmgr
56+ Delete oldtid -> let !tset = del oldtid tset0
57+ in go q tset ref timmgr
5458 where
5559 next tset = do
5660 action <- readIORef ref
@@ -67,8 +71,15 @@ stop (Manager q _) = atomically $ writeTQueue q Stop
6771spawnAction :: Manager -> IO ()
6872spawnAction (Manager q _) = atomically $ writeTQueue q Spawn
6973
70−replaceWithAction :: Manager -> ThreadId -> IO ()
71−replaceWithAction (Manager q _) tid = atomically $ writeTQueue q $ Replace tid
74+addMyId :: Manager -> IO ()
75+addMyId (Manager q _) = do
76+ tid <- myThreadId
77+ atomically $ writeTQueue q $ Add tid
78+
79+deleteMyId :: Manager -> IO ()
80+deleteMyId (Manager q _) = do
81+ tid <- myThreadId
82+ atomically $ writeTQueue q $ Delete tid
7283
7384----------------------------------------------------------------
7485
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+21−15
@@ -17,7 +17,7 @@ import Data.ByteString (ByteString)
1717import qualified Data.ByteString as BS
1818import Data.Maybe (isJust)
1919import Network.HTTP2
20−import Network.HTTP2.Priority
20+import Network.HTTP2.Priority (toPrecedence, delete, prepare)
2121import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
2222import Network.Wai.Handler.Warp.HTTP2.HPACK
2323import Network.Wai.Handler.Warp.HTTP2.Request
@@ -36,18 +36,18 @@ frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
3636 , continued
3737 , currentStreamId
3838 , inputQ
39− , outputQ
39+ , controlQ
4040 } = ctx
4141 sendGoaway e
4242 | Just (ConnectionError err msg) <- E.fromException e = do
4343 csid <- readIORef currentStreamId
4444 let !frame = goawayFrame csid err msg
45− enqueueControl outputQ 0 $ OGoaway frame
45+ enqueueControl controlQ $ CGoaway frame
4646 | otherwise = return ()
4747
4848 sendReset err sid = do
4949 let !frame = resetFrame err sid
50− enqueueControl outputQ 0 $ OFrame frame
50+ enqueueControl controlQ $ CFrame frame
5151
5252 loop :: Int -> IO ()
5353 loop !n
@@ -57,7 +57,7 @@ frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
5757 | otherwise = do
5858 hd <- recvN frameHeaderLength
5959 if BS.null hd then
60− enqueueControl outputQ 0 OFinish
60+ enqueueControl controlQ CFinish
6161 else do
6262 cont <- processStreamGuardingError $ decodeFrameHeader hd
6363 when cont $ loop (n + 1)
@@ -172,10 +172,13 @@ frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
172172
173173 consume = void . recvN
174174
175+initialFrame :: ByteString
176+initialFrame = settingsFrame id [(SettingsMaxConcurrentStreams,recommendedConcurrency)]
177+
175178----------------------------------------------------------------
176179
177180control :: FrameTypeId -> FrameHeader -> ByteString -> Context -> IO Bool
178−control FrameSettings header@FrameHeader{flags} bs Context{http2settings, outputQ} = do
181+control FrameSettings header@FrameHeader{flags} bs Context{http2settings, controlQ,firstSettings} = do
179182 SettingsFrame alist <- guardIt $ decodeSettingsFrame header bs
180183 case checkSettingsList alist of
181184 Just x -> E.throwIO x
@@ -183,19 +186,22 @@ control FrameSettings header@FrameHeader{flags} bs Context{http2settings, output
183186 unless (testAck flags) $ do
184187 modifyIORef' http2settings $ \old -> updateSettings old alist
185188 let !frame = settingsFrame setAck []
186− enqueueControl outputQ 0 $ OSettings frame alist
189+ sent <- readIORef firstSettings
190+ let !frame' = if sent then frame else BS.append initialFrame frame
191+ unless sent $ writeIORef firstSettings True
192+ enqueueControl controlQ $ CSettings frame' alist
187193 return True
188194
189−control FramePing FrameHeader{flags} bs Context{outputQ} =
195+control FramePing FrameHeader{flags} bs Context{controlQ} =
190196 if testAck flags then
191197 E.throwIO $ ConnectionError ProtocolError "the ack flag of this ping frame must not be set"
192198 else do
193199 let !frame = pingFrame bs
194− enqueueControl outputQ 0 $ OFrame frame
200+ enqueueControl controlQ $ CFrame frame
195201 return True
196202
197−control FrameGoAway _ _ Context{outputQ} = do
198− enqueueControl outputQ 0 OFinish
203+control FrameGoAway _ _ Context{controlQ} = do
204+ enqueueControl controlQ CFinish
199205 return False
200206
201207control FrameWindowUpdate header bs Context{connectionWindow} = do
@@ -258,7 +264,7 @@ stream FrameHeaders header@FrameHeader{flags} bs _ (Open (Body q)) _ = do
258264stream FrameData
259265 header@FrameHeader{flags,payloadLength,streamId}
260266 bs
261− Context{outputQ} s@(Open (Body q))
267+ Context{controlQ} s@(Open (Body q))
262268 Stream{streamNumber,streamBodyLength,streamContentLength} = do
263269 DataFrame body <- guardIt $ decodeDataFrame header bs
264270 let !endOfStream = testEndStream flags
@@ -269,7 +275,7 @@ stream FrameData
269275 let !frame1 = windowUpdateFrame 0 payloadLength
270276 !frame2 = windowUpdateFrame streamNumber payloadLength
271277 !frame = frame1 `BS.append` frame2
272− enqueueControl outputQ 0 $ OFrame frame
278+ enqueueControl controlQ $ CFrame frame
273279 atomically $ writeTQueue q body
274280 if endOfStream then do
275281 mcl <- readIORef streamContentLength
@@ -328,8 +334,8 @@ stream FramePriority header bs Context{outputQ,priorityTreeSize} s Stream{stream
328334 else do
329335 mx <- delete outputQ streamNumber oldpre
330336 case mx of
331− Nothing -> return ()
332− Just x -> enqueue outputQ streamNumber newpre x
337+ Nothing -> return ()
338+ Just out -> enqueueOutput outputQ out
333339 return s
334340
335341-- this ordering is important
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+191−188
@@ -9,23 +9,22 @@ import Control.Applicative
99#endif
1010import Control.Concurrent.STM
1111import qualified Control.Exception as E
12−import Control.Monad (unless, void, when)
12+import Control.Monad (void, when)
1313import qualified Data.ByteString as BS
1414import qualified Data.ByteString.Builder.Extra as B
15+import Data.Maybe (isNothing)
1516import Foreign.Ptr
1617import Network.HPACK (setLimitForEncoding)
1718import Network.HTTP2
18−import Network.HTTP2.Priority
19+import Network.HTTP2.Priority (isEmptySTM, dequeueSTM, Precedence)
1920import Network.Wai
2021import Network.Wai.Handler.Warp.Buffer
2122import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
2223import Network.Wai.Handler.Warp.HTTP2.HPACK
24+import Network.Wai.Handler.Warp.HTTP2.Manager (Manager)
2325import Network.Wai.Handler.Warp.HTTP2.Types
2426import Network.Wai.Handler.Warp.IORef
2527import qualified Network.Wai.Handler.Warp.Settings as S
26−#ifndef WINDOWS
27−import qualified Network.Wai.Handler.Warp.Timeout as T
28−#endif
2928import Network.Wai.Handler.Warp.Types
3029import Network.Wai.Internal (Response(..))
3130
@@ -34,6 +33,7 @@ import qualified System.IO as IO
3433#else
3534import Network.Wai.Handler.Warp.FdCache
3635import Network.Wai.Handler.Warp.SendFile (positionRead)
36+import qualified Network.Wai.Handler.Warp.Timeout as T
3737import System.Posix.IO (openFd, OpenFileFlags(..), defaultFileFlags, OpenMode(ReadOnly), closeFd)
3838import System.Posix.Types
3939#endif
@@ -46,25 +46,6 @@ data Leftover = LZero
4646
4747----------------------------------------------------------------
4848
49−unlessClosed :: Context -> Connection -> Stream -> IO () -> IO ()
50−unlessClosed ctx
51− Connection{connSendAll}
52− strm@Stream{streamState,streamNumber}
53− body = E.handle resetStream $ do
54− state <- readIORef streamState
55− unless (isClosed state) body
56− where
57− resetStream e = do
58− closed ctx strm (ResetByMe e)
59− let rst = resetFrame InternalError streamNumber
60− connSendAll rst
61−
62−getConnectionWindowSizeWhenReady :: Context -> IO WindowSize
63−getConnectionWindowSizeWhenReady Context{connectionWindow} = atomically $ do
64− w <- readTVar connectionWindow
65− check (w > 0)
66− return w
67−
6849getStreamWindowSize :: Stream -> IO WindowSize
6950getStreamWindowSize Stream{streamWindow} = atomically $ readTVar streamWindow
7051
@@ -78,187 +59,208 @@ waitStreaming tbq = do
7859 isEmpty <- isEmptyTBQueue tbq
7960 check (isEmpty == False)
8061
81−frameSender :: Context -> Connection -> InternalInfo -> S.Settings -> IO ()
82−frameSender ctx@Context{outputQ,connectionWindow,encodeDynamicTable}
62+data Switch = C Control
63+ | O (StreamId,Precedence,Output)
64+ | Flush
65+
66+frameSender :: Context -> Connection -> InternalInfo -> S.Settings -> Manager -> IO ()
67+frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
8368 conn@Connection{connWriteBuffer,connBufferSize,connSendAll}
84− ii settings = go `E.catch` ignore
69+ ii settings mgr = loop 0 `E.catch` ignore
8570 where
86− initialSettings = [(SettingsMaxConcurrentStreams,recommendedConcurrency)]
87− initialFrame = settingsFrame id initialSettings
88− bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
89− headerPayloadLim = connBufferSize - frameHeaderLength
90−
91− go = do
92− connSendAll initialFrame
93− loop
94−
95− -- ignoring the old priority because the value might be changed.
96− loop = dequeue outputQ >>= \(_sid,pre,out) -> switch out pre
97−
98− ignore :: E.SomeException -> IO ()
99− ignore _ = return ()
100−
101− switch OFinish _ = return ()
102− switch (OGoaway frame) _ = connSendAll frame
103− switch (OFrame frame) _ = do
104− connSendAll frame
105− loop
106− switch (OSettings frame alist) _ = do
71+ dequeue off = do
72+ isEmpty <- isEmptyTQueue controlQ
73+ if isEmpty then do
74+ w <- readTVar connectionWindow
75+ check (w > 0)
76+ emp <- isEmptySTM outputQ
77+ if emp then
78+ if off /= 0 then return Flush else retry
79+ else
80+ O <$> dequeueSTM outputQ
81+ else
82+ C <$> readTQueue controlQ
83+
84+ loop off = do
85+ x <- atomically $ dequeue off
86+ case x of
87+ C ctl -> do
88+ when (off /= 0) $ flushN off
89+ cont <- control ctl
90+ when cont $ loop 0
91+ O (_,pre,out) -> do
92+ let strm = outputStream out
93+ writeIORef (streamPrecedence strm) pre
94+ off' <- whenReadyOrEnqueueAgain out off $ output out off
95+ case off' of
96+ 0 -> loop 0
97+ _ | off' > 12288 -> flushN off' >> loop 0 -- fixme: hard-coding
98+ | otherwise -> loop off'
99+ Flush -> flushN off >> loop 0
100+
101+ control CFinish = return False
102+ control (CGoaway frame) = connSendAll frame >> return False
103+ control (CFrame frame) = connSendAll frame >> return True
104+ control (CSettings frame alist) = do
107105 connSendAll frame
108106 case lookup SettingsHeaderTableSize alist of
109107 Nothing -> return ()
110108 Just siz -> do
111109 dyntbl <- readIORef encodeDynamicTable
112110 setLimitForEncoding siz dyntbl
113− loop
114− switch out@(ONext strm curr binfo) pre = do
115− writeIORef (streamPrecedence strm) pre
116− whenReadyOrEnqueueAgain strm binfo out $ \sws -> do
117− cws <- getConnectionWindowSizeWhenReady ctx
118− let !lim = min cws sws
119− -- Data frame payload
120− Next datPayloadLen mnext <- curr lim
121− fillDataHeaderSend strm 0 datPayloadLen mnext
122− maybeEnqueueNext strm mnext binfo
123− loop
124− switch out@(OResponse strm rsp binfo) pre = do
125− writeIORef (streamPrecedence strm) pre
126− whenReadyOrEnqueueAgain strm binfo out $ \sws -> do
127− cws <- getConnectionWindowSizeWhenReady ctx
128− let !lim = min cws sws
129− -- Header frame and Continuation frame
130− let sid = streamNumber strm
131− endOfStream = case binfo of
132− Persist{} -> False
133− OneshotWithBody -> False
134− OneshotWithoutBody -> True
135− len <- headerContinue sid rsp endOfStream
136− let total = len + frameHeaderLength
137− case binfo of
138− OneshotWithBody -> do
139− -- Data frame payload
140− (off, _) <- sendHeadersIfNecessary total
141− let payloadOff = off + frameHeaderLength
142− Next datPayloadLen mnext <-
143− fillResponseBodyGetNext conn ii payloadOff lim rsp
144− fillDataHeaderSend strm total datPayloadLen mnext
145− maybeEnqueueNext strm mnext binfo
146− OneshotWithoutBody -> do
147− -- "closed" must be before "connSendAll". If not,
148− -- the context would be switched to the receiver,
149− -- resulting the inconsistency of concurrency.
150− closed ctx strm Finished
151− flushN total
152− Persist sq -> do
153− (off, needSend) <- sendHeadersIfNecessary total
154− let payloadOff = off + frameHeaderLength
155− Next datPayloadLen mnext <-
156− fillStreamBodyGetNext conn payloadOff lim sq strm
157− -- If no data was immediately available, avoid sending an
158− -- empty data frame.
159− if datPayloadLen > 0 then
160− fillDataHeaderSend strm total datPayloadLen mnext
161− else
162− when needSend $ flushN off
163− maybeEnqueueNext strm mnext binfo
164− loop
165−
166− whenReadyOrEnqueueAgain strm binfo out body =
167− unlessClosed ctx conn strm $ case binfo of
111+ return True
112+
113+ output (ONext strm binfo curr) off0 lim = do
114+ -- Data frame payload
115+ let !buf = connWriteBuffer `plusPtr` off0
116+ !siz = connBufferSize - off0
117+ Next datPayloadLen mnext <- curr buf siz lim
118+ off <- fillDataHeader strm off0 datPayloadLen mnext
119+ maybeEnqueueNext strm binfo mnext
120+ return off
121+
122+ output (OResponse strm binfo rsp) off0 lim = do
123+ -- Header frame and Continuation frame
124+ let sid = streamNumber strm
125+ endOfStream = case binfo of
126+ Persist{} -> False
127+ OneshotWithBody -> False
128+ OneshotWithoutBody -> True
129+ kvlen <- headerContinue sid rsp endOfStream off0
130+ off <- sendHeadersIfNecessary $ off0 + frameHeaderLength + kvlen
131+ case binfo of
132+ OneshotWithoutBody -> do
133+ closed ctx strm Finished
134+ return off
135+ OneshotWithBody -> do
136+ -- Data frame payload
137+ let payloadOff = off + frameHeaderLength
138+ Next datPayloadLen mnext <-
139+ fillResponseBodyGetNext conn ii payloadOff lim rsp
140+ off' <- fillDataHeader strm off datPayloadLen mnext
141+ maybeEnqueueNext strm binfo mnext
142+ return off'
143+ Persist sq -> do
144+ let payloadOff = off + frameHeaderLength
145+ Next datPayloadLen mnext <-
146+ fillStreamBodyGetNext conn payloadOff lim sq strm
147+ off' <- fillDataHeader strm off datPayloadLen mnext
148+ maybeEnqueueNext strm binfo mnext
149+ return off'
150+
151+ whenReadyOrEnqueueAgain out off body = E.handle resetStream $ do
152+ state <- readIORef $ streamState strm
153+ if isClosed state then
154+ return off
155+ else case binfo of
168156 Persist tbq -> checkStreaming tbq
169157 _ -> checkStreamWindowSize
170158 where
159+ strm = outputStream out
160+ binfo = outputBodyInfo out
171161 checkStreaming tbq = do
172162 isEmpty <- atomically $ isEmptyTBQueue tbq
173− if isEmpty then
174− enqueueWhenReady (waitStreaming tbq) outputQ out
163+ if isEmpty then do
164+ forkAndEnqueueWhenReady (waitStreaming tbq) outputQ out mgr
165+ return off
175166 else
176167 checkStreamWindowSize
177168 checkStreamWindowSize = do
178169 sws <- getStreamWindowSize strm
179170 if sws == 0 then do
180− enqueueWhenReady (waitStreamWindowSize strm) outputQ out
181− else
182− body sws
171+ forkAndEnqueueWhenReady (waitStreamWindowSize strm) outputQ out mgr
172+ return off
173+ else do
174+ cws <- atomically $ readTVar connectionWindow -- not 0
175+ let !lim = min cws sws
176+ body lim
177+ resetStream e = do
178+ closed ctx strm (ResetByMe e)
179+ let !rst = resetFrame InternalError $ streamNumber strm
180+ enqueueControl controlQ $ CFrame rst
181+ return off
183182
184183 -- Flush the connection buffer to the socket, where the first 'n' bytes of
185184 -- the buffer are filled.
186185 flushN :: Int -> IO ()
187186 flushN n = bufferIO connWriteBuffer n connSendAll
188187
189− headerContinue sid rsp endOfStream = do
188+ headerContinue sid rsp endOfStream off = do
190189 builder <- hpackEncodeHeader ctx ii settings rsp
191− (len, signal) <- B.runBuilder builder bufHeaderPayload headerPayloadLim
190+ let !offkv = off + frameHeaderLength
191+ let !bufkv = connWriteBuffer `plusPtr` offkv
192+ !limkv = connBufferSize - offkv
193+ (kvlen, signal) <- B.runBuilder builder bufkv limkv
192194 let flag0 = case signal of
193195 B.Done -> setEndHeader defaultFlags
194196 _ -> defaultFlags
195197 flag = if endOfStream then setEndStream flag0 else flag0
196− fillFrameHeader FrameHeaders len sid flag connWriteBuffer
197− continue sid len signal
198+ let buf = connWriteBuffer `plusPtr` off
199+ fillFrameHeader FrameHeaders kvlen sid flag buf
200+ continue sid kvlen signal
201+
202+ bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
203+ headerPayloadLim = connBufferSize - frameHeaderLength
198204
199− continue _ len B.Done = return len
200− continue sid len (B.More _ writer) = do
201− flushN $ len + frameHeaderLength
202− (len', signal') <- writer bufHeaderPayload headerPayloadLim
205+ continue _ kvlen B.Done = return kvlen
206+ continue sid kvlen (B.More _ writer) = do
207+ flushN $ kvlen + frameHeaderLength
208+ -- Now off is 0
209+ (kvlen', signal') <- writer bufHeaderPayload headerPayloadLim
203210 let flag = case signal' of
204211 B.Done -> setEndHeader defaultFlags
205212 _ -> defaultFlags
206− fillFrameHeader FrameContinuation len' sid flag connWriteBuffer
207− continue sid len' signal'
208− continue sid len (B.Chunk bs writer) = do
209− flushN $ len + frameHeaderLength
213+ fillFrameHeader FrameContinuation kvlen' sid flag connWriteBuffer
214+ continue sid kvlen' signal'
215+ continue sid kvlen (B.Chunk bs writer) = do
216+ flushN $ kvlen + frameHeaderLength
217+ -- Now off is 0
210218 let (bs1,bs2) = BS.splitAt headerPayloadLim bs
211− len' = BS.length bs1
219+ kvlen' = BS.length bs1
212220 void $ copy bufHeaderPayload bs1
213− fillFrameHeader FrameContinuation len' sid defaultFlags connWriteBuffer
221+ fillFrameHeader FrameContinuation kvlen' sid defaultFlags connWriteBuffer
214222 if bs2 == "" then
215− continue sid len' (B.More 0 writer)
223+ continue sid kvlen' (B.More 0 writer)
216224 else
217− continue sid len' (B.Chunk bs2 writer)
218−
219− -- True if the connection buffer has room for a 1-byte data frame.
220− canFitDataFrame total = total + frameHeaderLength < connBufferSize
225+ continue sid kvlen' (B.Chunk bs2 writer)
221226
222227 -- Re-enqueue the stream in the output queue.
223− maybeEnqueueNext :: Stream -> Maybe DynaNext -> BodyInfo -> IO ()
224− maybeEnqueueNext strm (Just next) binfo = do
225− let !out = ONext strm next binfo
226− enqueueOutput outputQ out
227− maybeEnqueueNext _ _ _ = return ()
228−
228+ maybeEnqueueNext :: Stream -> BodyInfo -> Maybe DynaNext -> IO ()
229+ maybeEnqueueNext _ _ Nothing = return ()
230+ maybeEnqueueNext strm binfo (Just next) = enqueueOutput outputQ out
231+ where
232+ !out = ONext strm binfo next
229233
230234 -- Send headers if there is not room for a 1-byte data frame, and return
231− -- the offset of the next frame's first header byte and whether the headers
232− -- still need to be sent.
233− sendHeadersIfNecessary total
234− | canFitDataFrame total = return (total, True)
235− | otherwise = do
236− flushN total
237− return (0, False)
238−
239− fillDataHeaderSend strm otherLen datPayloadLen mnext = do
235+ -- the offset of the next frame's first header byte.
236+ sendHeadersIfNecessary off
237+ -- True if the connection buffer has room for a 1-byte data frame.
238+ | off + frameHeaderLength < connBufferSize = return off
239+ | otherwise = do
240+ flushN off
241+ return 0
242+
243+ fillDataHeader strm off datPayloadLen mnext = do
240244 -- Data frame header
241− let sid = streamNumber strm
242− buf = connWriteBuffer `plusPtr` otherLen
243− total = otherLen + frameHeaderLength + datPayloadLen
245+ let !sid = streamNumber strm
246+ !buf = connWriteBuffer `plusPtr` off
247+ !off' = off + frameHeaderLength + datPayloadLen
244248 flag = case mnext of
245249 Nothing -> setEndStream defaultFlags
246250 _ -> defaultFlags
247251 fillFrameHeader FrameData datPayloadLen sid flag buf
248− -- "closed" must be before "connSendAll". If not,
249− -- the context would be switched to the receiver,
250− -- resulting the inconsistency of concurrency.
251− case mnext of
252− Nothing -> closed ctx strm Finished
253− _ -> return ()
254− flushN total
252+ when (isNothing mnext) $ closed ctx strm Finished
255253 atomically $ modifyTVar' connectionWindow (subtract datPayloadLen)
256254 atomically $ modifyTVar' (streamWindow strm) (subtract datPayloadLen)
255+ return off'
257256
258257 fillFrameHeader ftyp len sid flag buf = encodeFrameHeaderBuf ftyp hinfo buf
259258 where
260259 hinfo = FrameHeader len flag sid
261260
261+ ignore :: E.SomeException -> IO ()
262+ ignore _ = return ()
263+
262264----------------------------------------------------------------
263265
264266{-
@@ -274,7 +276,7 @@ fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
274276 let datBuf = connWriteBuffer `plusPtr` off
275277 room = min (connBufferSize - off) lim
276278 (len, signal) <- B.runBuilder bb datBuf room
277− return $ nextForBuilder connWriteBuffer connBufferSize len signal
279+ return $ nextForBuilder len signal
278280
279281#ifdef WINDOWS
280282fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
@@ -287,7 +289,8 @@ fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
287289 IO.hSeek h IO.AbsoluteSeek start
288290 len <- IO.hGetBufSome h datBuf (mini room bytes)
289291 let bytes' = bytes - fromIntegral len
290− return $ nextForFile len connWriteBuffer connBufferSize h bytes' (return ())
292+ -- fixme: connWriteBuffer connBufferSize
293+ return $ nextForFile len h bytes' (return ())
291294#else
292295fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
293296 ii off lim (ResponseFile _ _ path mpart) = do
@@ -303,7 +306,7 @@ fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
303306 len <- positionRead fd datBuf (mini room bytes) start
304307 refresh
305308 let len' = fromIntegral len
306− return $ nextForFile len connWriteBuffer connBufferSize fd (start + len') (bytes - len') refresh
309+ return $ nextForFile len fd (start + len') (bytes - len') refresh
307310#endif
308311
309312fillResponseBodyGetNext _ _ _ _ _ = error "fillResponseBodyGetNext"
@@ -321,12 +324,12 @@ fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
321324 let datBuf = connWriteBuffer `plusPtr` off
322325 room = min (connBufferSize - off) lim
323326 (leftover, cont, len) <- runStreamBuilder datBuf room sq
324− return $ nextForStream connWriteBuffer connBufferSize sq strm leftover cont len
327+ return $ nextForStream sq strm leftover cont len
325328
326329----------------------------------------------------------------
327330
328−fillBufBuilder :: Buffer -> BufSize -> Leftover -> DynaNext
329−fillBufBuilder buf0 siz0 leftover lim = do
331+fillBufBuilder :: Leftover -> DynaNext
332+fillBufBuilder leftover buf0 siz0 lim = do
330333 let payloadBuf = buf0 `plusPtr` frameHeaderLength
331334 room = min (siz0 - frameHeaderLength) lim
332335 case leftover of
@@ -345,15 +348,15 @@ fillBufBuilder buf0 siz0 leftover lim = do
345348 void $ copy payloadBuf bs1
346349 getNext room (B.Chunk bs2 writer)
347350 where
348− getNext l s = return $ nextForBuilder buf0 siz0 l s
351+ getNext l s = return $ nextForBuilder l s
349352
350−nextForBuilder :: Buffer -> BufSize -> BytesFilled -> B.Next -> Next
351−nextForBuilder _ _ len B.Done
353+nextForBuilder :: BytesFilled -> B.Next -> Next
354+nextForBuilder len B.Done
352355 = Next len Nothing
353−nextForBuilder buf siz len (B.More _ writer)
354− = Next len $ Just (fillBufBuilder buf siz (LOne writer))
355−nextForBuilder buf siz len (B.Chunk bs writer)
356− = Next len $ Just (fillBufBuilder buf siz (LTwo bs writer))
356+nextForBuilder len (B.More _ writer)
357+ = Next len $ Just (fillBufBuilder (LOne writer))
358+nextForBuilder len (B.Chunk bs writer)
359+ = Next len $ Just (fillBufBuilder (LTwo bs writer))
357360
358361----------------------------------------------------------------
359362
@@ -375,8 +378,8 @@ runStreamBuilder buf0 room0 sq = loop buf0 room0 0
375378 Just SFlush -> return (LZero, True, total)
376379 Just SFinish -> return (LZero, False, total)
377380
378−fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> Stream -> DynaNext
379−fillBufStream buf0 siz0 leftover0 sq strm lim0 = do
381+fillBufStream :: Leftover -> TBQueue Sequence -> Stream -> DynaNext
382+fillBufStream leftover0 sq strm buf0 siz0 lim0 = do
380383 let payloadBuf = buf0 `plusPtr` frameHeaderLength
381384 room0 = min (siz0 - frameHeaderLength) lim0
382385 case leftover0 of
@@ -394,7 +397,7 @@ fillBufStream buf0 siz0 leftover0 sq strm lim0 = do
394397 void $ copy payloadBuf bs1
395398 getNext (LTwo bs2 writer) True room0
396399 where
397− getNext l b r = return $ nextForStream buf0 siz0 sq strm l b r
400+ getNext l b r = return $ nextForStream sq strm l b r
398401 write writer1 buf room sofar = do
399402 (len, signal) <- writer1 buf room
400403 case signal of
@@ -409,45 +412,45 @@ fillBufStream buf0 siz0 leftover0 sq strm lim0 = do
409412 let !total = sofar + len
410413 getNext (LTwo bs writer) True total
411414
412−nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> Stream
415+nextForStream :: TBQueue Sequence -> Stream
413416 -> Leftover -> Bool -> BytesFilled
414417 -> Next
415−nextForStream _ _ _ _ _ False len = Next len Nothing
416−nextForStream buf siz sq strm leftOrZero True len =
417− Next len $ Just (fillBufStream buf siz leftOrZero sq strm)
418+nextForStream _ _ _ False len = Next len Nothing
419+nextForStream sq strm leftOrZero True len =
420+ Next len $ Just (fillBufStream leftOrZero sq strm)
418421
419422----------------------------------------------------------------
420423
421424#ifdef WINDOWS
422−fillBufFile :: Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> DynaNext
423−fillBufFile buf siz h bytes refresh lim = do
425+fillBufFile :: IO.Handle -> Integer -> IO () -> DynaNext
426+fillBufFile h bytes refresh buf siz lim = do
424427 let payloadBuf = buf `plusPtr` frameHeaderLength
425428 room = min (siz - frameHeaderLength) lim
426429 len <- IO.hGetBufSome h payloadBuf room
427430 refresh
428431 let bytes' = bytes - fromIntegral len
429− return $ nextForFile len buf siz h bytes' refresh
432+ return $ nextForFile len h bytes' refresh
430433
431−nextForFile :: BytesFilled -> Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> Next
432−nextForFile 0 _ _ _ _ _ = Next 0 Nothing
433−nextForFile len _ _ _ 0 _ = Next len Nothing
434−nextForFile len buf siz h bytes refresh =
435− Next len $ Just (fillBufFile buf siz h bytes refresh)
434+nextForFile :: BytesFilled -> IO.Handle -> Integer -> IO () -> Next
435+nextForFile 0 _ _ _ = Next 0 Nothing
436+nextForFile len _ 0 _ = Next len Nothing
437+nextForFile len h bytes refresh =
438+ Next len $ Just (fillBufFile h bytes refresh)
436439#else
437−fillBufFile :: Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> DynaNext
438−fillBufFile buf siz fd start bytes refresh lim = do
440+fillBufFile :: Fd -> Integer -> Integer -> IO () -> DynaNext
441+fillBufFile fd start bytes refresh buf siz lim = do
439442 let payloadBuf = buf `plusPtr` frameHeaderLength
440443 room = min (siz - frameHeaderLength) lim
441444 len <- positionRead fd payloadBuf (mini room bytes) start
442445 let len' = fromIntegral len
443446 refresh
444− return $ nextForFile len buf siz fd (start + len') (bytes - len') refresh
447+ return $ nextForFile len fd (start + len') (bytes - len') refresh
445448
446−nextForFile :: BytesFilled -> Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> Next
447−nextForFile 0 _ _ _ _ _ _ = Next 0 Nothing
448−nextForFile len _ _ _ _ 0 _ = Next len Nothing
449−nextForFile len buf siz fd start bytes refresh =
450− Next len $ Just (fillBufFile buf siz fd start bytes refresh)
449+nextForFile :: BytesFilled -> Fd -> Integer -> Integer -> IO () -> Next
450+nextForFile 0 _ _ _ _ = Next 0 Nothing
451+nextForFile len _ _ 0 _ = Next len Nothing
452+nextForFile len fd start bytes refresh =
453+ Next len $ Just (fillBufFile fd start bytes refresh)
451454#endif
452455
453456mini :: Int -> Integer -> Int
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+33−14
@@ -8,14 +8,17 @@ import Data.ByteString.Builder (Builder)
88#if __GLASGOW_HASKELL__ < 709
99import Control.Applicative ((<$>),(<*>))
1010#endif
11+import Control.Concurrent (forkIO)
1112import Control.Concurrent.STM
12−import Control.Exception (SomeException)
13+import Control.Exception (SomeException, bracket)
14+import Control.Monad (void)
1315import Data.ByteString (ByteString)
1416import qualified Data.ByteString as BS
1517import Data.IntMap.Strict (IntMap, IntMap)
1618import qualified Data.IntMap.Strict as M
1719import qualified Network.HTTP.Types as H
1820import Network.Wai (Request, Response)
21+import Network.Wai.Handler.Warp.HTTP2.Manager
1922import Network.Wai.Handler.Warp.IORef
2023import Network.Wai.Handler.Warp.Types
2124
@@ -42,23 +45,27 @@ data Input = Input Stream Request
4245
4346----------------------------------------------------------------
4447
45−type DynaNext = WindowSize -> IO Next
48+type DynaNext = Buffer -> BufSize -> WindowSize -> IO Next
4649
4750type BytesFilled = Int
4851
4952data Next = Next !BytesFilled (Maybe DynaNext)
5053
51−data Output = OFinish
52− | OGoaway !ByteString
53− | OFrame !ByteString
54− | OSettings !ByteString !SettingsList
55− | OResponse !Stream !Response !BodyInfo
56− | ONext !Stream !DynaNext !BodyInfo
54+data Output = OResponse !Stream !BodyInfo !Response
55+ | ONext !Stream !BodyInfo !DynaNext
5756
5857outputStream :: Output -> Stream
5958outputStream (OResponse strm _ _) = strm
6059outputStream (ONext strm _ _) = strm
61−outputStream _ = error "outputStream"
60+
61+outputBodyInfo :: Output -> BodyInfo
62+outputBodyInfo (OResponse _ binfo _) = binfo
63+outputBodyInfo (ONext _ binfo _) = binfo
64+
65+data Control = CFinish
66+ | CGoaway !ByteString
67+ | CFrame !ByteString
68+ | CSettings !ByteString !SettingsList
6269
6370----------------------------------------------------------------
6471
@@ -75,6 +82,7 @@ data BodyInfo = OneshotWithBody
7582-- | The context for HTTP/2 connection.
7683data Context = Context {
7784 http2settings :: !(IORef Settings)
85+ , firstSettings :: !(IORef Bool)
7886 , streamTable :: !StreamTable
7987 , concurrency :: !(IORef Int)
8088 , priorityTreeSize :: !(IORef Int)
@@ -86,6 +94,7 @@ data Context = Context {
8694 , currentStreamId :: !(IORef StreamId)
8795 , inputQ :: !(TQueue Input)
8896 , outputQ :: !(PriorityTree Output)
97+ , controlQ :: !(TQueue Control)
8998 , encodeDynamicTable :: !(IORef DynamicTable)
9099 , decodeDynamicTable :: !(IORef DynamicTable)
91100 , connectionWindow :: !(TVar WindowSize)
@@ -95,6 +104,7 @@ data Context = Context {
95104
96105newContext :: IO Context
97106newContext = Context <$> newIORef defaultSettings
107+ <*> newIORef False
98108 <*> newStreamTable
99109 <*> newIORef 0
100110 <*> newIORef 0
@@ -102,6 +112,7 @@ newContext = Context <$> newIORef defaultSettings
102112 <*> newIORef 0
103113 <*> newTQueueIO
104114 <*> newPriorityTree
115+ <*> newTQueueIO
105116 <*> (newDynamicTableForEncoding defaultDynamicTableSize >>= newIORef)
106117 <*> (newDynamicTableForDecoding defaultDynamicTableSize >>= newIORef)
107118 <*> newTVarIO defaultInitialWindowSize
@@ -211,11 +222,15 @@ remove (StreamTable ref) k = atomicModifyIORef' ref $ \m ->
211222search :: StreamTable -> M.Key -> IO (Maybe Stream)
212223search (StreamTable ref) k = M.lookup k <$> readIORef ref
213224
214−{-# INLINE enqueueWhenReady #-}
215−enqueueWhenReady :: STM () -> PriorityTree Output -> Output -> IO ()
216−enqueueWhenReady wait outQ out = do
217− atomically wait
218− enqueueOutput outQ out
225+{-# INLINE forkAndEnqueueWhenReady #-}
226+forkAndEnqueueWhenReady :: STM () -> PriorityTree Output -> Output -> Manager -> IO ()
227+forkAndEnqueueWhenReady wait outQ out mgr = bracket setup teardown $ \_ ->
228+ void . forkIO $ do
229+ atomically wait
230+ enqueueOutput outQ out
231+ where
232+ setup = addMyId mgr
233+ teardown _ = deleteMyId mgr
219234
220235{-# INLINE enqueueOutput #-}
221236enqueueOutput :: PriorityTree Output -> Output -> IO ()
@@ -223,3 +238,7 @@ enqueueOutput outQ out = do
223238 let Stream{..} = outputStream out
224239 pre <- readIORef streamPrecedence
225240 enqueue outQ streamNumber pre out
241+
242+{-# INLINE enqueueControl #-}
243+enqueueControl :: TQueue Control -> Control -> IO ()
244+enqueueControl ctlQ ctl = atomically $ writeTQueue ctlQ ctl
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+20−13
@@ -15,7 +15,6 @@ module Network.Wai.Handler.Warp.HTTP2.Worker (
1515import Control.Applicative
1616import Data.Monoid (mempty)
1717#endif
18−import Control.Concurrent
1918import Control.Concurrent.STM
2019import Control.Exception (SomeException(..), AsyncException(..))
2120import qualified Control.Exception as E
@@ -23,7 +22,6 @@ import Control.Monad (when)
2322import Data.ByteString.Builder (byteString)
2423import qualified Network.HTTP.Types as H
2524import Network.HTTP2
26−import Network.HTTP2.Priority
2725import Network.Wai
2826import Network.Wai.Handler.Warp.File
2927import Network.Wai.Handler.Warp.FileInfoCache
@@ -65,17 +63,23 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
6563 !isHead = requestMethod req == H.methodHead
6664 !s0 = responseStatus rsp
6765 !hs0 = responseHeaders rsp
68− !logger = S.settingsLogger settings req
66+ !logger = S.settingsLogger settings
67+
68+ -- Ideally, log messages should be written when responses are
69+ -- actually sent. But there is no way to keep good memory usage
70+ -- (resist to Request leak) and throughput. By compromise,
71+ -- log message are written here even the window size of streams
72+ -- is 0.
6973
7074 responseNoBody s hs = responseBuilderCore s hs mempty OneshotWithoutBody
7175
7276 responseBuilderBody s hs bdy = responseBuilderCore s hs bdy OneshotWithBody
7377
7478 responseBuilderCore s hs bdy binfo = do
75− logger s Nothing
79+ logger req s Nothing
7680 setThreadContinue tconf True
7781 let rsp' = ResponseBuilder s hs bdy
78− out = OResponse strm rsp' binfo
82+ out = OResponse strm binfo rsp'
7983 enqueueOutput outputQ out
8084 return ResponseReceived
8185
@@ -90,12 +94,14 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
9094 responseFileXXX path mpart = responseFile2XX s0 hs0 path mpart
9195
9296 responseFile2XX s hs path mpart
93− | isHead = responseNoBody s hs
97+ | isHead = do
98+ logger req s Nothing
99+ responseNoBody s hs
94100 | otherwise = do
95− logger s (filePartByteCount <$> mpart)
101+ logger req s (filePartByteCount <$> mpart)
96102 setThreadContinue tconf True
97103 let rsp' = ResponseFile s hs path mpart
98− out = OResponse strm rsp' OneshotWithBody
104+ out = OResponse strm OneshotWithBody rsp'
99105 enqueueOutput outputQ out
100106 return ResponseReceived
101107
@@ -106,20 +112,20 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
106112 body = byteString "File not found"
107113
108114 responseStreaming strmbdy = do
109− logger s0 Nothing
115+ logger req s0 Nothing
110116 -- We must not exit this WAI application.
111117 -- If the application exits, streaming would be also closed.
112118 -- So, this work occupies this thread.
113119 --
114120 -- We need to increase the number of workers.
115− myThreadId >>= replaceWithAction mgr
121+ spawnAction mgr
116122 -- After this work, this thread stops to decease
117123 -- the number of workers.
118124 setThreadContinue tconf False
119125 -- Since 'StreamingBody' is loop, we cannot control it.
120126 -- So, let's serialize 'Builder' with a designated queue.
121127 sq <- newTBQueueIO 10 -- fixme: hard coding: 10
122− let out = OResponse strm rsp (Persist sq)
128+ let out = OResponse strm (Persist sq) rsp
123129 enqueueOutput outputQ out
124130 let push b = do
125131 atomically $ writeTBQueue sq (SBuilder b)
@@ -127,10 +133,11 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
127133 flush = atomically $ writeTBQueue sq SFlush
128134 _ <- strmbdy push flush
129135 atomically $ writeTBQueue sq SFinish
136+ deleteMyId mgr
130137 return ResponseReceived
131138
132139worker :: Context -> S.Settings -> Application -> Responder -> T.Manager -> IO ()
133−worker ctx@Context{inputQ,outputQ} set app responder tm = do
140+worker ctx@Context{inputQ,controlQ} set app responder tm = do
134141 sinfo <- newStreamInfo
135142 tcont <- newThreadContinue
136143 E.bracket (T.registerKillThread tm) T.cancel $ go sinfo tcont
@@ -166,7 +173,7 @@ worker ctx@Context{inputQ,outputQ} set app responder tm = do
166173 Just (Input strm req) -> do
167174 closed ctx strm Killed
168175 let frame = resetFrame InternalError (streamNumber strm)
169− enqueueControl outputQ 0 (OFrame frame)
176+ enqueueControl controlQ $ CFrame frame
170177 case me of
171178 Nothing -> return ()
172179 Just e -> S.settingsOnException set (Just req) e