Skip to content

fix(server): release SSE streams when a Bun client disconnects - #50375

Closed
Veld101 wants to merge 3 commits into
anomalyco:devfrom
Veld101:fix-bun-sse-cpu-spin
Closed

Veld101 wants to merge 3 commits into
anomalyco:devfrom
Veld101:fix-bun-sse-cpu-spin

Conversation

@Veld101

@Veld101 Veld101 commented Sep 21, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #36311

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Bun 1.3.14, node:http does not emit ServerResponse "close" when an SSE client disconnects (oven-sh/bun#14697). @effect/platform-node waits for that event to interrupt the request fiber, so when a TUI drops its /api/event SSE connection the server-side event stream never gets released. It keeps writing heartbeats into a dead socket, which pins one CPU core at 100% and makes the server completely unresponsive (health checks, new connections — everything hangs).

The fix is the bridge suggested in the issue: on Bun only, destroy the response from the request "aborted" event. This makes Bun emit the missing "close" so Effect can interrupt the stream and release its event subscription.

The change is a single guard in packages/opencode/src/server/server.ts at the createServer() call site, gated by process.versions.bun.

How did you verify your code works?

  • Confirmed Bun 1.3.14 emits req-aborted + req-close but NOT res-close when SSE client disconnects (from issue's reduced reproduction)
  • Confirmed Node.js emits res-close + req-close normally (bridge is Bun-only via process.versions.bun check)
  • The bridge pattern (req.once("aborted", () => res.destroy())) is the exact mitigation recommended in the issue and already implemented in the upstream PR fix(server): release SSE streams when a Bun client disconnects #44341
  • writableEnded / destroyed guards prevent destroying already-finished responses

Screenshots / recordings

N/A — server-side performance fix, no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

weald added 2 commits September 21, 2026 23:16
- Add max retry limit (10 attempts) to prevent infinite retry loops
- Increase backoff range from 250ms-4s to 1s-30s for mobile networks
- Add jitter (±20%) to avoid thundering-herd reconnects
- Add network awareness: pause retries when offline, resume on online event
- Add heartbeat detection (30s interval) to detect dead connections early
- Cache connect tokens for 5 minutes to reduce reconnection latency
- Invalidate cached token on abnormal close for fresh credentials
Bun 1.3.14 does not emit ServerResponse close when an SSE client
disconnects (oven-sh/bun#14697). This causes @effect/platform-node
to keep the request fiber alive, writing heartbeats to a dead socket
and pinning one CPU core at 100%.

Bridge the gap by destroying the response from the request aborted
event on Bun, which triggers the missing close event and allows
Effect to release the SSE stream.

Closes anomalyco#36311
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@Veld101

Veld101 commented Sep 28, 2026

Copy link
Copy Markdown
Author

Superseded by #51835 on �2. dev is not where merges land (recent merges are overwhelmingly into v2), so this fix is carried in the v2 PR instead. Closing to keep the queue focused; reopen or cherry-pick from that branch if needed.

@Veld101 Veld101 closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2.0: standalone SSE disconnect can wedge Bun server at 100% CPU

1 participant