Learning objective. Read a real loss-and-recovery trace end to end (confirming that reliability held, that loss was detected promptly, and that the congestion window responded the way Chapter 3 says it should) so you can validate recovery on your own captures.
The lab: induce loss, capture the sender's view #
Everything in this chapter is observable. The lab in labs/03-5-loss-recovery/ runs a
loopback transfer where the server streams 256 KB to the client but drops about one in
eight of its own datagrams after the handshake. QUIC must still deliver every byte, and
the server (the sender here) is the endpoint that runs loss detection and congestion control
(§3.1–§3.3), so its qlog records the whole story. Run it with the
pinned toolchain:
uv run labs/03-5-loss-recovery/loss_recovery.py
Its genuine output (numbers vary a little per run):
payload: 262144 bytes
transfer: 262144 bytes received -> complete (despite induced loss)
induced drops: 37 of 298 server datagrams (~1 in 8)
sender qlog: [server] 299 packets sent, 37 declared lost + retransmitted
congestion window (sender) — the sawtooth of loss recovery:
initial: 12000 B (= kInitialWindow, 10 x max_datagram_size)
peak: 25660 B (grown by slow start / congestion avoidance)
min: 2400 B (halved into recovery on a congestion event)
smoothed_rtt: 0.61 -> 1.13 ms (loopback)
Reading it against the chapter #
Four things in that output validate that recovery worked exactly as designed:
- Reliability held. All 262 144 bytes arrived even though 37 datagrams were dropped. The lost frames were resent in new packets and delivered (§3.1). This is the headline check: a correct QUIC stack never loses stream data to packet loss.
- Loss was detected, not guessed. The sender logged 37
recovery:packet_lostevents (one per dropped datagram), driven by the ACK gaps the client reported, not by blind timeouts. With loss this frequent, threshold detection (§3.1) does the work and the PTO rarely fires. - The window shows the sawtooth. It starts at exactly 12000 B —
kInitialWindow, 10 × the 1200-byte max datagram size (constants.md§8). It grows to a peak while ACKs flow, and each detected loss halves it into recovery (§3.3). Under sustained one-in-eight loss it bottoms at 2400 B, which iskMinimumWindow(2 × max_datagram_size): the controller refuses to shrink below that floor. - RTT stayed sane.
smoothed_rttis sub-millisecond (loopback), confirming the RTT estimator tracked the path rather than being thrown off by the retransmissions.
What "healthy recovery" looks like #
On a real capture, the same four signals tell you whether recovery is behaving. Loss
events should track actual drops, not exceed them (excess means spurious
retransmissions: the sender resending packets that were only reordered,
§3.4). Time from a loss to its retransmission should sit near one RTT, not
one PTO. A PTO-length gap means threshold detection failed to fire, usually because ACKs
stopped. And the congestion window should recover between losses rather than flatlining at
kMinimumWindow. A window pinned at the floor means the path is in or near persistent
congestion (§3.2).
editorial Load a qlog into qvis (qvis.quictools.info, run locally for offline safety) and open the congestion view to see this window trajectory as an actual sawtooth over time, with loss events marked. Far easier to read than raw JSON. When a transfer is "slow," this view answers the first question immediately: is the sender loss-limited (deep, frequent sawtooth), RTT-limited (window fine, throughput still low), or application-limited (window never fills)? Chapter 11 (§11.3) builds this into a repeatable measurement workflow.
Takeaways #
A recovery trace is validated by four checks: all data arrived, losses were detected from
ACKs rather than timeouts, the congestion window shows a halving-and-recovery sawtooth
bounded by kMinimumWindow, and RTT estimates stayed stable. You have now seen QUIC detect
and recover from loss on a real connection, closing the reliability chapter.
Chapter 4 turns from whether data arrives to how it is organized —
streams, flow control, and multiplexing for performance.