Build a gRPC-style RPC framework (14 scenes)
Scene 10.5 · Head-of-line blocking and the QUIC fix
HTTP/2 fixed app-layer head-of-line blocking, but all streams share one in-order TCP pipe, so one lost packet stalls them all. QUIC moves streams below the loss boundary.
Previously
Healthy flow-control windows on every stream don't help when the problem is one layer down: because all those streams share a single in-order TCP pipe, one dropped packet holds them all hostage until it's resent.
Scene 10.5
Head-of-line blocking and the QUIC fix
Diagram
Two stacked transports carry the SAME ten greet("Ada") streams. TOP: HTTP/2 over one ordered TCP pipe. BOTTOM: HTTP/3 over QUIC (UDP). A single packet on stream #5 is knocked out. On TCP, in-order delivery means every stream greys out and waits for the one retransmit — that all-streams-wait stall is **head-of-line blocking** at the transport layer. **QUIC** is a transport that runs streams natively over UDP with independent per-stream loss recovery (HTTP/3 is QUIC's HTTP), so the same drop freezes only stream #5 while the other nine keep flowing.
ten greet("Ada") streams, one TCP pipe
Recall from the multiplexing scene that gRPC runs many independent streams down one HTTP/2 connection — here, ten concurrent greet("Ada") calls. We say multiplexing made the streams independent, and at the application layer it did: a slow response no longer blocks the others. But every one of those streams still travels inside a SINGLE TCP connection, and TCP hands the receiver one byte stream that it must deliver strictly in order. Watch what happens when one packet — a packet that happens to belong to stream #5 — is dropped. Because TCP refuses to deliver any later byte until the missing one is resent, all ten streams grey out and wait, even though nine of them never lost a thing. When one stalled item at the front holds up everything queued behind it, that's **head-of-line blocking** — and here it's happening at the transport layer, below the streams HTTP/2 tried to keep independent.
Implementation
Sender.multiplex
ten gRPC streams interleave DATA frames down one connection
1# one HTTP/2 stream per RPC, all on one connection2for frame in interleave(streams): # round-robin3 packet = transport.packetize(frame)4 packet.seq = next_seq() # one ordered seq space5 transport.send(packet)6 # window-bounded; only DATA frames are flow-controlled7 await peer.WINDOW_UPDATE if window == 0
TcpReceiver.deliver
one byte stream, handed up strictly in order
1def on_packet(pkt):2 buffer[pkt.seq] = pkt3 # in-order gate: cannot skip a missing seq4 while buffer.has(next_expected):5 app.deliver(buffer.pop(next_expected))6 next_expected += 17 if gap_at(next_expected):8 stall() # every later stream waits here9 request_retransmit(next_expected)
QuicReceiver.deliver
per-stream reassembly over UDP — one gap, one stall
1def on_packet(pkt): # carries pkt.stream_id2 s = streams[pkt.stream_id]3 s.buffer[pkt.offset] = pkt4 # in-order only WITHIN this stream5 while s.buffer.has(s.next_offset):6 app.deliver(s.stream_id, s.buffer.pop(s.next_offset))7 s.next_offset += len(pkt)8 if gap_at(s.next_offset):9 s.stall() # other streams keep flowing10 request_retransmit(s.stream_id, s.next_offset)