Skip to content

fix(collab): unref the session-eviction interval so it can't keep the process alive - #8

Merged
zumbrunn merged 1 commit into
mainfrom
fix/collab-eviction-timer-unref
Oct 27, 2026
Merged

zumbrunn merged 1 commit into
mainfrom
fix/collab-eviction-timer-unref

Conversation

@zumbrunn

Copy link
Copy Markdown
Member

Problem

After dune serve receives SIGTERM it closes the listener and prints shutdown complete, but the process never exits: it sits at 0% CPU forever with no port. That is where a lot of "idle dune processes" come from. A second SIGTERM ends it.

Cause

createCollabManager() starts a 5-minute setInterval for session eviction (src/collab/manager.ts) that is never cleared or unref'd (the variable was even named _evictionTimer). I found it by recording the creator of every live timer after shutdown in a scratch copy of core's serve.ts: this interval, created via mount() → createCollabManager, was the only live timer, and process.getActiveResourcesInfo() showed exactly one Timeout. plugin-inline-edit's equivalent GC timer already calls .unref().

Fix

Deno.unrefTimer(evictionTimer). Housekeeping should never decide process lifetime. There is no shutdown hook to clear it from (the manager has no dispose, core has no plugin-shutdown hook), and once unref'd there is nothing left to clear. It still fires normally while the process is alive.

Tests

tests/collab/eviction_timer_test.ts runs in a subprocess, because "does the process exit on its own?" is the property under test; an in-process check cannot see a ref'd timer. It creates a collab manager and does nothing else, and must exit. Against the old code it hangs until the test's 60 s kill timeout and fails; with the fix it passes in ~75 ms.

  • deno task check clean; deno task test: 239 passed, 0 failed
  • Companion change in @dune/core so serve also exits explicitly after shutdown, so no plugin timer can hold the process (separate PR).

Not verified

plugin-inline-edit has several timers; its GC interval is unref'd, but an active editing session's awareness intervals could in principle linger. I haven't tested that; the core Deno.exit change covers it regardless.

Bumps to 3.1.3 with a CHANGELOG entry.

🤖 Generated with Claude Code

… process alive

createCollabManager() started a ref'd 5-minute setInterval for session
eviction and never cleared or unref'd it (the leading underscore on the
variable was the tell). After `dune serve` received SIGTERM, closed its
listener and printed "shutdown complete", that one timer kept the process
alive forever at 0% CPU: an idle dune process with no listener. A second
SIGTERM ended it. Found by recording who created each live timer after
shutdown: this interval, created in mount() via createCollabManager, was the
only one.

Deno.unrefTimer() it: housekeeping must never decide process lifetime. There
is no shutdown hook to clear it from (the manager has no dispose and core
has no plugin-shutdown hook), and once unref'd there is nothing left to
clear.

The regression test runs in a subprocess because "does the process exit on
its own?" is the property: it creates a collab manager and does nothing
else, and must exit. Against the old code it hangs until the test's kill
timeout; with the fix it exits in well under a second.

Co-Authored-By: Claude Opus 5.5 <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.

1 participant