After a WebSocket endpoint has sent its close frame, what may it still do before the connection is finished?
answer
- one direction ends, the other does not
- an exchange, not a single message
- keep reading after you send it
- in-flight messages may still arrive
- the peer answers with its own
basics
~20 sIt may still receive. After sending a close frame an endpoint must not send any further data frames, but it can go on reading until the peer's close frame arrives, so messages already in flight may still arrive.
solid answer
~50 sClosing a WebSocket is a handshake with two halves, not a single act. Once an endpoint has sent its close frame it must not send any more data frames - that direction is finished - but the connection is not, and it can go on reading until the peer's close frame comes back. Anything the peer had already sent, or sends before it sees the close, may still arrive, and a well-behaved receiver delivers those messages rather than discarding them; an endpoint that tears down its reader the instant it sends close simply throws them away. On the other side, an endpoint that receives a close frame without having sent one must send one in response, and may delay doing so until it has finished sending a message already in progress, so a fragmented message in flight can be completed rather than truncated.
code
pseudocode · 13 linesbegin shutdown():
stop accepting outbound messages
send close frame
# from here: no further data frames, ever
loop until peer close received or wait expires:
f = read next frame
if f is a close frame:
break
if f is a data message:
deliver it # in flight before the close was seen
let the server close the transportgo deeper
Know that closing is an exchange: each side sends a close frame and waits for the other's. Sending yours ends your sending direction, not the connection.
Explain the half-closed window: no further data frames may be sent, the reader can stay live until the peer's close arrives, and a message already in progress may be finished before answering.
Show what a premature teardown costs in production - discarded acknowledgements, cut-off messages and endings recorded as abnormal on one side - and put a bounded wait on a peer that never answers.
Make orderly shutdown a property of the platform rather than of each service. Draining connections the same way everywhere is what keeps a routine deploy from producing a wave of ambiguous endings.
## Closing is a handshake, and it has two halves A WebSocket connection is closed by an exchange, not by a single message. One side sends a close frame; the other, on receiving it, sends one back; each side reads until it has seen the other's; then the underlying transport connection is closed. The half worth understanding is the gap in the middle. Between sending your close frame and receiving the peer's, your connection is **half-closed**: your sending direction is finished and your receiving direction is still fully live. ## What the specification actually requires The distinction between what is required, what is recommended and what is merely permitted matters here, because people routinely quote one as another: - After sending a close frame, an endpoint **MUST NOT** send any further data frames. That direction is over, unconditionally. - An endpoint that receives a close frame and has not already sent one **MUST** send one in response. - It **MAY** delay sending that response until it has finished sending a message already in progress - which is what lets a fragmented message complete instead of being truncated midway. - Once an endpoint has both sent and received a close frame, it considers the connection closed and closes the underlying transport connection. The server closes that transport; the client **SHOULD** wait for the server to do so rather than closing first. That asymmetry at the end is the part implementations get wrong by accident: both sides racing to tear down the transport is how a clean close turns into an abnormal one in the records. ## What a premature teardown loses An endpoint that sends its close frame and immediately stops reading - or closes the transport on the spot - discards everything the peer had already put on the wire. Concretely, in a crane cabin console shutting down at end of shift: - The final acknowledgements for commands the console issued seconds earlier are thrown away, so the operator's last actions look unconfirmed when they actually succeeded. - The peer's own close frame is never seen, so the ending is recorded as abnormal on at least one side even though both behaved correctly. - A message the peer had begun sending is cut off rather than completed, because the sender is allowed to finish it and the receiver is no longer there to accept it. None of that is a protocol error. It is the ordinary consequence of treating a handshake as a single message. ## The shape of a correct shutdown 1. Stop accepting new outbound messages from the application and send the close frame. 2. Keep the reader running. Deliver whatever data messages still arrive - they are as valid as any that arrived a second earlier. 3. Stop when the peer's close frame arrives, or when a bounded wait expires, so a peer that never answers cannot hold the connection open forever. 4. Let the server close the transport; the client waits for that rather than racing it. ## The wait has to be bounded Step 3 needs saying out loud, because it is where a careful implementation goes wrong in the opposite direction. Having learned not to tear the reader down, people then wait for the peer's close frame indefinitely - and a peer that has crashed, been suspended, or sits behind something that has silently stopped forwarding will never send one. The connection then sits half-closed forever, holding a file descriptor, a buffer and whatever per-connection state the node keeps, which at scale is exactly the leak a deploy is trying to avoid. So the drain is a bounded one: - Give the peer a deadline measured in seconds, not minutes - it only has to answer a frame it has already been sent. - When the deadline expires, abandon the exchange and close the transport yourself. The ending will be recorded as abnormal, which is honest: it was. - Distinguish the two outcomes in whatever you record. A drain that completed and a drain that timed out look identical from the outside and mean very different things about the peer. ## Why it exists at all Half-close is what makes an orderly shutdown possible on a connection where both ends write independently. Without it, either side deciding to finish would destroy the other side's in-flight work as collateral. With it, the deciding side stops talking, both sides drain what is already on the wire, and the connection ends with both ends agreeing that it ended - which is precisely the difference an operator sees between a clean shutdown and a mystery in the records.
- An endpoint receives a close frame while it is halfway through sending a fragmented message. What may it do?It must send a close frame in response, but it may delay that until it has finished sending the message already in progress, so the fragmented message can be completed rather than truncated. What it must not do is start a new data frame after its own close frame has gone out.
- Why should a client not close the underlying transport connection as soon as the exchange completes?Because the server closes the transport and the client should wait for it. A client that tears it down first can cut off the server's final bytes and turns what both sides did correctly into an abnormal ending on at least one side's records - a needless source of noise in shutdown diagnostics.
saying these in an interview costs you the question
- Stops reading the moment it sends its close frame
- Treats closing as one message rather than an exchange
- Assumes the peer sees the close before its next message
- Truncates a message in progress to answer a close instantly
- Has the client tear down the transport first