Skip to content

fix: prevent memory leaks from SSE streams, LSP, Bus, and process cleanup#15646

Open
brendandebeasi wants to merge 1 commit intoanomalyco:devfrom
brendandebeasi:fix/sse-memory-leaks
Open

fix: prevent memory leaks from SSE streams, LSP, Bus, and process cleanup#15646
brendandebeasi wants to merge 1 commit intoanomalyco:devfrom
brendandebeasi:fix/sse-memory-leaks

Conversation

@brendandebeasi
Copy link

@brendandebeasi brendandebeasi commented Mar 2, 2026

Issue for this PR

Fixes #15645
Relates to #10913, #9140, #11696, #9156, #14091, #15592

Type of change

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

What does this PR do?

Fixes multiple memory leaks that cause OpenCode's memory usage to grow unbounded over time, particularly during long-running sessions.

Memory leak fixes (always active)

Fix Issues File Change
SSE cleanup #15645 server.ts, global.ts, routes.ts done flag + writeSSE error handling + unsub on abort
LSP shutdown #10913, #9140 client.ts diagnostics.clear() + reset files Map in shutdown
Bus dispose #10913 bus/index.ts subscriptions.clear() after dispose callback
ACP stdin #11696 acp.ts .on().once() for end/error listeners
GitHub unsub #9156 github.ts Capture + call Bus.subscribe return in finally block
Process exit #10913, #14091 index.ts Instance.disposeAll() with 5s timeout before process.exit()

Debug tooling (always available, zero overhead)

  • Bus.debug() — subscription count introspection per event type
  • Instance.debug() — cache state inspection
  • State.debug() — state entry counts
  • GET /debug/memory — on-demand runtime memory diagnostics endpoint
  • SIGUSR1 signal — captures heap snapshot + diagnostic report to ~/.local/share/opencode/diagnostics/

Memory monitoring (opt-in via OPENCODE_DIAGNOSTICS=1)

The polling monitor and auto-kill are disabled by default. To enable:

OPENCODE_DIAGNOSTICS=1 opencode

When enabled:

  • 30s polling interval checks RSS usage
  • 2GB warning — logs heap stats + writes periodic diagnostic reports (60s cooldown)
  • 4GB kill (configurable via OPENCODE_MEMORY_LIMIT=N in GB) — writes heap snapshot + diagnostic report, then exits
  • Kill threshold formula: max(2GB, min(25% total RAM, 4GB))

How did you verify your code works?

  • All modified files pass LSP diagnostics
  • Type errors are pre-existing (3 TUI errors: Property 'tui' does not exist on type 'Config' — unrelated to this PR)
  • Diagnostics opt-in verified: without OPENCODE_DIAGNOSTICS=1, only SIGUSR1 handler is registered (no polling, no kill)
  • With OPENCODE_DIAGNOSTICS=1, full monitoring loop activates

Checklist

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

@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

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

Potential duplicate/related PRs found:

  1. fix: consolidate memory leak fixes from #14650 and #8953 #15435 - "fix: consolidate memory leak fixes from fix: resolve memory leak issues across multiple subsystems #14650 and feat(bash): stream large output to tmpfile with filtering and GC #8953"

    • Related to memory leak fixes with potential overlap in SSE/Bus listener cleanup scope
  2. fix: resolve memory leak issues across multiple subsystems #14650 - "fix: resolve memory leak issues across multiple subsystems"

    • Addresses broader memory leak issues that may include SSE stream handling
  3. fix(core): add dispose functions to prevent subscription memory leaks #7032 - "fix(core): add dispose functions to prevent subscription memory leaks"

    • Earlier work on subscription memory leak prevention that may be relevant to Bus listener cleanup

These PRs all address memory leak issues in similar areas. Review #15435 and #14650 particularly to ensure the SSE stream cleanup in PR #15646 doesn't duplicate existing fixes or conflict with prior memory leak consolidation efforts.

@brendandebeasi
Copy link
Author

Reviewed the related PRs flagged above — this PR is complementary, not duplicative:

The core fix in this PR — awaiting writeSSE() calls, catching write errors to detect dead connections, and cleaning up Bus listeners + heartbeat intervals on failure — is not addressed by any of the other open PRs. Those PRs fix leaks in other subsystems; this one fixes the SSE stream handler leak path specifically.

@brendandebeasi brendandebeasi changed the title fix: prevent SSE stream memory leaks on client disconnect fix: prevent memory leaks from SSE streams, LSP, Bus, and process cleanup Mar 3, 2026
- Add cleanup() guard with done flag to prevent double-cleanup
- await stream.writeSSE() calls and catch errors to trigger cleanup
- Unsubscribe Bus and GlobalBus listeners on abort or write failure
- Clear heartbeat interval in all exit paths
- Add Bus.debug() subscription count introspection
- Add GET /debug/memory endpoint for runtime memory diagnostics
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.

SSE event streams leak memory on client disconnect

1 participant