Skip to content

feat(runtime): instance registry written by dev and serve - #52

Merged
zumbrunn merged 7 commits into
mainfrom
feat/instance-registry
Oct 27, 2026
Merged

zumbrunn merged 7 commits into
mainfrom
feat/instance-registry

Conversation

@zumbrunn

@zumbrunn zumbrunn commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Refs #42

Stacked on #51 (base is feat/event-loop-watchdog; I'll retarget to main once #51 merges). The branch also contains a merge of #51's latest review fixes; the registry itself is instance-registry.ts plus its two call sites.

What

dune dev / dune serve record themselves in ~/.dune/instances/<pid>.json (DUNE_REGISTRY_DIR overrides): pid, process start time, mode, site root, title, port, @dune/core version. Removed on graceful exit (serve's two shutdown paths, plus an unload backstop).

A SIGKILL, crash or default signal termination leaves the file behind, so every reader checks that the pid is alive and has the recorded start time (ps -o lstart=; pids get reused) and deletes records that fail. Writes are atomic (temp file + rename). Nothing here can stop a server from starting: all failures are swallowed. Unix only, like dune ps; where the start time can't be read nothing is written.

Nothing reads it in this PR; #53 makes dune ps use it (and keeps the command-line heuristics as the fallback for older versions and for processes that hang during bootstrap, which are never registered because the record is written after bootstrap).

A change to the plan

I'd planned for the watchdog to also mark an instance hung in the registry. That only helps in warn/off mode (in the default mode a hung process is killed within seconds). #53 probes /health/live instead, which needs no cooperation from the stuck process and also works for unregistered ones.

Verification

  • deno lint src/; deno test -A --unstable-kv tests/runtime tests/cli: passes
  • tests/runtime/instance_registry_test.ts (9 tests): fields round-trip, no leftover temp file, repeat-safe unregister, dead process pruned, live pid with a different start time pruned, garbage and unrelated files tolerated, missing directory, unwritable location doesn't throw, a normally exiting process leaves no record.
  • Live on a scaffolded site (isolated registry dir): dev and serve each registered with the real server pids (the re-exec children, not the wrapper); SIGTERM to serve removed its record; SIGKILL to dev left a stale file that the next listing pruned.

Not done

  • Single-site records the requested port; multi-site records title: null.
  • dune dev has no graceful-exit hook, so its record relies on the unload handler and pruning.

🤖 Generated with Claude Code

zumbrunn and others added 2 commits October 26, 2026 15:18
One synchronous loop blocks the only thread serving requests, so every URL
stops answering at once with nothing in the logs. A Worker now expects a
heartbeat from the main thread; after 15s (DUNE_WATCHDOG_TIMEOUT_MS) of
silence it writes a report to <site>/.dune/hangs/ (in-flight requests, plus a
native stack sample on macOS) and SIGKILLs the process so a supervisor can
restart it. DUNE_WATCHDOG=warn reports only, =off disables.

SIGKILL rather than Deno.exit(): an exit requested from a Worker only ends the
Worker, the spinning main thread carries on. A Worker also can't be unref'd, so
serve's graceful-shutdown paths stop it explicitly.

The main thread additionally logs a warning when its own timer ran over a
second late.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each running instance records itself in ~/.dune/instances/<pid>.json (pid,
process start time, mode, root, title, port, version) and removes the record on
a graceful exit. Readers verify a record against the live process (alive and
same start time, since pids are reused) and prune stale ones. Groundwork for
`dune ps` to list instances from what each one says about itself instead of
parsing command lines and lsof; nothing reads it yet.

Refs #42

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
zumbrunn and others added 4 commits October 26, 2026 22:46
…imately busy

Review of #51 and a test of my own turned up false-kill cases and a kill that
could fail to happen:

- A process suspended and resumed (laptop sleep, Ctrl-Z/SIGSTOP) was killed on
  resume: confirmed by SIGSTOPping a watched process for 4s. The stall clock is
  now monotonic (performance.now()), and a gap in the Worker's own ticks resets
  it instead of firing.
- Startup gets its own, much longer limit (2 min) until the server calls
  ready(), so a big site's initial indexing can't put a supervised process into
  a kill/restart loop.
- watchdogProgress() (rate-limited heartbeat for long synchronous loops) and
  watchdogGrace() (capped, auto-expiring, nestable busy window), exported as
  @dune/core/watchdog.
- Under --inspect/--inspect-brk the default becomes warn.
- The kill no longer depends on the report: writing it is raced against a
  timeout, and a failed Deno.kill is logged instead of silencing the watchdog.
- Reports are pruned to the newest 20, and path segments over 24 characters are
  masked before they are written.

Refs #42

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@zumbrunn
zumbrunn changed the base branch from feat/event-loop-watchdog to main October 27, 2026 16:08
# Conflicts:
#	CHANGELOG.md
#	src/cli/dev.ts
#	src/cli/serve.ts
@zumbrunn
zumbrunn merged commit a3f3ffa into main Oct 27, 2026
7 checks passed
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