skip to content

While a WebSocket receiver is reassembling a fragmented message, a control frame arrives between two of its fragments - what must the receiver do?

level: middleimportance: should knowfreq 42%

answer

  1. the connection does not go quiet
  2. something small can arrive mid-message
  3. handle it where you found it
  4. it never joins the reassembly buffer
  5. branch on frame kind before appending

basics

~20 s

Handle it immediately and separately. A WebSocket control frame may be injected between the fragments of a message; it never joins the reassembly buffer and never ends the message being reassembled, which resumes with the next fragment.

solid answer

~40 s

The protocol lets an endpoint insert a control frame in the middle of a fragmented message, so a receiver cannot assume everything it reads between the first fragment and the last belongs to that message. The receive loop has to branch on what it read: a control frame is handled there and then - a liveness probe answered, a close honoured - and then discarded, while the reassembly buffer and the message's declared type survive the excursion untouched. A loop that simply appends whatever arrives until the final fragment does two things wrong at once: it corrupts the message with a control frame's payload, and it never answers the probe, so the peer eventually gives up on a connection that was working.

code

pseudocode · 19 lines
pseudocode
buffer = empty
message type = none

loop:
    f = read next frame

    if f is a control frame:
        handle it now (answer a probe, honour a close)
        continue          # never appended, never ends the message

    if message type is none:
        message type = f.type

    append f.payload to buffer

    if f is the final fragment:
        deliver message(message type, buffer)
        buffer = empty
        message type = none

go deeper

for a junior

Know that a message can arrive in fragments and that something else can arrive between them. The unit the application cares about is the whole message, never the individual fragment.

for a middle

Explain the loop: branch on the frame kind first, handle a control frame immediately, and leave the reassembly buffer and declared message type untouched so the next fragment continues the same message.

for a senior

Recognise the production signature - long transfers dropping while short ones survive - and trace it to liveness handled on message boundaries instead of frame boundaries, or to a buffer corrupted by a spliced-in control payload.

for a principal

Decide where this belongs. Reassembly and control dispatch are exactly the wire details an application should not reimplement per service; standardise one receive layer and let product code see only whole messages.

## A fragmented message is not an uninterrupted run of frames A WebSocket message may be sent in one piece or split into several fragments, and a sender splits it because it does not yet know how long the message will be - it is streaming a file, serialising a large structure, or relaying something it is itself still receiving. The receiver's job is to reassemble those fragments into the single application message they represent. The trap is that the connection does not go quiet while that happens. The protocol explicitly permits **control frames to be injected in the middle of a fragmented message**. A control frame is never itself fragmented and carries a small payload, so it can be slipped between two fragments without disturbing them - and it is meant to be, because the alternative is worse. ## Why the protocol allows the interruption Suppose control frames had to wait their turn behind whatever message was in progress. A fragmented message can take minutes - a large upload over a poor link, or a stream the sender is generating as it goes. Everything the connection needs in order to stay healthy would be stuck behind it: - a **liveness probe** could not be sent or answered, so each side would conclude the other was dead while both were perfectly alive; - a **close** could not be initiated, so an endpoint wanting to shut down cleanly would have to abandon the transport instead; - an interruption on either side could only be expressed by dropping the connection. Allowing the injection is what stops a long message from taking the connection's control path hostage. ## What a naive loop gets wrong The loop most people write first reads frames and appends payloads until the final one. Against an interleaved control frame it fails in three distinct ways, and only the first is obvious: 1. **It corrupts the message.** The control frame's payload is concatenated into the reassembly buffer, so the application receives a message with foreign bytes spliced into the middle of it. In a text message that is very likely invalid UTF-8, which then costs the whole connection. 2. **It stalls liveness.** The probe is never recognised, so it is never answered. The peer sees an unanswered probe and closes a connection whose data path was working the whole time. 3. **It can truncate.** A control frame is a complete frame in its own right, so a loop that keys delivery off the final-fragment flag it happens to carry will deliver a short, wrong message and then treat the next real fragment as the start of a new one - corrupting that message too. The third failure is the nastiest in production because it is silent and it cascades: every subsequent message on the connection is off by one fragment. ## The shape that works The receive loop has two independent states and must keep them apart: - **Reassembly state** - the buffer of fragments so far and the message type declared by the first fragment. It is touched only by data fragments. - **Frame dispatch** - a branch, taken first on every frame read, that routes a control frame to its own handler and returns to the loop without altering reassembly state. As rules: handle the control frame where you found it, do not append it, do not let it end the message, and do not reset the buffer. When the next data fragment arrives, reassembly continues exactly where it left off. ## What this means for the application above The application should never see fragments at all. If your own code surfaces them, it is exposing a wire detail the protocol went to some trouble to make invisible: the unit the application reasons about is the **message**, and the fragment boundaries - and the control frames between them - are the receiver's business. Two practical consequences follow: - A test or a monitor that asserts on frame arrivals is asserting on the wrong unit. Fragments are not messages, and their count is not stable. - A receiver that must answer a liveness probe promptly cannot do so from a code path that only runs between messages. If probes are answered on message boundaries rather than frame boundaries, a single long message is enough to make a healthy connection look dead.

  • Why can a control frame be injected mid-message but a second data message cannot?
    Because a control frame is small, never fragmented, and is not part of any message - it can be lifted out of the stream without disturbing reassembly. A second data message would be indistinguishable from the continuation of the one in progress, so the protocol keeps one fragmented message in flight per direction and requires it to finish first.
  • A connection drops during long uploads but never during small ones. How does this explain it?
    A large upload is a long fragmented message. If the receiver answers liveness probes only between messages, the probe sent during the upload goes unanswered for as long as the upload lasts, and the peer closes a connection that was transferring data perfectly. Answering on frame boundaries rather than message boundaries fixes it.

saying these in an interview costs you the question

  • Thinks control frames queue behind a message in progress
  • Appends every frame read until the final fragment
  • Treats each fragment as a separate application message
  • Answers liveness probes only between whole messages
  • Resets the reassembly buffer when a control frame arrives