GotchaDifficulty: Advanced2 min

Head-of-Line Blocking: One Lost Segment Stalls Everything Behind It

Data that arrived safely waits in the buffer because TCP must hand it over in order.

Head-of-line blocking is the stall that happens when one TCP segment is lost: every byte behind it has to wait in the receiver's buffer until the missing one is resent, even though those bytes arrived safely, because TCP delivers data only in order. On one connection carrying several independent streams of data, a loss in one stalls all of them.

Picture a web page loading over HTTP/2, which sends many requests over a single TCP connection at once. An image, a script and a stylesheet are downloading together when one segment of the image is lost. The script and the stylesheet stop too, although their segments are sitting in the buffer.

The symptom. On a lossy network, such as a busy mobile connection, everything on a connection pauses together, then resumes in a burst.

The receive buffer with one gap, and the data held behind it

InputFive segments; the second is lost and resent

  1. Segment 2 lost

    1. delivered
    2. missinggap
    3. held
    4. held
    5. held

    Segments 3 to 5 have arrived but cannot be given to the program.

  2. Segment 2 arrives again

    1. delivered
    2. deliveredfilled
    3. delivered
    4. delivered
    5. delivered

    Everything is delivered at once, after one retransmission delay.

ResultData that arrived early waited for data that arrived late.

Why it happens. TCP promises a single ordered stream of bytes. It knows nothing about the requests inside that stream, so it cannot tell that segment 3 belongs to the script and segment 2 to the image. It holds everything after the gap, as its promise requires.

What it costs. Each loss adds at least a round trip for every stream on the connection, not only the one that lost data. On a clean network that is rare; at a few percent loss, HTTP/2 over one connection can be slower than HTTP/1.1 over six.

What to do about it. Within TCP nothing removes it; it is the price of in-order delivery. Applications that cannot afford it spread work over several connections, or move to a transport that orders each stream separately. QUIC, under HTTP/3, does that: each stream has its own ordering, so a loss in the image's stream holds up only the image.