skip to content

Your Server-Sent Events emitter is still producing events for viewers who closed the page hours ago — how does it find out?

level: seniorimportance: should knowfreq 46%

answer

  1. nothing comes back from the receiver
  2. the plumbing tells you, not the protocol
  3. a write fails
  4. write interval is detection interval
  5. cancel the subscription, drop the entry

basics

~20 s

Usually only when a write fails. A one-way response carries no message from the receiver, so on a stream that is silent for long stretches the departure surfaces at the next write — which is one more reason to write a periodic comment.

solid answer

~50 s

This protocol defines nothing the receiver can send on an open response: after the request there is no inbound channel at all. What the emitting side gets instead is indirect. The transport may report the connection closed, and over HTTP/2 a client can cancel the stream carrying the response, in which case the server learns quickly. The dependable, portable signal is a **failed write** — which means detection rides the write path, and on a stream that says nothing for twenty minutes the departure stays invisible for twenty minutes. Once it is known, the duty is to release everything the response was holding: cancel the subscription to the source, remove the entry from whatever registry fan-out walks, and free the response's own resources. Otherwise the cost of the feed grows with viewers who have left, not with viewers who are watching.

code

pseudocode · 12 lines
pseudocode
function emit_loop(response, source, subscription, registry):
    loop:
        event = source.next_within(15 seconds)
        if event is none:
            bytes = comment_line("keep-alive")   // nothing to report; still write
        else:
            bytes = render(event)                // ends with a blank line
        if not write_and_flush(response, bytes):
            subscription.cancel()                // stop producing for nobody
            registry.remove(response)            // stop fanning out to it
            release(response)                    // free what it held
            return

go deeper

for a junior

The fact to hold on to: on this protocol the receiver sends nothing after its request, so a server learns about a departure indirectly rather than being told.

for a middle

Explain the mechanism — the failed write, the transport-level close, and a cancelled stream under the newer HTTP versions — and why a silent response delays all of it.

for a senior

Show the operational consequence: the write interval is the detection interval, and cleanup means cancelling the source subscription and removing the registry entry, not just freeing the response.

for a principal

The angle to own is cost that scales with abandoned viewers rather than live ones, and what that implies for how a feed's capacity is planned and measured.

## A one-way response has no back channel The receiving side makes one request and then listens. The protocol gives it nothing to say on that response — no acknowledgement, no unsubscribe, no goodbye. So the natural question, "how does the client tell us it has gone?", has the answer: **it does not, and it cannot**. Everything available to the emitting side is inferred from the transport underneath, not from the protocol on top. ## The signals that actually exist - **A failed write.** The receiver's departure closed the connection; the next attempt to write into it errors. This is the signal that exists everywhere and the one to build on. - **A transport-level report of a closed connection.** Many servers notice a closed socket without writing to it and surface that; useful, and not something to rely on as the only mechanism. - **A cancelled stream under HTTP/2 and HTTP/3.** A client can cancel the one stream that carries the response without closing the whole connection, and the server side learns promptly. | HTTP version | what the departure looks like | how it surfaces | |---|---|---| | HTTP/1.1 | the connection carrying the response is closed | a failed write, or a transport-level close report | | HTTP/2, HTTP/3 | the single stream carrying the response is cancelled, connection intact | a prompt cancellation signal, plus a failed write | The version matters because it changes how fast the news travels, not who sends it. In both cases the emitting side is being told by the plumbing, never by the protocol. ## Why silence delays discovery 1. A viewer closes the page at minute one of a fourteen-hour firing. 2. The next stage change is due at minute twenty-one, so nothing is written for twenty minutes. 3. Nothing is written, so nothing fails. 4. For twenty minutes the emitting side holds a live subscription, a registry entry and a response for an audience of nobody — and keeps rendering events into it. This is the operational argument for the periodic comment that a silent stream should carry anyway: **the write interval is the detection interval**. Writing every fifteen seconds bounds the dead weight to fifteen seconds. It is also why "we only need keep-alives if something in between might cut us" is a weak answer — the emitting side's own bookkeeping depends on writing. ## What is owed once the departure is known - **Stop producing.** Cancel the subscription to the underlying source. A source that is expensive per subscriber — a poll, a query, a downstream stream of its own — is the part that actually hurts. - **Remove the registry entry.** Whatever collection fan-out walks on every publish must no longer contain this response, or every future event pays to render for a receiver that is gone. - **Release the response's resources.** The connection slot, and on a thread-per-request model the thread, are held until the response is finished; finish it. - **Do it once, and do it on every exit path.** The same cleanup must run for a failed write, a cancelled stream, a deliberate end of the response, and an error in the producer — four ways out, one release. ## What it costs to miss it The leak is per-departed-viewer, and departed viewers accumulate. A dashboard opened and abandoned fifty times a day leaves fifty subscriptions and fifty registry entries behind, each of which is walked on every publish and each of which pins whatever its source pins. Fan-out cost stops tracking the audience and starts tracking the history of the audience, which is the version of this bug that shows up as steadily climbing memory and steadily slowing publishes rather than as an outage. ## What an interviewer is checking That you do not expect a message from the receiver; that you can say why detection rides the write path and what that implies for a quiet stream; and that cleanup means the **subscription and the registry entry**, not merely closing the response object. Candidates who have only ever written the happy path say "we close the connection" and stop there, leaving the expensive half of the leak in place.

  • Why does a quiet stream hide a departure for so long?
    Because discovery rides the write path. If nothing is written for twenty minutes, nothing fails for twenty minutes, and the emitting side keeps a subscription and a registry entry alive for a viewer who left at minute one. Writing a comment on an interval bounds that window to the interval.
  • What leaks when a departed receiver goes unnoticed?
    The subscription to the upstream source and whatever it pins, the registry entry that every publish walks to fan out, and the response's own connection slot. The cost grows with viewers who have left rather than with viewers watching, which shows up as climbing memory and slowing publishes.
  • Does closing the response object finish the job?
    No, and this is the common half-fix. Releasing the response frees the connection but leaves the producer running and the registry entry in place, so every future event is still rendered and fanned out for a receiver that is gone. Cancel the subscription and remove the entry too.

saying these in an interview costs you the question

  • Expects the receiver to send a goodbye on the same response.
  • Thinks reading the response will reveal that the client left.
  • Believes a closed page raises an error in the producer immediately.
  • Releases the response but leaves the upstream subscription running.
  • Assumes fan-out cost tracks live viewers rather than registered ones.
  • Relies on a periodic sweep instead of acting on the failed write.