skip to content

A sign controller sends one 8 MB WebSocket binary frame; why does a ping control frame wait behind it?

level: seniorimportance: should knowfreq 38%

answer

  1. no way to interrupt a frame
  2. frames are written back to back
  3. gaps exist only between frames
  4. control frames fit in 125 bytes
  5. fragment size bounds the delay

basics

~20 s

A control frame may be sent between the frames of a fragmented message, never inside one. An unfragmented 8 MB frame holds the wire until its last payload byte, so the ping queues behind it. Fragmenting the payload creates the gaps a control frame needs.

solid answer

~40 s

Frames are written end to end with no delimiter, so the next frame's header can only begin after the current frame's final payload byte. One unfragmented 8 MB binary frame therefore occupies the connection for its whole transmission, and a ping — or a close — queued behind it cannot go out until it finishes. On a slow link that is seconds or minutes of apparent silence, which the peer's liveness deadline may read as a dead connection. The protocol's answer is fragmentation: the sender splits the payload into bounded frames, and a control frame is injected **between** them. Control frames are built for exactly this — payload of 125 bytes or less and never fragmented, so one always fits in a gap.

code

pseudocode · 13 lines
pseudocode
function send_large(payload, fragment_size)
    first = true
    for each chunk in split(payload, fragment_size)
        if first
            write_frame(fin = is_last(chunk), opcode = BINARY, chunk)
            first = false
        else
            write_frame(fin = is_last(chunk), opcode = CONTINUATION, chunk)
        flush()
        # a gap: anything queued may go out now, in one frame
        while control_queue is not empty
            write_frame(fin = true, opcode = take(control_queue).opcode, payload <= 125 bytes)
            flush()

go deeper

for a junior

Remember the two limits on a control frame: 125 payload bytes at most, and never fragmented, so a ping or a close is always exactly one small frame.

for a middle

Explain that frames are written back to back with no delimiter, so nothing can be sent until the current frame's declared payload has finished — the gap exists only between frames.

for a senior

Diagnose it from the symptom: only large-payload connections drop, a capture shows an unbroken payload run, and the fix is a bounded fragment size that caps how long a control frame can be blocked.

for a principal

Set the maximum frame size as a cross-service parameter and justify it as a latency budget — it bounds control-frame delay, shutdown time and receiver memory exposure at once.

## Control frames are small and atomic on purpose The specification puts two hard constraints on a control frame — opcode `%x8` close, `%x9` ping, `%xA` pong: - its payload **must be 125 bytes or less**, so it always fits in the short length form and its header is never more than 2 bytes plus a masking key; - it **must not be fragmented**, so it is always exactly one frame with `FIN` set. Together those guarantee that a control frame is a tiny, indivisible unit a receiver can act on the instant it has read it. That is what makes them usable as protocol machinery rather than as data. The third rule is the one this scenario turns on: a control frame **may be injected between the frames of a fragmented message**. It may not be spliced into the middle of a frame — there is no mechanism to interrupt a frame once its header has declared a length, because the bytes that follow are, by definition, that frame's payload. ## Why the ping waits A WebSocket connection is a single ordered byte stream with frames written back to back and no delimiter between them. A receiver locates the next frame's header by counting: two header bytes, a length, that many payload bytes, then the next header. So while an 8 MB frame is being written: 1. The sender has already declared 8 MB of payload in the header it wrote. 2. Every byte it writes from then until byte 8,388,608 is payload of that frame. 3. Any frame it wants to send next — ping, pong, close, another data message — can only start after that last byte. On a link delivering 1 MB/s, that is roughly eight seconds during which nothing else leaves the sender. If the peer's application enforces a liveness deadline shorter than that after an unanswered probe, the connection looks dead while it is in fact perfectly healthy and busy. The mechanism is **occupancy of the wire**, not a timeout in any library, and the fix is to stop occupying it. ## The fix is fragmentation Send the same 8 MB as a run of bounded frames — the first with opcode `%x2` and `FIN` clear, the rest with `%x0`, the last with `FIN` set — and the sender regains a decision point between every pair of frames. At each one it may: - emit a queued ping or pong; - emit a close frame and stop; - continue with the next fragment. Choosing the fragment size is the real engineering judgment, and it is a latency budget: | fragment size | control-frame delay at 1 MB/s | framing overhead on 8 MB | |---|---|---| | unfragmented (8 MB) | up to ~8 s | ~10 bytes | | 1 MB | up to ~1 s | ~80 bytes | | 64 KB | up to ~65 ms | ~1.3 KB | | 4 KB | up to ~4 ms | ~20 KB | The overhead column is negligible until the fragments get genuinely small, so the practical range is wide and the decision is nearly free. What you are buying is a bound on how long a control frame can be stuck behind payload. ## Reading the symptom in production The failure this prevents has a recognisable signature, and it is worth being able to name the mechanism rather than saying "it timed out": - A connection carrying **large payloads** drops, while connections carrying small ones on the same service do not. - The drops correlate with **payload size and link speed**, not with load or with time of day. - A capture shows a long, uninterrupted run of payload bytes and then a close, with the probe that should have been answered never appearing on the wire at all. The same occupancy also delays a **close**: an endpoint that decides to shut down mid-payload cannot send the close frame until the frame in flight completes, so shutdown appears to hang for exactly as long as the remaining payload takes. ## Two adjacent rules worth stating correctly - A control frame may appear between the fragments of a message, and the receiver must be prepared for that — a receiver that assumes the next frame after a fragment is always another fragment of the same message is broken. - Fragmenting does **not** let two data messages interleave. The gaps between one message's fragments are for control frames; a second data message still waits. The summary an interviewer is listening for: the protocol gives you no way to interrupt a frame, so the frame size *is* the maximum time anything else can be blocked, and that makes frame size an operational parameter rather than a detail of your write loop.

  • Why is a control frame capped at 125 payload bytes?
    So it always fits the short length form and is never fragmented — a control frame is therefore a single, tiny, indivisible unit that a receiver can act on the moment it has read it. A cap large enough to need the extended length would reintroduce the occupancy problem inside the mechanism meant to escape it.
  • The peer's liveness probes go unanswered for eight seconds. How do you tell occupancy from a genuinely dead connection?
    Look at what is on the wire. Under occupancy a capture shows a continuous run of payload bytes and the answer arriving immediately after the frame completes; on a dead connection the bytes stop entirely. Payload-size correlation is the other tell: only connections carrying large frames are affected.
  • Does fragmenting a large payload let a second data message start before it finishes?
    No. The gaps between fragments are for control frames only; fragments of two data messages may not be interleaved on one connection unless a negotiated extension defines it. A second message still waits for the first to complete, so large messages remain head-of-line blocking for data.
  • Which frame is delayed besides a ping?
    Any frame at all, including a close. An endpoint that decides to shut down while an 8 MB frame is in flight cannot emit the close frame until the payload's last byte, so shutdown appears to hang for exactly the remaining transfer time — and a peer watching for a clean close sees nothing until then.

A single-lane tunnel with one long convoy in it: nothing can pass it, and an emergency vehicle waits for the last trailer to clear. Split the convoy into short sections with gaps between them and the emergency vehicle slips through at the next gap — but only at a gap, never alongside a moving section.

saying these in an interview costs you the question

  • Says a control frame can be spliced into a frame mid-payload
  • Thinks control frames travel on a separate channel
  • Believes fragmenting lets two data messages interleave
  • Blames a library timeout rather than wire occupancy
  • Assumes a control frame may be fragmented when it is large
  • Picks frame size only by memory, never by latency