patterntypescriptModerate
Long Polling Fallback Must Handle Concurrent Requests and Timeouts
Viewed 0 times
long pollingfallbackcomethold timeasyncthundering herdreal-time fallback
Problem
Long polling is used as a WebSocket fallback but the server holds connections open indefinitely, consuming a thread/connection per client. Under load, the server runs out of file descriptors or threads.
Solution
Set a maximum hold time (20–30 seconds) after which the server responds with an empty result and the client immediately re-polls. Use async I/O (Node.js event loop) so held connections do not block threads.
// Server: async long poll with timeout
app.get('/poll', async (req, res) => {
const { userId, since } = req.query;
const TIMEOUT = 25_000;
const events = await getEventsSince(userId as string, Number(since));
if (events.length > 0) {
return res.json({ events, cursor: events.at(-1)!.id });
}
// Wait for new event or timeout
const result = await Promise.race([
waitForEvent(userId as string),
new Promise<null>(resolve => setTimeout(() => resolve(null), TIMEOUT))
]);
if (result) {
return res.json({ events: [result], cursor: result.id });
}
return res.json({ events: [], cursor: Number(since) });
});Why
Node.js can handle tens of thousands of concurrent open HTTP connections because it uses async I/O, not a thread per connection. The limiting resource is memory, not threads, and a 25-second timeout prevents indefinite hold.
Gotchas
- Clients must immediately re-poll after receiving a response, including empty-result timeout responses.
- Do not use long polling on a server that is not async-capable — Apache with mod_php will exhaust workers.
- Add jitter to the re-poll delay (e.g., 0–2 seconds) to prevent thundering herd after a server restart.
- Load balancers may close connections after 60 seconds — set the hold time well below the LB timeout.
Revisions (0)
No revisions yet.