skip to content

Why does a long-poll server answer empty-handed when its own hold period expires instead of waiting indefinitely?

level: middleimportance: should knowfreq 48%

answer

  1. unanswered is indistinguishable from broken
  2. bound the hold, then answer
  3. shorter than the client's read timeout
  4. shorter than the path's idle limit
  5. idle floor: one exchange per hold

basics

~20 s

Because a request nobody has answered is indistinguishable from a broken path. A bounded hold lets the server return an empty result, release the request and prove the cycle still works; the client re-issues at once.

solid answer

~50 s

An open request that never completes tells neither side anything: the client cannot distinguish quiet from a severed path, and the server cannot tell a live client from one that vanished. So the server bounds the wait — commonly tens of seconds — and on expiry answers with an empty result and the client's current position. Two orderings matter. The hold must be **shorter than the client's read timeout**, or the client tears the request down mid-hold and every cycle ends as a client-side failure. It must also be shorter than the shortest idle limit anything on the path enforces, or the cut arrives as a transport error with no status line and no body. The price of the bound is a floor on idle traffic: `clients / hold period` exchanges per second even when nothing ever happens.

code

http · 11 lines
http
GET /pivot/42/events?since=1733&wait=45 HTTP/1.1
Host: fleet.example.net
Accept: application/json

(45 seconds pass with nothing published)

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 25

{"events":[],"next":1733}

go deeper

for a junior

Know that a long poll comes back empty when the server's hold expires, and that this is normal — the client simply asks again straight away.

for a middle

Explain the ordering: the server's hold must sit under both the client's read timeout and the shortest idle limit on the path, and say what each wrong ordering looks like from the outside.

for a senior

Diagnose from symptoms — an abort with no status and no body points at an idle limit on the path, while an answer written into a request nobody reads points at a read timeout shorter than the hold.

for a principal

Treat the hold as a budget line: it sets the floor on idle traffic for the whole fleet, and it is the number to defend when someone proposes shortening it for responsiveness it does not affect.

The hold period is the one number a long-poll server owns, and almost every operational problem with the technique is that number sitting in the wrong place relative to two others. ## Why the hold is bounded at all An unanswered request is silence, and silence has too many causes. - **The client cannot read it.** No response means either nothing was published or the path is broken; from the client's side those look identical. - **The server cannot read it either.** A client that has been powered off leaves a parked request behind that looks exactly like an attentive one until something is written to it. - **Resources accumulate.** Every parked request holds a registration, a buffer and whatever runtime resource the request occupies; without a bound, the population only ever grows. - **Things on the path have their own opinions.** Intermediaries impose idle limits on connections carrying no bytes, and they enforce them whether or not your server is willing to keep waiting. Answering with an empty result solves all four at once: it frees the request, refreshes both sides' evidence that the other is alive, and does it on the server's schedule rather than an intermediary's. ## Order the three deadlines These are three different timeouts with three different owners, and the order between them is the whole design: 1. **The server's hold period** — how long it parks a request with nothing to say. 2. **The client's read timeout** — how long the client waits for a response before giving up on the request. 3. **The shortest idle limit on the path** — how long anything between them tolerates a connection carrying no bytes. The rule is `hold period < read timeout` and `hold period < shortest idle limit`, with margin on both. Get either backwards and the technique still appears to work, at a cost you only see in the logs. | ordering | what you observe | |---|---| | hold longer than the client's read timeout | every cycle ends as a client-side abort; the server writes its answer into a request nobody is reading, and the event it carried is the one the client never sees | | hold longer than an intermediary's idle limit | the request dies with no status line and no body; the client sees a transport error and cannot tell an empty result from a failure | | hold far shorter than the path allows | the technique still works, but the idle exchange rate climbs toward short polling's without buying the freshness | | hold correctly ordered | quiet periods produce one empty answer per hold per client, and publishes go out about a round trip after they happen | ## What the empty answer costs The bound puts a floor under idle traffic: `clients / hold period` exchanges per second regardless of activity. At a 45-second hold, 5,000 devices generate roughly 110 exchanges a second doing nothing at all. That is far below a tight short poll, and it is not zero — which is why the hold length is an economic choice and not just a safety one. ## Choosing the number - Start below the shortest idle limit you can actually observe on the path, with margin for a slow answer. - Keep it comfortably under the client's read timeout, and set that read timeout deliberately rather than inheriting a default nobody chose. - Make it long enough that the idle exchange rate is affordable at fleet scale. - Make the empty answer explicit — a normal successful response carrying an empty event list and the client's current position — so the client can tell it apart from a failure without guessing. ## What the client does with it It re-issues immediately, with the same position it sent before. An empty answer at hold expiry is the normal cycle, not an error, so there is nothing to recover from and nothing to slow down for. Clients that treat it as a failure — and back away from the server for it — turn a healthy quiet period into a growing delay on the first real update. The distinction to hold onto is that the *server's hold period* and the *client's poll interval* are different things owned by different sides: long polling replaces the interval with the hold, and a design that keeps both is usually one that has not decided which technique it is using.

  • How would you pick the hold length for a fleet?
    Start from the shortest idle limit you can observe anywhere on the path and subtract a margin; keep the result comfortably under the client's read timeout; then check the idle exchange rate it implies — clients divided by the hold — is affordable. Within those bounds, longer is cheaper and no less fresh.
  • What should the client do on an empty answer at hold expiry?
    Re-issue immediately with the same position. It is the ordinary cycle, not a failure, so nothing should be slowed down or escalated for it. Treating it as an error turns a quiet period into delay on the first real update, and inflates error counts that then hide genuine problems.

saying these in an interview costs you the question

  • Sets the server's hold longer than the client's read timeout
  • Treats an empty long-poll answer as an error the client should back off from
  • Assumes a parked request costs the server nothing while it waits
  • Cannot distinguish the server's hold period from the client's poll interval
  • Believes a parked request survives any intermediary as long as TCP is open
  • Waits indefinitely because the specification does not forbid it