Skip to content

fix: add idle-timeout Instance disposal for serve mode memory#16616

Open
sjawhar wants to merge 2 commits intoanomalyco:devfrom
sjawhar:fix/instance-idle-timeout
Open

fix: add idle-timeout Instance disposal for serve mode memory#16616
sjawhar wants to merge 2 commits intoanomalyco:devfrom
sjawhar:fix/instance-idle-timeout

Conversation

@sjawhar
Copy link

@sjawhar sjawhar commented Mar 8, 2026

Issue for this PR

Fixes #13041

Type of change

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

What does this PR do?

In serve mode, each unique workspace directory creates an Instance that spawns LSP servers, MCP clients, file watchers, and ~26 state entries. These are cached forever and never cleaned up, even after all sessions disconnect — causing unbounded memory growth (2.9GB+ per session as reported in #13041).

This PR adds automatic idle-timeout disposal:

Instance idle-timeout disposal (instance.ts):

  • Reference counting via acquire()/release() tracks active provide() calls per directory
  • When refs drop to 0, schedules disposal after OPENCODE_IDLE_TIMEOUT ms (default 5 min)
  • New provide() calls cancel any pending idle timer
  • Instance.list() returns cached instances with ref counts for observability
  • Instance.disposeByDirectory(dir) for API-driven disposal

Race condition fix (state.ts):

  • Detaches state entries from the registry before running disposal callbacks, so new Instances arriving during disposal get fresh state instead of references to disposing/disposed state

Configuration (flag.ts):

  • OPENCODE_IDLE_TIMEOUT env var (ms, default 300000 = 5 min, set to 0 to disable)
  • Dynamic getter so the value is read at access time, not module load time

API endpoints (routes/global.ts):

  • GET /global/instances — list cached instances with ref counts
  • POST /global/instances/dispose — dispose a specific instance by directory

Tests (instance-idle.test.ts):

  • 8 tests covering: basic provide, list, idle disposal, timer cancellation, fresh instance after disposal, disposeByDirectory, concurrent refs, dispose-within-provide

How did you verify your code works?

  • bun run typecheck passes
  • 8 new unit tests all pass
  • Manual testing in serve mode: instances dispose after 5 min idle, re-create on next request, memory is reclaimed

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 8, 2026

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

Potential Duplicate/Related PRs Found:

  1. fix(web): dispose idle MCP server instances to prevent process accumulation #15326 - fix(web): dispose idle MCP server instances to prevent process accumulation

    • Related to idle disposal of server instances, though focused on MCP servers specifically. May address a subset of the broader memory issue.
  2. fix: resolve memory leak issues across multiple subsystems #14650 - fix: resolve memory leak issues across multiple subsystems

These are related to memory/disposal management but have different scopes. #16616 (current PR) appears to be the most comprehensive solution targeting the specific serve mode instance caching issue with idle-timeout disposal.

@Morzaram
Copy link

Morzaram commented Mar 8, 2026

Why does yours have 58 files to fix this? Majority being MD?

@sjawhar sjawhar force-pushed the fix/instance-idle-timeout branch from b2326b3 to 2b44b94 Compare March 8, 2026 18:38
@sjawhar
Copy link
Author

sjawhar commented Mar 8, 2026

Good catch — those were internal tooling files (.sisyphus/) that shouldn't have been tracked. Cleaned up, the diff now only contains the 2 relevant source files (instance.ts + flag.ts) plus the test file.

@sjawhar sjawhar force-pushed the fix/instance-idle-timeout branch 2 times, most recently from 677af75 to 1f4520d Compare March 9, 2026 16:28
binarydoubling added a commit to binarydoubling/opencode that referenced this pull request Mar 9, 2026
Addresses the remaining memory leaks identified in anomalyco#16697 by
consolidating the best fixes from 23+ open community PRs into
a single coherent changeset.

Fixes consolidated from PRs: anomalyco#16695, anomalyco#16346, anomalyco#14650, anomalyco#15646,
anomalyco#13186, anomalyco#10392, anomalyco#7914, anomalyco#9145, anomalyco#9146, anomalyco#7049, anomalyco#16616, anomalyco#16241

- Plugin subscriber stacking: unsub before re-subscribing in init()
- Subagent deallocation: Session.remove() after task completion
- SSE stream cleanup: centralized cleanup with done guard (3 endpoints)
- Compaction data trimming: clear output/attachments on prune
- Process exit cleanup: Instance.disposeAll() with 5s timeout
- Serve cmd: graceful shutdown instead of blocking forever
- Bash tool: ring buffer with 10MB cap instead of O(n²) concat
- LSP index teardown: clear clients/broken/spawning on dispose
- LSP open-files cap: evict oldest when >1000 tracked files
- Format subscription: store and cleanup unsub handle
- Permission/Question clearSession: reject pending on session delete
- Session.remove() cleanup chain: FileTime, Permission, Question
- ShareNext subscription cleanup: store unsub handles, cleanup on dispose
- OAuth transport: close existing before replacing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sjawhar sjawhar force-pushed the fix/instance-idle-timeout branch 2 times, most recently from 347d450 to 274da90 Compare March 11, 2026 22:25
@sjawhar sjawhar force-pushed the fix/instance-idle-timeout branch from 274da90 to 364633b Compare March 12, 2026 17:23
@sjawhar sjawhar force-pushed the fix/instance-idle-timeout branch from 364633b to 895362f Compare March 12, 2026 23:37
Keep TUI instances alive during active sessions by wrapping tui() inside
Instance.provide() in both thread.ts and attach.ts. This prevents the
idle timer from firing while the TUI is running — the ref stays > 0 for
the entire session and only drops to 0 when the user exits.

Also fix verbose permission ruleset serialization in evaluate() log and
add a regression test for the idle-timeout lifecycle invariant.
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.

Per-session MCP server and LSP duplication multiplies memory usage across concurrent sessions

3 participants