on master help sign in
Merge pull request #423 from yesodweb/refactor-http2

Refactor http2

commit9c4ac8904e4b6f9af30f93da3fa087de8cc2d913 authorKazu Yamamoto <kazu@iij.ad.jp> dateThu 13 Aug 2015, 11:11 (11 years ago) parent615e384c6bac47
4 files changed+140−100
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+15−5 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+72−74 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+31−2 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+22−19
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+15−5
@@ -92,7 +92,7 @@ frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
9292 control ftyp header pl ctx
9393 | otherwise = do
9494 checkContinued
95− strm@Stream{streamState,streamContentLength} <- getStream
95+ strm@Stream{streamState,streamContentLength,streamPriority} <- getStream
9696 pl <- recvN payloadLength
9797 state <- readIORef streamState
9898 state' <- stream ftyp header pl ctx state strm
@@ -103,21 +103,23 @@ frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
103103 Just vh -> do
104104 when (isJust (vhCL vh) && vhCL vh /= Just 0) $
105105 E.throwIO $ StreamError ProtocolError streamId
106+ writeIORef streamPriority pri
106107 writeIORef streamState HalfClosed
107108 let req = mkreq vh (return "")
108− atomically $ writeTQueue inputQ $ Input strm req pri
109+ atomically $ writeTQueue inputQ $ Input strm req
109110 Nothing -> E.throwIO $ StreamError ProtocolError streamId
110111 Open (HasBody hdr pri) -> do
111112 resetContinued
112113 case validateHeaders hdr of
113114 Just vh -> do
114115 q <- newTQueueIO
116+ writeIORef streamPriority pri
115117 writeIORef streamState (Open (Body q))
116118 writeIORef streamContentLength $ vhCL vh
117119 readQ <- newReadBody q
118120 bodySource <- mkSource readQ
119121 let req = mkreq vh (readSource bodySource)
120− atomically $ writeTQueue inputQ $ Input strm req pri
122+ atomically $ writeTQueue inputQ $ Input strm req
121123 Nothing -> E.throwIO $ StreamError ProtocolError streamId
122124 s@(Open Continued{}) -> do
123125 setContinued
@@ -314,10 +316,18 @@ stream FrameRSTStream header bs ctx _ strm = do
314316 closed ctx strm cc
315317 return $ Closed cc -- will be written to streamState again
316318
317−stream FramePriority header bs Context{outputQ} s Stream{streamNumber} = do
319+stream FramePriority header bs Context{outputQ} s Stream{streamNumber,streamPriority} = do
318320 PriorityFrame p <- guardIt $ decodePriorityFrame header bs
319321 checkPriority p streamNumber
320− prepare outputQ streamNumber p
322+ -- checkme: this should be tested
323+ -- fixme: This works well when the priority gets lower because
324+ -- the old higher priority value comes out from the queue quickly
325+ -- and the new lower priority is used when enqueuing again.
326+ -- But when the priority get higher, it takes time to use the new
327+ -- priority.
328+ writeIORef streamPriority p
329+ -- checkme: this should be tested
330+ when (isIdle s) $ prepare outputQ streamNumber p
321331 return s
322332
323333-- this ordering is important
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+72−74
@@ -7,7 +7,6 @@ module Network.Wai.Handler.Warp.HTTP2.Sender (frameSender) where
77#if __GLASGOW_HASKELL__ < 709
88import Control.Applicative
99#endif
10−import Control.Concurrent (forkIO)
1110import Control.Concurrent.STM
1211import qualified Control.Exception as E
1312import Control.Monad (unless, void, when)
@@ -58,22 +57,16 @@ unlessClosed ctx
5857 let rst = resetFrame InternalError streamNumber
5958 connSendAll rst
6059
61−checkWindowSize :: TVar WindowSize -> TVar WindowSize -> PriorityTree Output -> Output -> Priority -> (WindowSize -> IO ()) -> IO ()
62−checkWindowSize connWindow strmWindow outQ out pri body = do
60+getWindowSize :: TVar WindowSize -> TVar WindowSize -> IO WindowSize
61+getWindowSize connWindow strmWindow = do
62+ -- Waiting that the connection window gets open.
6363 cw <- atomically $ do
6464 w <- readTVar connWindow
6565 check (w > 0)
6666 return w
67+ -- This stream window is greater than 0 thanks to the invariant.
6768 sw <- atomically $ readTVar strmWindow
68− if sw == 0 then
69− void $ forkIO $ do
70− -- checkme: if connection is closed? GC throws dead lock error?
71− atomically $ do
72− x <- readTVar strmWindow
73− check (x > 0)
74− enqueue outQ out pri
75− else
76− body (min cw sw)
69+ return $ min cw sw
7770
7871frameSender :: Context -> Connection -> InternalInfo -> S.Settings -> IO ()
7972frameSender ctx@Context{outputQ,connectionWindow}
@@ -89,59 +82,60 @@ frameSender ctx@Context{outputQ,connectionWindow}
8982 connSendAll initialFrame
9083 loop
9184
92− loop = dequeue outputQ >>= \(out, pri) -> switch out pri
85+ -- ignoring the old priority because the value might be changed.
86+ loop = dequeue outputQ >>= \(out, _) -> switch out
9387
9488 ignore :: E.SomeException -> IO ()
9589 ignore _ = return ()
9690
97− switch OFinish _ = return ()
98− switch (OGoaway frame) _ = connSendAll frame
99− switch (OFrame frame) _ = do
91+ switch OFinish = return ()
92+ switch (OGoaway frame) = connSendAll frame
93+ switch (OFrame frame) = do
10094 connSendAll frame
10195 loop
102− switch out@(OResponse strm rsp aux) pri = unlessClosed ctx conn strm $ do
103− checkWindowSize connectionWindow (streamWindow strm) outputQ out pri $ \lim -> do
104− -- Header frame and Continuation frame
105− let sid = streamNumber strm
106− endOfStream = case aux of
107− Persist{} -> False
108− Oneshot hb -> not hb
109− len <- headerContinue sid rsp endOfStream
110− let total = len + frameHeaderLength
111− case aux of
112− Oneshot True -> do -- hasBody
113− -- Data frame payload
114− (off, _) <- sendHeadersIfNecessary total
115− let payloadOff = off + frameHeaderLength
116− Next datPayloadLen mnext <-
117− fillResponseBodyGetNext conn ii payloadOff lim rsp
96+ switch (OResponse strm rsp aux) = unlessClosed ctx conn strm $ do
97+ lim <- getWindowSize connectionWindow (streamWindow strm)
98+ -- Header frame and Continuation frame
99+ let sid = streamNumber strm
100+ endOfStream = case aux of
101+ Persist{} -> False
102+ Oneshot hb -> not hb
103+ len <- headerContinue sid rsp endOfStream
104+ let total = len + frameHeaderLength
105+ case aux of
106+ Oneshot True -> do -- hasBody
107+ -- Data frame payload
108+ (off, _) <- sendHeadersIfNecessary total
109+ let payloadOff = off + frameHeaderLength
110+ Next datPayloadLen mnext <-
111+ fillResponseBodyGetNext conn ii payloadOff lim rsp
112+ fillDataHeaderSend strm total datPayloadLen mnext
113+ maybeEnqueueNext strm mnext
114+ Oneshot False -> do
115+ -- "closed" must be before "connSendAll". If not,
116+ -- the context would be switched to the receiver,
117+ -- resulting the inconsistency of concurrency.
118+ closed ctx strm Finished
119+ flushN total
120+ Persist sq tvar -> do
121+ (off, needSend) <- sendHeadersIfNecessary total
122+ let payloadOff = off + frameHeaderLength
123+ Next datPayloadLen mnext <-
124+ fillStreamBodyGetNext conn payloadOff lim sq tvar strm
125+ -- If no data was immediately available, avoid sending an
126+ -- empty data frame.
127+ if datPayloadLen > 0 then
118128 fillDataHeaderSend strm total datPayloadLen mnext
119− maybeEnqueueNext strm mnext pri
120− Oneshot False -> do
121− -- "closed" must be before "connSendAll". If not,
122− -- the context would be switched to the receiver,
123− -- resulting the inconsistency of concurrency.
124− closed ctx strm Finished
125− flushN total
126− Persist sq tvar -> do
127− (off, needSend) <- sendHeadersIfNecessary total
128− let payloadOff = off + frameHeaderLength
129− Next datPayloadLen mnext <-
130− fillStreamBodyGetNext conn payloadOff lim sq tvar
131− -- If no data was immediately available, avoid sending an
132− -- empty data frame.
133− if datPayloadLen > 0 then
134− fillDataHeaderSend strm total datPayloadLen mnext
135− else
136− when needSend $ flushN off
137− maybeEnqueueNext strm mnext pri
129+ else
130+ when needSend $ flushN off
131+ maybeEnqueueNext strm mnext
138132 loop
139− switch out@(ONext strm curr) pri = unlessClosed ctx conn strm $ do
140− checkWindowSize connectionWindow (streamWindow strm) outputQ out pri $ \lim -> do
141− -- Data frame payload
142− Next datPayloadLen mnext <- curr lim
143− fillDataHeaderSend strm 0 datPayloadLen mnext
144− maybeEnqueueNext strm mnext pri
133+ switch (ONext strm curr) = unlessClosed ctx conn strm $ do
134+ lim <- getWindowSize connectionWindow (streamWindow strm)
135+ -- Data frame payload
136+ Next datPayloadLen mnext <- curr lim
137+ fillDataHeaderSend strm 0 datPayloadLen mnext
138+ maybeEnqueueNext strm mnext
145139 loop
146140
147141 -- Flush the connection buffer to the socket, where the first 'n' bytes of
@@ -183,12 +177,15 @@ frameSender ctx@Context{outputQ,connectionWindow}
183177 canFitDataFrame total = total + frameHeaderLength < connBufferSize
184178
185179 -- Re-enqueue the stream in the output queue if more output is immediately
186− -- available; do nothing otherwise. If the stream is not finished, it must
187− -- already have been written to the 'TVar' owned by 'waiter', which will
180+ -- available; do nothing otherwise.
181+ maybeEnqueueNext :: Stream -> Control DynaNext -> IO ()
182+ maybeEnqueueNext strm (CNext next) = do
183+ let out = ONext strm next
184+ enqueueOrSpawnTemporaryWaiter strm outputQ out
185+ -- If the streaming is not finished, it must already have been
186+ -- written to the 'TVar' owned by 'waiter', which will
188187 -- put it back into the queue when more output becomes available.
189− maybeEnqueueNext :: Stream -> Control DynaNext -> Priority -> IO ()
190− maybeEnqueueNext strm (CNext next) = enqueue outputQ (ONext strm next)
191− maybeEnqueueNext _ _ = const $ return ()
188+ maybeEnqueueNext _ _ = return ()
192189
193190
194191 -- Send headers if there is not room for a 1-byte data frame, and return
@@ -282,13 +279,13 @@ fileStartEnd _ (Just part) =
282279
283280----------------------------------------------------------------
284281
285−fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> TVar Sync -> IO Next
282+fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> TVar Sync -> Stream -> IO Next
286283fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
287− off lim sq tvar = do
284+ off lim sq tvar strm = do
288285 let datBuf = connWriteBuffer `plusPtr` off
289286 room = min (connBufferSize - off) lim
290287 (leftover, cont, len) <- runStreamBuilder datBuf room sq
291− nextForStream connWriteBuffer connBufferSize sq tvar leftover cont len
288+ nextForStream connWriteBuffer connBufferSize sq tvar strm leftover cont len
292289
293290----------------------------------------------------------------
294291
@@ -342,8 +339,8 @@ runStreamBuilder buf0 room0 sq = loop buf0 room0 0
342339 Just SFlush -> return (LZero, True, total)
343340 Just SFinish -> return (LZero, False, total)
344341
345−fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> TVar Sync -> DynaNext
346−fillBufStream buf0 siz0 leftover0 sq tvar lim0 = do
342+fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> TVar Sync -> Stream -> DynaNext
343+fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
347344 let payloadBuf = buf0 `plusPtr` frameHeaderLength
348345 room0 = min (siz0 - frameHeaderLength) lim0
349346 case leftover0 of
@@ -361,7 +358,7 @@ fillBufStream buf0 siz0 leftover0 sq tvar lim0 = do
361358 void $ copy payloadBuf bs1
362359 getNext (LTwo bs2 writer) True room0
363360 where
364− getNext = nextForStream buf0 siz0 sq tvar
361+ getNext = nextForStream buf0 siz0 sq tvar strm
365362 write writer1 buf room sofar = do
366363 (len, signal) <- writer1 buf room
367364 case signal of
@@ -376,17 +373,18 @@ fillBufStream buf0 siz0 leftover0 sq tvar lim0 = do
376373 let !total = sofar + len
377374 getNext (LTwo bs writer) True total
378375
379−nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> TVar Sync
376+nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> TVar Sync -> Stream
380377 -> Leftover -> Bool -> BytesFilled
381378 -> IO Next
382−nextForStream _ _ _ tvar _ False len = do
379+nextForStream _ _ _ tvar _ _ False len = do
383380 atomically $ writeTVar tvar SyncFinish
384381 return $ Next len CFinish
385−nextForStream buf siz sq tvar LZero True len = do
386− atomically $ writeTVar tvar $ SyncNext (fillBufStream buf siz LZero sq tvar)
382+nextForStream buf siz sq tvar strm LZero True len = do
383+ let out = ONext strm (fillBufStream buf siz LZero sq tvar strm)
384+ atomically $ writeTVar tvar $ SyncNext out
387385 return $ Next len CNone
388−nextForStream buf siz sq tvar leftover True len =
389− return $ Next len (CNext (fillBufStream buf siz leftover sq tvar))
386+nextForStream buf siz sq tvar strm leftover True len =
387+ return $ Next len (CNext (fillBufStream buf siz leftover sq tvar strm))
390388
391389----------------------------------------------------------------
392390
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+31−2
@@ -7,6 +7,7 @@ import Data.ByteString.Builder (Builder)
77#if __GLASGOW_HASKELL__ < 709
88import Control.Applicative ((<$>),(<*>))
99#endif
10+import Control.Concurrent (forkIO)
1011import Control.Concurrent.STM
1112import Control.Exception (SomeException)
1213import Control.Monad (void)
@@ -39,7 +40,7 @@ isHTTP2 tls = useHTTP2
3940
4041----------------------------------------------------------------
4142
42−data Input = Input Stream Request Priority
43+data Input = Input Stream Request
4344
4445----------------------------------------------------------------
4546
@@ -64,6 +65,11 @@ data Output = OFinish
6465 | OResponse Stream Response Aux
6566 | ONext Stream DynaNext
6667
68+outputStream :: Output -> Stream
69+outputStream (OResponse strm _ _) = strm
70+outputStream (ONext strm _) = strm
71+outputStream _ = error "outputStream"
72+
6773----------------------------------------------------------------
6874
6975data Sequence = SFinish
@@ -72,7 +78,7 @@ data Sequence = SFinish
7278
7379data Sync = SyncNone
7480 | SyncFinish
75− | SyncNext DynaNext
81+ | SyncNext Output
7682
7783data Aux = Oneshot Bool
7884 | Persist (TBQueue Sequence) (TVar Sync)
@@ -170,6 +176,7 @@ data Stream = Stream {
170176 , streamContentLength :: IORef (Maybe Int)
171177 , streamBodyLength :: IORef Int
172178 , streamWindow :: TVar WindowSize
179+ , streamPriority :: IORef Priority
173180 }
174181
175182instance Show Stream where
@@ -180,6 +187,7 @@ newStream sid win = Stream sid <$> newIORef Idle
180187 <*> newIORef Nothing
181188 <*> newIORef 0
182189 <*> newTVarIO win
190+ <*> newIORef defaultPriority
183191
184192----------------------------------------------------------------
185193
@@ -227,3 +235,24 @@ insert strmtbl k v = reaperAdd strmtbl (k,v)
227235
228236search :: StreamTable -> M.Key -> IO (Maybe Stream)
229237search strmtbl k = M.lookup k <$> reaperRead strmtbl
238+
239+
240+-- INVARIANT: streams in the output queue have non-zero window size.
241+enqueueWhenWindowIsOpen :: PriorityTree Output -> Output -> IO ()
242+enqueueWhenWindowIsOpen outQ out = do
243+ let strm = outputStream out
244+ atomically $ do
245+ x <- readTVar $ streamWindow strm
246+ check (x > 0)
247+ pri <- readIORef $ streamPriority strm
248+ enqueue outQ out pri
249+
250+enqueueOrSpawnTemporaryWaiter :: Stream -> PriorityTree Output -> Output -> IO ()
251+enqueueOrSpawnTemporaryWaiter strm outQ out = do
252+ sw <- atomically $ readTVar $ streamWindow strm
253+ if sw == 0 then
254+ -- This waiter waits only for the stream window.
255+ void $ forkIO $ enqueueWhenWindowIsOpen outQ out
256+ else do
257+ pri <- readIORef $ streamPriority strm
258+ enqueue outQ out pri
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+22−19
@@ -36,14 +36,14 @@ import Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceive
3636-- | The wai definition is 'type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived'.
3737-- This type implements the second argument (Response -> IO ResponseReceived)
3838-- with extra arguments.
39−type Responder = ThreadContinue -> T.Handle -> Stream -> Priority -> Request ->
39+type Responder = ThreadContinue -> T.Handle -> Stream -> Request ->
4040 Response -> IO ResponseReceived
4141
4242-- | This function is passed to workers.
4343-- They also pass 'Response's from 'Application's to this function.
4444-- This function enqueues commands for the HTTP/2 sender.
4545response :: Context -> Manager -> Responder
46−response Context{outputQ} mgr tconf th strm pri req rsp = do
46+response Context{outputQ} mgr tconf th strm req rsp = do
4747 case rsp of
4848 ResponseStream _ _ strmbdy -> do
4949 -- We must not exit this WAI application.
@@ -59,22 +59,24 @@ response Context{outputQ} mgr tconf th strm pri req rsp = do
5959 -- So, let's serialize 'Builder' with a designated queue.
6060 sq <- newTBQueueIO 10 -- fixme: hard coding: 10
6161 tvar <- newTVarIO SyncNone
62− enqueue outputQ (OResponse strm rsp (Persist sq tvar)) pri
62+ let out = OResponse strm rsp (Persist sq tvar)
63+ -- Since we must not enqueue an empty queue to the priority
64+ -- queue, we spawn a thread to ensure that the designated
65+ -- queue is not empty.
66+ void $ forkIO $ waiter tvar sq outputQ
67+ atomically $ writeTVar tvar (SyncNext out)
6368 let push b = do
6469 atomically $ writeTBQueue sq (SBuilder b)
6570 T.tickle th
6671 flush = atomically $ writeTBQueue sq SFlush
67− -- Since we must not enqueue an empty queue to the priority
68− -- queue, we spawn a thread to ensure that the designated
69− -- queue is not empty.
70− void $ forkIO $ waiter tvar sq (enqueue outputQ) strm pri
7172 strmbdy push flush
7273 atomically $ writeTBQueue sq SFinish
7374 _ -> do
7475 setThreadContinue tconf True
7576 let hasBody = requestMethod req /= H.methodHead
7677 && R.hasBody (responseStatus rsp)
77− enqueue outputQ (OResponse strm rsp (Oneshot hasBody)) pri
78+ out = OResponse strm rsp (Oneshot hasBody)
79+ enqueueOrSpawnTemporaryWaiter strm outputQ out
7880 return ResponseReceived
7981
8082data Break = Break deriving (Show, Typeable)
@@ -93,11 +95,11 @@ worker ctx@Context{inputQ,outputQ} set tm app responder = do
9395 setThreadContinue tcont True
9496 ex <- E.try $ do
9597 T.pause th
96− Input strm req pri <- atomically $ readTQueue inputQ
98+ Input strm req <- atomically $ readTQueue inputQ
9799 setStreamInfo sinfo strm req
98100 T.resume th
99101 T.tickle th
100− app req $ responder tcont th strm pri req
102+ app req $ responder tcont th strm req
101103 cont1 <- case ex of
102104 Right ResponseReceived -> return True
103105 Left e@(SomeException _)
@@ -126,26 +128,27 @@ worker ctx@Context{inputQ,outputQ} set tm app responder = do
126128 Just e -> S.settingsOnException set (Just req) e
127129 clearStreamInfo sinfo
128130
129−waiter :: TVar Sync -> TBQueue Sequence
130− -> (Output -> Priority -> IO ()) -> Stream -> Priority
131− -> IO ()
132−waiter tvar sq enq strm pri = do
131+waiter :: TVar Sync -> TBQueue Sequence -> PriorityTree Output -> IO ()
132+waiter tvar sq outQ = do
133+ -- waiting for actions other than SyncNone
133134 mx <- atomically $ do
134135 mout <- readTVar tvar
135136 case mout of
136137 SyncNone -> retry
137− SyncNext nxt -> do
138+ SyncNext out -> do
138139 writeTVar tvar SyncNone
139− return $ Just nxt
140+ return $ Just out
140141 SyncFinish -> return Nothing
141142 case mx of
142143 Nothing -> return ()
143− Just next -> do
144+ Just out -> do
145+ -- ensuring that the streaming queue is not empty.
144146 atomically $ do
145147 isEmpty <- isEmptyTBQueue sq
146148 when isEmpty retry
147− enq (ONext strm next) pri
148− waiter tvar sq enq strm pri
149+ -- ensuring that stream window is greater than 0.
150+ enqueueWhenWindowIsOpen outQ out
151+ waiter tvar sq outQ
149152
150153----------------------------------------------------------------
151154