Skip to content

fix(rpc): reclaim shared-host occupancy (idle eviction, session cap, empty-host exit) - #1226

Merged
code-yeongyu merged 7 commits into
mainfrom
fix/mem-shared-host-eviction
Aug 31, 2026
Merged

fix(rpc): reclaim shared-host occupancy (idle eviction, session cap, empty-host exit)#1226
code-yeongyu merged 7 commits into
mainfrom
fix/mem-shared-host-eviction

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Problem

The shared multi-session RPC host (--mode rpc --multi-session) reclaimed nothing without client cooperation:

  • Sessions were never evicted. An abandoned session (client crashed/reconnected without close_session) kept its complete runtime — extension instances, fs.watch handles, transcript — resident forever.
  • open_session was unbounded. Every open owns a full runtime (~340-510 MB RSS each); reconnect churn and multi-window use grow the host without bound. A desktop host measured 1.28 GB in 54 min.
  • An empty host never exited. runStdioHost parks on return new Promise(() => {}); a zero-session host burns its baseline RSS until its pipe dies. The lifecycle supervisor only covers supervised socket hosts with zero connections.

Fixes

(4.1) Idle eviction + empty-host exitsession-registry.ts / session-command-router.ts / multi-session-host.ts:

  • Registry entries track lastCommandAt, refreshed by every routed command (the getForCommand chokepoint) and by path attach.
  • An unref'd interval sweep evicts entries idle past the window through the existing beginClose -> closeMarked path (abort -> waitForIdle -> dispose, all attachments drained, path reservation released, pending extension UI requests cancelled, session_closed broadcast + final close_session response record).
  • Never evicts a session with an active turn (isStreaming) or running session-owned bash; their idle clock restarts when the work settles, mirroring the headless completion contract.
  • When the registry stays empty for the empty window, the host exits through its clean shutdown path (flush, socket removal) — both stdio and --listen flavors.
  • Clock is injectable (now option, the repo's established pattern); tests use vitest fake timers, zero sleeps (mindful of the rpc-host-lifecycle: 'does not exit while a turn is active' host never exits (main CI red since ~07:44Z 08-28) #1181 rpc-host-lifecycle flake class).

(4.2) Bounded concurrent sessionsopen_session beyond the cap fails with the new too_many_sessions error code. Attach-on-open is exempt (attached: true adds no runtime), so desktop reconnect/resume flows keep working at cap; an evicted session simply reopens on the next open_session with the same sessionPath. Capacity frees automatically once the idle evictor closes an idle session.

Defaults (env-overridable)

Env Default Rationale
SENPI_RPC_SESSION_IDLE_EVICTION_MS 1800000 (30 min) Bounds abandoned-session residency while staying far above any interactive pause; an idle session holds ~340-510 MB.
SENPI_RPC_MAX_SESSIONS 8 ~3.6 GB worst-case ceiling at ~450 MB/session; generous for multi-window desktop use.
SENPI_RPC_HOST_EMPTY_EXIT_MS 900000 (15 min) Matches the supervisor's idle window, so supervised hosts usually take the supervisor's clean idle exit; the in-host bound covers connected-but-sessionless, bare, and embedded hosts.

Invalid values fall through to defaults (same policy as SENPI_RPC_HOST_IDLE_EXIT_MS). Explicit Number.POSITIVE_INFINITY disables a bound (tests only).

Test evidence (TDD, remote runner mengmotaMac)

  • RED (SHA 0901919cc): 10 failed / 2 passed — idle session still open after the window; onEmptyExit never called / createHostCore unexported; third open_session resolved rpc-3 instead of rejecting.
  • GREEN (SHA 8fd742289): 10 files / 127 tests passed, exit 0 — new rpc-session-occupancy suite (12 tests) plus existing session-registry, multi-session*, and host-lifecycle suites.

Docs

  • docs/rpc.md: new "Shared host occupancy" section + too_many_sessions in the stable error-code list.
  • src/modes/rpc/changes.md + CHANGELOG.md [Unreleased] entries.

Out of scope by design (other lanes): per-session service sharing, transcript residency, session-manager/session-resident-store.


Summary by cubic

Stops the shared multi-session RPC host from holding abandoned runtimes forever by reclaiming occupancy on its own instead of relying on clients to send close_session. The host now evicts idle sessions, caps concurrent sessions, and exits when empty; its supervisor reads that clean exit as an intentional stop, not a crash.

Behavior changes

  • Sessions idle for 30 minutes (no routed command and no session-owned work: an agent run, bash, compaction, session-work barrier, or published wake sources like background terminal jobs) are closed through the normal close path; an evicted session can be reopened with the same sessionPath.
  • open_session beyond 8 concurrently opening/open sessions fails with the new stable too_many_sessions error code; attaching to a live session is exempt.
  • A host with zero open sessions for 15 minutes exits cleanly, for both stdio and --listen hosts; a connected client counts as occupancy on socket hosts, and the supervisor treats the clean exit as an intentional stop rather than a crash.

Configuration

  • SENPI_RPC_SESSION_IDLE_EVICTION_MS, SENPI_RPC_MAX_SESSIONS, and SENPI_RPC_HOST_EMPTY_EXIT_MS override the defaults; invalid values fall back to defaults.

Written for commit 76fe034. Summary will update on new commits.

Review in cubic

@code-yeongyu
code-yeongyu force-pushed the fix/mem-shared-host-eviction branch from 0f8a1b4 to 9e360e0 Compare August 31, 2026 05:41
@code-yeongyu
code-yeongyu force-pushed the fix/mem-shared-host-eviction branch from 9e360e0 to 76fe034 Compare August 31, 2026 07:25
@code-yeongyu
code-yeongyu merged commit 1d14b15 into main Aug 31, 2026
15 checks passed
@code-yeongyu
code-yeongyu deleted the fix/mem-shared-host-eviction branch August 31, 2026 07:52
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.

1 participant