Live event streams ignore backpressure #71

Closed
opened 2026-10-08 16:33:31 -04:00 by goddard · 1 comment
Collaborator

The live event streams queue frames without checking backpressure (api/events/servers/+server.ts:20-29), so a stalled client buffers status updates in memory until its connection drops.

Fix

  • Check the stream's desired size / backpressure before enqueuing, and drop or coalesce frames (or close the connection) for clients that fall behind.
The live event streams queue frames without checking backpressure (`api/events/servers/+server.ts:20-29`), so a stalled client buffers status updates in memory until its connection drops. **Fix** - Check the stream's desired size / backpressure before enqueuing, and drop or coalesce frames (or close the connection) for clients that fall behind.
goddard added the
priority
low
type/hardening
labels 2026-10-08 16:39:15 -04:00
josh was assigned by goddard 2026-10-08 17:31:48 -04:00
Author
Collaborator

Reviewed deb2d21f29. The fix is done, so I'm closing this.

Checklist

  • Backpressure checked before enqueuing. Both live streams now use one helper, eventStreamResponse (event-stream.ts). It caps each connection's queue at 64 frames (event-stream.ts:8,51), and checks desiredSize before every enqueue. A client that falls behind is closed instead of buffered (event-stream.ts:32-36), and the helper releases its subscription and heartbeat itself, since close() doesn't call cancel().

Review notes

  • This only works if the server stops reading for a stalled socket. It does: SvelteKit's Node writer waits for drain when res.write returns false (@sveltejs/kit/src/exports/node/index.js:222-225), so the queue really fills.
  • Both clients use EventSource and only close it when the page tears down (servers.ts:141, pipeline.ts:60). A closed stream therefore reconnects automatically, and the servers stream sends a fresh snapshot on connect.
  • Good extra: the new tests cover a stalled client not affecting a reading one, and heartbeats alone filling the queue.
  • The 5 affected test files pass locally (105 tests), the full suite passes (208 files, 2,625 tests), and npm run check reports 0 errors.
Reviewed deb2d21f29ed1f0f4721d09a704b5f84fff64272. The fix is done, so I'm closing this. **Checklist** - [x] **Backpressure checked before enqueuing.** Both live streams now use one helper, `eventStreamResponse` (`event-stream.ts`). It caps each connection's queue at 64 frames (`event-stream.ts:8,51`), and checks `desiredSize` before every enqueue. A client that falls behind is closed instead of buffered (`event-stream.ts:32-36`), and the helper releases its subscription and heartbeat itself, since `close()` doesn't call `cancel()`. **Review notes** - This only works if the server stops reading for a stalled socket. It does: SvelteKit's Node writer waits for `drain` when `res.write` returns false (`@sveltejs/kit/src/exports/node/index.js:222-225`), so the queue really fills. - Both clients use `EventSource` and only close it when the page tears down (`servers.ts:141`, `pipeline.ts:60`). A closed stream therefore reconnects automatically, and the servers stream sends a fresh snapshot on connect. - Good extra: the new tests cover a stalled client not affecting a reading one, and heartbeats alone filling the queue. - The 5 affected test files pass locally (105 tests), the full suite passes (208 files, 2,625 tests), and `npm run check` reports 0 errors.
Sign in to join this conversation.