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 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>
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