Learning objective. Understand how flow control creates backpressure that buffers data (exactly the wrong response for real-time traffic) and why unreliable datagrams, which have no flow control, push the sender to shed stale data instead, keeping the stream fresh.
Backpressure buffers; real-time wants to shed #
Flow control (§7.2) exists to stop a fast sender from overwhelming a slow receiver: when
the receiver's window fills, the sender is blocked and must hold data until a MAX_DATA or
MAX_STREAM_DATA grants more credit. For a file transfer this backpressure is exactly right: the
sender pauses, nothing is lost, and the transfer resumes. For real-time media it is a trap. A blocked
sender does not discard the media it cannot send; it queues it, and that queue is pure added
latency: a growing backlog of frames that are already stale by the time they leave. This is
application-level bufferbloat: the data all arrives, later and later, precisely when the
application needed it to arrive now or not at all.
Why DATAGRAM frames deliberately omit flow control #
The DATAGRAM extension makes a pointed design choice: datagrams "do not provide any explicit flow
control signaling and do not contribute to any per-flow or connection-wide data limit"
([RFC 9221 §5.3]). There is no window to fill, no MAX_DATA, no backpressure from the receiver at all.
That sounds dangerous — what stops a flood? — but two other mechanisms remain, and the omission is
what makes datagrams right for real-time:
- Congestion control still applies. Datagrams "employ the QUIC connection's congestion controller" ([RFC 9221 §5.4]), so the sender is still bounded by what the path can carry; it cannot overrun the network. What is gone is the receiver-driven window that would make the sender queue.
- The receiver may simply drop. "Since DATAGRAM frames are inherently unreliable, they MAY be dropped by the receiver if the receiver cannot process them" ([RFC 9221 §5.3]). Overload is handled by discarding, not by signalling the sender to hold. No queue forms on either side.
So the absence of flow control moves the overload decision to where the application knows best: the sender, at the moment of sending, with full knowledge of which frames are still worth sending.
Shedding at the source #
When the congestion controller will not allow a datagram, RFC 9221 is explicit that the sender must not let a backlog build: it "MUST either delay sending the frame until the controller allows it or drop the frame without sending it" ([RFC 9221 §5.4]). And it may honour an application-set deadline: implementations can let the application "specify a sending expiration time beyond which a congestion-controlled DATAGRAM frame ought to be dropped without transmission" ([RFC 9221 §5.4]). For real-time this is the whole game: drop the frame that missed its deadline so the next frame goes out on time. A shallow, self-pruning send queue keeps latency bounded; a deep FIFO queue guarantees the opposite.
The same discipline applies even when you do use a reliable stream for real-time-ish data. A large
flow-control window lets the sender dump seconds of data that then sits queued in the transport,
invisible and stale. Keeping the window and the application's own send buffer small bounds how much
stale data can accumulate: you trade a little throughput headroom for tight latency coupling. And a
sender that is flow-control blocked with nothing ack-eliciting in flight should still emit a
STREAM_DATA_BLOCKED/DATA_BLOCKED signal so the connection does not look idle (§7.2,
§7.3). But the real fix is not to be generating data faster than the path drains.
Worked example: a webcam under a bandwidth drop #
A conferencing app is sending 30 fps video when the path bandwidth halves. Trace both responses in Figure 9.4-1. On a reliable stream, the flow-control window fills; the sender blocks and the encoder's output queues up. A second later the connection is still shipping frames from a second ago. The participant sees smooth-but-delayed video that drifts further behind real time with every frame: the call has silently added a second of latency. With datagrams, the sender sees the congestion controller tighten and drops the frames it cannot send within their deadline, shipping only the newest; combined with the encoder dropping its bitrate, the participant sees a brief quality dip but stays in sync. The reliable path preserved every frame and lost the conversation; the datagram path lost a few frames and kept it.
"No flow control" is not "no limit." A datagram sender that ignores congestion control, or that queues frames instead of dropping them, re-creates exactly the bufferbloat it was trying to escape — now without reliability. The correct pattern is a tight loop: offer a frame, let the transport send it only if the congestion window allows right now, and drop it otherwise. The transport enforces the network limit (congestion control); the application enforces the time limit (the deadline). Both are required.
editorial Manage the queue you can see. Keep the send-side buffer small enough that it cannot hold more than a frame or two of latency, and give datagrams an expiration deadline so the transport prunes stale ones for you. For reliable real-time-ish streams, resist the urge to enlarge the flow-control window "for throughput": on a real-time flow a big window is a big latency reservoir. The signature of getting this wrong is a stream that stays smooth but drifts steadily further behind live; the fix is upstream (encode less, drop sooner), never a bigger buffer.
Takeaways #
Flow control turns overload into backpressure, and backpressure into a queue of stale data: the wrong answer for real-time, where late data is useless. DATAGRAM frames omit flow control by design: the sender stays bounded by congestion control but must shed stale frames at the source (drop, don't queue, honouring a deadline), and the receiver may drop what it cannot process. The same "small buffers, drop early" discipline keeps even reliable streams from becoming latency reservoirs. Putting the whole chapter together into a tuned media profile is §9.5.