on master help sign in
Merge topic branch to improve performance and footprint for HTTP/2.

Now workers and a sender optimistic output enqueueing. A receiver controls input enqueueing by itself.

commit356fa3159a58c1ae321168aab02e48b63ee141ee authorKazu Yamamoto <kazu@iij.ad.jp> dateFri 18 Dec 2015, 09:40 (11 years ago) parentc6995919a881a1
4 files changed+147−169
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+28−22 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+92−74 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+16−37 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+11−36
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+28−22
@@ -9,6 +9,7 @@ module Network.Wai.Handler.Warp.HTTP2.Receiver (frameReceiver) where
99#if __GLASGOW_HASKELL__ < 709
1010import Control.Applicative
1111#endif
12+import Control.Concurrent
1213import Control.Concurrent.STM
1314import qualified Control.Exception as E
1415import Control.Monad (when, unless, void)
@@ -27,7 +28,7 @@ import Network.Wai.Handler.Warp.Types
2728----------------------------------------------------------------
2829
2930frameReceiver :: Context -> MkReq -> (BufSize -> IO ByteString) -> IO ()
30−frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
31+frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
3132 where
3233 Context{ http2settings
3334 , streamTable
@@ -40,21 +41,26 @@ frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
4041 sendGoaway e
4142 | Just (ConnectionError err msg) <- E.fromException e = do
4243 csid <- readIORef currentStreamId
43− let frame = goawayFrame csid err msg
44+ let !frame = goawayFrame csid err msg
4445 enqueueControl outputQ 0 $ OGoaway frame
4546 | otherwise = return ()
4647
4748 sendReset err sid = do
48− let frame = resetFrame err sid
49+ let !frame = resetFrame err sid
4950 enqueueControl outputQ 0 $ OFrame frame
5051
51− loop = do
52+ loop :: Int -> IO ()
53+ loop !n
54+ | n == 6 = do
55+ yield
56+ loop 0
57+ | otherwise = do
5258 hd <- recvN frameHeaderLength
5359 if BS.null hd then
5460 enqueueControl outputQ 0 OFinish
5561 else do
5662 cont <- processStreamGuardingError $ decodeFrameHeader hd
57− when cont loop
63+ when cont $ loop (n + 1)
5864
5965 processStreamGuardingError (_, FrameHeader{streamId})
6066 | isResponse streamId = E.throwIO $ ConnectionError ProtocolError "stream id should be odd"
@@ -176,7 +182,7 @@ control FrameSettings header@FrameHeader{flags} bs Context{http2settings, output
176182 Nothing -> return ()
177183 unless (testAck flags) $ do
178184 modifyIORef' http2settings $ \old -> updateSettings old alist
179− let frame = settingsFrame setAck []
185+ let !frame = settingsFrame setAck []
180186 enqueueControl outputQ 0 $ OSettings frame alist
181187 return True
182188
@@ -184,7 +190,7 @@ control FramePing FrameHeader{flags} bs Context{outputQ} =
184190 if testAck flags then
185191 E.throwIO $ ConnectionError ProtocolError "the ack flag of this ping frame must not be set"
186192 else do
187− let frame = pingFrame bs
193+ let !frame = pingFrame bs
188194 enqueueControl outputQ 0 $ OFrame frame
189195 return True
190196
@@ -194,7 +200,7 @@ control FrameGoAway _ _ Context{outputQ} = do
194200
195201control FrameWindowUpdate header bs Context{connectionWindow} = do
196202 WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
197− w <- (n +) <$> atomically (readTVar connectionWindow)
203+ !w <- (n +) <$> atomically (readTVar connectionWindow)
198204 when (isWindowOverflow w) $ E.throwIO $ ConnectionError FlowControlError "control window should be less than 2^31"
199205 atomically $ writeTVar connectionWindow w
200206 return True
@@ -225,8 +231,8 @@ stream FrameHeaders header@FrameHeader{flags} bs ctx (Open JustOpened) Stream{st
225231 Just p -> do
226232 checkPriority p streamNumber
227233 return p
228− let endOfStream = testEndStream flags
229− endOfHeader = testEndHeader flags
234+ let !endOfStream = testEndStream flags
235+ !endOfHeader = testEndHeader flags
230236 if endOfHeader then do
231237 hdr <- hpackDecodeHeader frag ctx
232238 return $ if endOfStream then
@@ -241,7 +247,7 @@ stream FrameHeaders header@FrameHeader{flags} bs _ (Open (Body q)) _ = do
241247 -- trailer is not supported.
242248 -- let's read and ignore it.
243249 HeadersFrame _ _ <- guardIt $ decodeHeadersFrame header bs
244− let endOfStream = testEndStream flags
250+ let !endOfStream = testEndStream flags
245251 if endOfStream then do
246252 atomically $ writeTQueue q ""
247253 return HalfClosed
@@ -255,14 +261,14 @@ stream FrameData
255261 Context{outputQ} s@(Open (Body q))
256262 Stream{streamNumber,streamBodyLength,streamContentLength} = do
257263 DataFrame body <- guardIt $ decodeDataFrame header bs
258− let endOfStream = testEndStream flags
264+ let !endOfStream = testEndStream flags
259265 len0 <- readIORef streamBodyLength
260266 let !len = len0 + payloadLength
261267 writeIORef streamBodyLength len
262268 when (payloadLength /= 0) $ do
263− let frame1 = windowUpdateFrame 0 payloadLength
264− frame2 = windowUpdateFrame streamNumber payloadLength
265− frame = frame1 `BS.append` frame2
269+ let !frame1 = windowUpdateFrame 0 payloadLength
270+ !frame2 = windowUpdateFrame streamNumber payloadLength
271+ !frame = frame1 `BS.append` frame2
266272 enqueueControl outputQ 0 $ OFrame frame
267273 atomically $ writeTQueue q body
268274 if endOfStream then do
@@ -276,16 +282,16 @@ stream FrameData
276282 return s
277283
278284stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz n endOfStream pri)) _ = do
279− let endOfHeader = testEndHeader flags
280− rfrags' = frag : rfrags
281− siz' = siz + BS.length frag
282− n' = n + 1
285+ let !endOfHeader = testEndHeader flags
286+ !rfrags' = frag : rfrags
287+ !siz' = siz + BS.length frag
288+ !n' = n + 1
283289 when (siz' > 51200) $ -- fixme: hard coding: 50K
284290 E.throwIO $ ConnectionError EnhanceYourCalm "Header is too big"
285291 when (n' > 10) $ -- fixme: hard coding
286292 E.throwIO $ ConnectionError EnhanceYourCalm "Header is too fragmented"
287293 if endOfHeader then do
288− let hdrblk = BS.concat $ reverse rfrags'
294+ let !hdrblk = BS.concat $ reverse rfrags'
289295 hdr <- hpackDecodeHeader hdrblk ctx
290296 return $ if endOfStream then
291297 Open (NoBody hdr pri)
@@ -296,7 +302,7 @@ stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz
296302
297303stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow} = do
298304 WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
299− w <- (n +) <$> atomically (readTVar streamWindow)
305+ !w <- (n +) <$> atomically (readTVar streamWindow)
300306 when (isWindowOverflow w) $
301307 E.throwIO $ StreamError FlowControlError streamId
302308 atomically $ writeTVar streamWindow w
@@ -304,7 +310,7 @@ stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow
304310
305311stream FrameRSTStream header bs ctx _ strm = do
306312 RSTStreamFrame e <- guardIt $ decoderstStreamFrame header bs
307− let cc = Reset e
313+ let !cc = Reset e
308314 closed ctx strm cc
309315 return $ Closed cc -- will be written to streamState again
310316
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+92−74
@@ -57,16 +57,24 @@ unlessClosed ctx
5757 let rst = resetFrame InternalError streamNumber
5858 connSendAll rst
5959
60−getWindowSize :: TVar WindowSize -> TVar WindowSize -> IO WindowSize
61−getWindowSize connWindow strmWindow = do
62− -- Waiting that the connection window gets open.
63− cw <- atomically $ do
64− w <- readTVar connWindow
65− check (w > 0)
66− return w
67− -- This stream window is greater than 0 thanks to the invariant.
68− sw <- atomically $ readTVar strmWindow
69− return $ min cw sw
60+getConnectionWindowSizeWhenReady :: Context -> IO WindowSize
61+getConnectionWindowSizeWhenReady Context{connectionWindow} = atomically $ do
62+ w <- readTVar connectionWindow
63+ check (w > 0)
64+ return w
65+
66+getStreamWindowSize :: Stream -> IO WindowSize
67+getStreamWindowSize Stream{streamWindow} = atomically $ readTVar streamWindow
68+
69+waitStreamWindowSize :: Stream -> STM ()
70+waitStreamWindowSize Stream{streamWindow} = do
71+ w <- readTVar streamWindow
72+ check (w > 0)
73+
74+waitStreaming :: TBQueue a -> STM ()
75+waitStreaming tbq = do
76+ isEmpty <- isEmptyTBQueue tbq
77+ check (isEmpty == False)
7078
7179frameSender :: Context -> Connection -> InternalInfo -> S.Settings -> IO ()
7280frameSender ctx@Context{outputQ,connectionWindow,encodeDynamicTable}
@@ -101,55 +109,76 @@ frameSender ctx@Context{outputQ,connectionWindow,encodeDynamicTable}
101109 dyntbl <- readIORef encodeDynamicTable
102110 setLimitForEncoding siz dyntbl
103111 loop
104− switch (OResponse strm rsp aux) pre = do
112+ switch out@(ONext strm curr binfo) pre = do
113+ writeIORef (streamPrecedence strm) pre
114+ whenReadyOrEnqueueAgain strm binfo out $ \sws -> do
115+ cws <- getConnectionWindowSizeWhenReady ctx
116+ let !lim = min cws sws
117+ -- Data frame payload
118+ Next datPayloadLen mnext <- curr lim
119+ fillDataHeaderSend strm 0 datPayloadLen mnext
120+ maybeEnqueueNext strm mnext binfo
121+ loop
122+ switch out@(OResponse strm rsp binfo) pre = do
105123 writeIORef (streamPrecedence strm) pre
106− unlessClosed ctx conn strm $ do
107− lim <- getWindowSize connectionWindow (streamWindow strm)
124+ whenReadyOrEnqueueAgain strm binfo out $ \sws -> do
125+ cws <- getConnectionWindowSizeWhenReady ctx
126+ let !lim = min cws sws
108127 -- Header frame and Continuation frame
109128 let sid = streamNumber strm
110− endOfStream = case aux of
111− Persist{} -> False
112− Oneshot hb -> not hb
129+ endOfStream = case binfo of
130+ Persist{} -> False
131+ OneshotWithBody -> False
132+ OneshotWithoutBody -> True
113133 len <- headerContinue sid rsp endOfStream
114134 let total = len + frameHeaderLength
115− case aux of
116− Oneshot True -> do -- hasBody
135+ case binfo of
136+ OneshotWithBody -> do
117137 -- Data frame payload
118138 (off, _) <- sendHeadersIfNecessary total
119139 let payloadOff = off + frameHeaderLength
120140 Next datPayloadLen mnext <-
121141 fillResponseBodyGetNext conn ii payloadOff lim rsp
122142 fillDataHeaderSend strm total datPayloadLen mnext
123− maybeEnqueueNext strm mnext
124− Oneshot False -> do
143+ maybeEnqueueNext strm mnext binfo
144+ OneshotWithoutBody -> do
125145 -- "closed" must be before "connSendAll". If not,
126146 -- the context would be switched to the receiver,
127147 -- resulting the inconsistency of concurrency.
128148 closed ctx strm Finished
129149 flushN total
130− Persist sq tvar -> do
150+ Persist sq -> do
131151 (off, needSend) <- sendHeadersIfNecessary total
132152 let payloadOff = off + frameHeaderLength
133153 Next datPayloadLen mnext <-
134− fillStreamBodyGetNext conn payloadOff lim sq tvar strm
154+ fillStreamBodyGetNext conn payloadOff lim sq strm
135155 -- If no data was immediately available, avoid sending an
136156 -- empty data frame.
137157 if datPayloadLen > 0 then
138158 fillDataHeaderSend strm total datPayloadLen mnext
139159 else
140160 when needSend $ flushN off
141− maybeEnqueueNext strm mnext
142− loop
143− switch (ONext strm curr) pre = do
144− writeIORef (streamPrecedence strm) pre
145− unlessClosed ctx conn strm $ do
146− lim <- getWindowSize connectionWindow (streamWindow strm)
147− -- Data frame payload
148− Next datPayloadLen mnext <- curr lim
149− fillDataHeaderSend strm 0 datPayloadLen mnext
150− maybeEnqueueNext strm mnext
161+ maybeEnqueueNext strm mnext binfo
151162 loop
152163
164+ whenReadyOrEnqueueAgain strm binfo out body =
165+ unlessClosed ctx conn strm $ case binfo of
166+ Persist tbq -> checkStreaming tbq
167+ _ -> checkStreamWindowSize
168+ where
169+ checkStreaming tbq = do
170+ isEmpty <- atomically $ isEmptyTBQueue tbq
171+ if isEmpty then
172+ enqueueWhenReady (waitStreaming tbq) outputQ out
173+ else
174+ checkStreamWindowSize
175+ checkStreamWindowSize = do
176+ sws <- getStreamWindowSize strm
177+ if sws == 0 then do
178+ enqueueWhenReady (waitStreamWindowSize strm) outputQ out
179+ else
180+ body sws
181+
153182 -- Flush the connection buffer to the socket, where the first 'n' bytes of
154183 -- the buffer are filled.
155184 flushN :: Int -> IO ()
@@ -188,16 +217,12 @@ frameSender ctx@Context{outputQ,connectionWindow,encodeDynamicTable}
188217 -- True if the connection buffer has room for a 1-byte data frame.
189218 canFitDataFrame total = total + frameHeaderLength < connBufferSize
190219
191− -- Re-enqueue the stream in the output queue if more output is immediately
192− -- available; do nothing otherwise.
193− maybeEnqueueNext :: Stream -> Control DynaNext -> IO ()
194− maybeEnqueueNext strm (CNext next) = do
195− let out = ONext strm next
196− enqueueOrSpawnTemporaryWaiter strm outputQ out
197− -- If the streaming is not finished, it must already have been
198− -- written to the 'TVar' owned by 'waiter', which will
199− -- put it back into the queue when more output becomes available.
200− maybeEnqueueNext _ _ = return ()
220+ -- Re-enqueue the stream in the output queue.
221+ maybeEnqueueNext :: Stream -> Maybe DynaNext -> BodyInfo -> IO ()
222+ maybeEnqueueNext strm (Just next) binfo = do
223+ let !out = ONext strm next binfo
224+ enqueueOutput outputQ out
225+ maybeEnqueueNext _ _ _ = return ()
201226
202227
203228 -- Send headers if there is not room for a 1-byte data frame, and return
@@ -215,19 +240,18 @@ frameSender ctx@Context{outputQ,connectionWindow,encodeDynamicTable}
215240 buf = connWriteBuffer `plusPtr` otherLen
216241 total = otherLen + frameHeaderLength + datPayloadLen
217242 flag = case mnext of
218− CFinish -> setEndStream defaultFlags
243+ Nothing -> setEndStream defaultFlags
219244 _ -> defaultFlags
220245 fillFrameHeader FrameData datPayloadLen sid flag buf
221246 -- "closed" must be before "connSendAll". If not,
222247 -- the context would be switched to the receiver,
223248 -- resulting the inconsistency of concurrency.
224249 case mnext of
225− CFinish -> closed ctx strm Finished
226− _ -> return ()
250+ Nothing -> closed ctx strm Finished
251+ _ -> return ()
227252 flushN total
228− atomically $ do
229− modifyTVar' connectionWindow (subtract datPayloadLen)
230− modifyTVar' (streamWindow strm) (subtract datPayloadLen)
253+ atomically $ modifyTVar' connectionWindow (subtract datPayloadLen)
254+ atomically $ modifyTVar' (streamWindow strm) (subtract datPayloadLen)
231255
232256 fillFrameHeader ftyp len sid flag buf = encodeFrameHeaderBuf ftyp hinfo buf
233257 where
@@ -289,13 +313,13 @@ fileStartEnd _ _ = error "fileStartEnd"
289313
290314----------------------------------------------------------------
291315
292−fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> TVar Sync -> Stream -> IO Next
316+fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> Stream -> IO Next
293317fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
294− off lim sq tvar strm = do
318+ off lim sq strm = do
295319 let datBuf = connWriteBuffer `plusPtr` off
296320 room = min (connBufferSize - off) lim
297321 (leftover, cont, len) <- runStreamBuilder datBuf room sq
298− nextForStream connWriteBuffer connBufferSize sq tvar strm leftover cont len
322+ return $ nextForStream connWriteBuffer connBufferSize sq strm leftover cont len
299323
300324----------------------------------------------------------------
301325
@@ -323,11 +347,11 @@ fillBufBuilder buf0 siz0 leftover lim = do
323347
324348nextForBuilder :: Buffer -> BufSize -> BytesFilled -> B.Next -> Next
325349nextForBuilder _ _ len B.Done
326− = Next len CFinish
350+ = Next len Nothing
327351nextForBuilder buf siz len (B.More _ writer)
328− = Next len (CNext (fillBufBuilder buf siz (LOne writer)))
352+ = Next len $ Just (fillBufBuilder buf siz (LOne writer))
329353nextForBuilder buf siz len (B.Chunk bs writer)
330− = Next len (CNext (fillBufBuilder buf siz (LTwo bs writer)))
354+ = Next len $ Just (fillBufBuilder buf siz (LTwo bs writer))
331355
332356----------------------------------------------------------------
333357
@@ -349,8 +373,8 @@ runStreamBuilder buf0 room0 sq = loop buf0 room0 0
349373 Just SFlush -> return (LZero, True, total)
350374 Just SFinish -> return (LZero, False, total)
351375
352−fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> TVar Sync -> Stream -> DynaNext
353−fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
376+fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> Stream -> DynaNext
377+fillBufStream buf0 siz0 leftover0 sq strm lim0 = do
354378 let payloadBuf = buf0 `plusPtr` frameHeaderLength
355379 room0 = min (siz0 - frameHeaderLength) lim0
356380 case leftover0 of
@@ -368,7 +392,7 @@ fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
368392 void $ copy payloadBuf bs1
369393 getNext (LTwo bs2 writer) True room0
370394 where
371− getNext = nextForStream buf0 siz0 sq tvar strm
395+ getNext l b r = return $ nextForStream buf0 siz0 sq strm l b r
372396 write writer1 buf room sofar = do
373397 (len, signal) <- writer1 buf room
374398 case signal of
@@ -383,18 +407,12 @@ fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
383407 let !total = sofar + len
384408 getNext (LTwo bs writer) True total
385409
386−nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> TVar Sync -> Stream
410+nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> Stream
387411 -> Leftover -> Bool -> BytesFilled
388− -> IO Next
389−nextForStream _ _ _ tvar _ _ False len = do
390− atomically $ writeTVar tvar SyncFinish
391− return $ Next len CFinish
392−nextForStream buf siz sq tvar strm LZero True len = do
393− let out = ONext strm (fillBufStream buf siz LZero sq tvar strm)
394− atomically $ writeTVar tvar $ SyncNext out
395− return $ Next len CNone
396−nextForStream buf siz sq tvar strm leftover True len =
397− return $ Next len (CNext (fillBufStream buf siz leftover sq tvar strm))
412+ -> Next
413+nextForStream _ _ _ _ _ False len = Next len Nothing
414+nextForStream buf siz sq strm leftOrZero True len =
415+ Next len $ Just (fillBufStream buf siz leftOrZero sq strm)
398416
399417----------------------------------------------------------------
400418
@@ -409,10 +427,10 @@ fillBufFile buf siz h bytes refresh lim = do
409427 return $ nextForFile len buf siz h bytes' refresh
410428
411429nextForFile :: BytesFilled -> Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> Next
412−nextForFile 0 _ _ _ _ _ = Next 0 CFinish
413−nextForFile len _ _ _ 0 _ = Next len CFinish
430+nextForFile 0 _ _ _ _ _ = Next 0 Nothing
431+nextForFile len _ _ _ 0 _ = Next len Nothing
414432nextForFile len buf siz h bytes refresh =
415− Next len (CNext (fillBufFile buf siz h bytes refresh))
433+ Next len $ Just (fillBufFile buf siz h bytes refresh)
416434#else
417435fillBufFile :: Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> DynaNext
418436fillBufFile buf siz fd start bytes refresh lim = do
@@ -424,10 +442,10 @@ fillBufFile buf siz fd start bytes refresh lim = do
424442 return $ nextForFile len buf siz fd (start + len') (bytes - len') refresh
425443
426444nextForFile :: BytesFilled -> Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> Next
427−nextForFile 0 _ _ _ _ _ _ = Next 0 CFinish
428−nextForFile len _ _ _ _ 0 _ = Next len CFinish
445+nextForFile 0 _ _ _ _ _ _ = Next 0 Nothing
446+nextForFile len _ _ _ _ 0 _ = Next len Nothing
429447nextForFile len buf siz fd start bytes refresh =
430− Next len (CNext (fillBufFile buf siz fd start bytes refresh))
448+ Next len $ Just (fillBufFile buf siz fd start bytes refresh)
431449#endif
432450
433451mini :: Int -> Integer -> Int
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+16−37
@@ -8,10 +8,8 @@ import Data.ByteString.Builder (Builder)
88#if __GLASGOW_HASKELL__ < 709
99import Control.Applicative ((<$>),(<*>))
1010#endif
11−import Control.Concurrent (forkIO)
1211import Control.Concurrent.STM
1312import Control.Exception (SomeException)
14−import Control.Monad (void)
1513import Data.ByteString (ByteString)
1614import qualified Data.ByteString as BS
1715import Data.IntMap.Strict (IntMap, IntMap)
@@ -44,31 +42,22 @@ data Input = Input Stream Request
4442
4543----------------------------------------------------------------
4644
47−data Control a = CFinish
48− | CNext a
49− | CNone
50−
51−instance Show (Control a) where
52− show CFinish = "CFinish"
53− show (CNext _) = "CNext"
54− show CNone = "CNone"
55−
5645type DynaNext = WindowSize -> IO Next
5746
5847type BytesFilled = Int
5948
60−data Next = Next !BytesFilled (Control DynaNext)
49+data Next = Next !BytesFilled (Maybe DynaNext)
6150
6251data Output = OFinish
6352 | OGoaway !ByteString
6453 | OFrame !ByteString
6554 | OSettings !ByteString !SettingsList
66− | OResponse !Stream !Response !Aux
67− | ONext !Stream !DynaNext
55+ | OResponse !Stream !Response !BodyInfo
56+ | ONext !Stream !DynaNext !BodyInfo
6857
6958outputStream :: Output -> Stream
7059outputStream (OResponse strm _ _) = strm
71−outputStream (ONext strm _) = strm
60+outputStream (ONext strm _ _) = strm
7261outputStream _ = error "outputStream"
7362
7463----------------------------------------------------------------
@@ -77,12 +66,9 @@ data Sequence = SFinish
7766 | SFlush
7867 | SBuilder Builder
7968
80−data Sync = SyncNone
81− | SyncFinish
82− | SyncNext Output
83−
84−data Aux = Oneshot Bool
85− | Persist (TBQueue Sequence) (TVar Sync)
69+data BodyInfo = OneshotWithBody
70+ | OneshotWithoutBody
71+ | Persist (TBQueue Sequence)
8672
8773----------------------------------------------------------------
8874
@@ -225,22 +211,15 @@ remove (StreamTable ref) k = atomicModifyIORef' ref $ \m ->
225211search :: StreamTable -> M.Key -> IO (Maybe Stream)
226212search (StreamTable ref) k = M.lookup k <$> readIORef ref
227213
228−-- INVARIANT: streams in the output queue have non-zero window size.
229−enqueueWhenWindowIsOpen :: PriorityTree Output -> Output -> IO ()
230−enqueueWhenWindowIsOpen outQ out = do
214+{-# INLINE enqueueWhenReady #-}
215+enqueueWhenReady :: STM () -> PriorityTree Output -> Output -> IO ()
216+enqueueWhenReady wait outQ out = do
217+ atomically $ wait
218+ enqueueOutput outQ out
219+
220+{-# INLINE enqueueOutput #-}
221+enqueueOutput :: PriorityTree Output -> Output -> IO ()
222+enqueueOutput outQ out = do
231223 let Stream{..} = outputStream out
232− atomically $ do
233− x <- readTVar streamWindow
234− check (x > 0)
235224 pre <- readIORef streamPrecedence
236225 enqueue outQ streamNumber pre out
237−
238−enqueueOrSpawnTemporaryWaiter :: Stream -> PriorityTree Output -> Output -> IO ()
239−enqueueOrSpawnTemporaryWaiter Stream{..} outQ out = do
240− sw <- atomically $ readTVar streamWindow
241− if sw == 0 then
242− -- This waiter waits only for the stream window.
243− void $ forkIO $ enqueueWhenWindowIsOpen outQ out
244− else do
245− pre <- readIORef streamPrecedence
246− enqueue outQ streamNumber pre out
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+11−36
@@ -19,7 +19,7 @@ import Control.Concurrent
1919import Control.Concurrent.STM
2020import Control.Exception (SomeException(..), AsyncException(..))
2121import qualified Control.Exception as E
22−import Control.Monad (void, when)
22+import Control.Monad (when)
2323import Data.ByteString.Builder (byteString)
2424import qualified Network.HTTP.Types as H
2525import Network.HTTP2
@@ -67,14 +67,16 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
6767 !hs0 = responseHeaders rsp
6868 !logger = S.settingsLogger settings req
6969
70− responseNoBody s hs = responseBuilderBody s hs mempty
70+ responseNoBody s hs = responseBuilderCore s hs mempty OneshotWithoutBody
7171
72− responseBuilderBody s hs bdy = do
72+ responseBuilderBody s hs bdy = responseBuilderCore s hs bdy OneshotWithBody
73+
74+ responseBuilderCore s hs bdy binfo = do
7375 logger s Nothing
7476 setThreadContinue tconf True
7577 let rsp' = ResponseBuilder s hs bdy
76− out = OResponse strm rsp' (Oneshot True)
77− enqueueOrSpawnTemporaryWaiter strm outputQ out
78+ out = OResponse strm rsp' binfo
79+ enqueueOutput outputQ out
7880 return ResponseReceived
7981
8082 responseFileXXX path Nothing = do
@@ -93,8 +95,8 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
9395 logger s (filePartByteCount <$> mpart)
9496 setThreadContinue tconf True
9597 let rsp' = ResponseFile s hs path mpart
96− out = OResponse strm rsp' (Oneshot True)
97− enqueueOrSpawnTemporaryWaiter strm outputQ out
98+ out = OResponse strm rsp' OneshotWithBody
99+ enqueueOutput outputQ out
98100 return ResponseReceived
99101
100102 response404 = responseBuilderBody s hs body
@@ -117,13 +119,8 @@ response ii settings Context{outputQ} mgr tconf th strm req rsp
117119 -- Since 'StreamingBody' is loop, we cannot control it.
118120 -- So, let's serialize 'Builder' with a designated queue.
119121 sq <- newTBQueueIO 10 -- fixme: hard coding: 10
120− tvar <- newTVarIO SyncNone
121− let out = OResponse strm rsp (Persist sq tvar)
122− -- Since we must not enqueue an empty queue to the priority
123− -- queue, we spawn a thread to ensure that the designated
124− -- queue is not empty.
125− void $ forkIO $ waiter tvar sq outputQ
126− atomically $ writeTVar tvar (SyncNext out)
122+ let out = OResponse strm rsp (Persist sq)
123+ enqueueOutput outputQ out
127124 let push b = do
128125 atomically $ writeTBQueue sq (SBuilder b)
129126 T.tickle th
@@ -174,28 +171,6 @@ worker ctx@Context{inputQ,outputQ} set app responder tm = do
174171 Nothing -> return ()
175172 Just e -> S.settingsOnException set (Just req) e
176173
177−waiter :: TVar Sync -> TBQueue Sequence -> PriorityTree Output -> IO ()
178−waiter tvar sq outQ = do
179− -- waiting for actions other than SyncNone
180− mx <- atomically $ do
181− mout <- readTVar tvar
182− case mout of
183− SyncNone -> retry
184− SyncNext out -> do
185− writeTVar tvar SyncNone
186− return $ Just out
187− SyncFinish -> return Nothing
188− case mx of
189− Nothing -> return ()
190− Just out -> do
191− -- ensuring that the streaming queue is not empty.
192− atomically $ do
193− isEmpty <- isEmptyTBQueue sq
194− when isEmpty retry
195− -- ensuring that stream window is greater than 0.
196− enqueueWhenWindowIsOpen outQ out
197− waiter tvar sq outQ
198−
199174----------------------------------------------------------------
200175
201176-- | It would nice if responders could return values to workers.