Repository navigation
fix(events): start every SSE response once its subscriptions are live - #8700
Conversation
Next sends a route's status and headers with the first body chunk, and createSSEStream wrote nothing until an event or the 30 s heartbeat. A client therefore saw no response for up to 30 s after connecting: the desktop inbox doorbell E2E waited 32-34 s for its stream to open in every run, and Sim desktop's doorbell gives up on a response that has not started within 15 s. - createSSEStream writes a `: connected` comment as soon as every subscription is live, which starts the response at once. - PubSubChannel exposes ready(), settled when the process's Redis SUBSCRIBE completes, so a client that reads its state on open cannot miss an event published before the channel was listening. The desktop doorbell, chat status and MCP streams wait on their channels. - The desktop inbox E2E asserts the doorbell opens within the desktop's 15 s handshake and that a ring arrives within 2 s of the open.
|
@cubic-dev-ai review this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Turn on auto-fix | Re-trigger cubic
|
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Requires human review: Auto-approval blocked because this review re-detected 3 unresolved issues already reported by Cubic.
Fix all with cubic | Turn on auto-fix | Re-trigger cubic
- Events wait for the opening gate too, so one published while another subscription is still connecting cannot start the response early. - The gate opens at OPEN_DEADLINE_MS (5 s) when a subscription is not live, so a Redis outage degrades to an open stream instead of a silent one. - PubSubChannel subscribes on every ready connection and is not ready again after a dropped one until it has resubscribed. - The desktop inbox E2E compiles the doorbell route with a refused request before timing its open.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Turn on auto-fix | Re-trigger cubic
…swers a closed connection A failed SUBSCRIBE no longer settles ready(): the stream's opening deadline bounds the wait, and the next connection subscribes again. A subscribe answered after its connection closed is ignored, so it cannot mark a reconnected channel ready before that connection has subscribed.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… closed before it opens - Every event goes through one ordered write chain: it is authorized as soon as it arrives, so concurrent events still share one check, but written only after every event before it. An event queued before the stream opened can no longer be overtaken by one that arrived during its authorization. - Closing settles the opening gate as well as clearing its deadline, so a stream closed before it opens is not kept alive by a subscription that never becomes ready. - Tests cover event order, retention after an early close, timer cleanup, and the ready wiring of the mothership and MCP event routes.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
Next sends a route's status and headers with the first body chunk (
pipe-readablecallsres.flushHeaders()on the first write, innext devandnext startalike).createSSEStreamwrote nothing until an event or the 30 s heartbeat, so a client saw no response for up to 30 s after connecting.EventSource.onopenfired up to 30 s late, and a rotation's replacement could open after the old stream's grace period closed it.Changes:
createSSEStreamwrites a: connectedcomment once every subscription is live, which starts the response at once. Clients ignore comments.PubSubChannel.ready()settles once this process's RedisSUBSCRIBEon the current connection has completed. A dropped connection is not ready again until it has resubscribed, and a failedSUBSCRIBEleaves the channel not ready. The desktop doorbell, chat status and MCP event streams wait on their channels, so a client that reads its state on open (the desktop pulls its inbox) cannot miss an event published before the channel was listening.OPEN_DEADLINE_MS(5 s) when a subscription is still not live. A Redis outage then gives an open stream, which rides out the outage like any already-open stream, instead of a silent one. The deadline is shorter than the heartbeat interval, so a heartbeat can never start the response first.MAX_UNDRAINED_CHUNKS) can wait before the stream opens, or behind an authorization. One more closes the stream (pending_backpressure), and the client reconnects and reconciles. This replaces the revalidated-onlyauthorization_backpressurebound.Type of Change
Testing
sse-endpoint.test.ts, each red before its fix:pubsub.test.tscovers per-connection readiness: subscribe, reconnect, a failed subscribe, and a subscribe answered after its connection closed.next devapp:sse-endpoint.ts, 2/2 runs failed, opening at the 30 s heartbeat (30045 and 30052 ms).Checklist