Flow control: a slow reader slows the writer
HTTP/2 gives the receiver a window of credit measured in octets; the sender may only send DATA up to that credit and must wait for a WINDOW_UPDATE — so a slow consumer applies backpressure instead of letting the producer buffer unboundedly.
Now that calls spread across backends, picture one server-streaming RPC firing Greetings faster than the client can read them — without a brake the producer buffers until it runs out of memory, so HTTP/2 hands the reader a way to push back.
Scene 10
Flow control: a slow reader slows the writer
- Watch
- Try it
- Predict
- Capture
A server-streaming RPC is running: the server keeps sending Greeting responses for one greet("Ada") call, each carried in a DATA frame (the frame type from scene 4 that holds the payload bytes). The new idea is the gauge under the pipe. The receiver advertises how many bytes it is willing to accept right now — a running balance of credit measured in octets. We call this credit balance the receiver's flow-control window. Every DATA frame the server sends spends credit and drains the gauge; as the client reads frames it sends a WINDOW_UPDATE frame back to grant more credit and refill the gauge. Watch the window drain and refill while the client keeps up — DATA flows steadily because credit keeps being replenished.
Highlighted lines are the ones running in the diagram right now.
def sendData(stream, payload):frame = DataFrame(payload) # only DATA is flow-controlled# block until this stream has credit for the whole framewhile stream.window < frame.octets:wait_for(WINDOW_UPDATE) # the backpressure pointstream.window -= frame.octetsconn.window -= frame.octets # per-connection level tooconn.write(frame)
def onData(stream, frame):stream.recvBuffer.append(frame)stream.window -= frame.octets # gauge drainsdef onAppRead(stream, n_octets):# the app consuming bytes is what frees creditgrant = WindowUpdate(stream.id, n_octets)conn.write(grant) # refills the sender's window
def onWindowUpdate(frame):if frame.stream_id == 0:conn.window += frame.increment # connection levelelse:stream(frame.stream_id).window += frame.increment# any sender parked in sendData's wait loop wakes herewake_waiters()
Where this sits in Build a gRPC-style RPC framework
Scene 10 of 14, in the Resilience act — Client-side balancing, backpressure, head-of-line blocking.. The receiver advertises a window of credit; the sender may only send DATA up to it. A slow reader stops granting credit, so the producer pauses instead of OOMing.
Up next. This keeps one stream's writer polite to its reader — but all our streams still share ONE TCP connection, and a single lost packet on that connection can freeze every stream at once, no matter how healthy their windows are.
All 14 scenes in Build a gRPC-style RPC framework · Every curriculum