Skip to content

Close the snapshot store off the runtime; release v0.8.4 - #31

Merged
jaredLunde merged 1 commit into
mainfrom
jared/close-store-off-runtime
Oct 17, 2026
Merged

jaredLunde merged 1 commit into
mainfrom
jared/close-store-off-runtime

Conversation

@jaredLunde

Copy link
Copy Markdown
Contributor

watch_applied dropped its snapshot store on the tokio worker wherever the loop exited. fjall 3.1.x's Database::drop can deadlock while joining its worker pool: a blocking send(Close) into a full channel after the last worker has exited. On a single-worker runtime, which is what a CPUQuota=100% service gets, that froze the whole process. In production, instd stopped pulling NATS, logging and handling SIGTERM after a KV-watch heartbeat miss, and never reached the error path that restarts it. gdb on the wedged process showed fjall's thread counter at 0, the channel at 1000/1000, one parked sender and no worker threads. fjall 3.1.12 still has the same Drop.

Change

  • The loop now only borrows the fold. On every exit (Ok or Err), the store is dropped on a dedicated thread, and watch_applied waits up to 30s for that close before returning.
  • A normal close still finishes before the return, so callers can reopen the same path.
  • A close that hangs leaves its thread behind; the runtime keeps going and the caller still gets the watch's error.
  • This uses a std thread rather than spawn_blocking, because runtime shutdown waits on blocking tasks and would just move the hang to process exit.
  • ARCHITECTURE.md and the watch_applied docs describe the new close behavior.
  • Version bumped to 0.8.4.

Tests

  • a_store_that_hangs_on_close_cannot_freeze_the_runtime runs on a current-thread runtime. Against the pre-fix code it hangs (killed at 30s); with the fix it passes.
  • the_store_is_closed_before_watch_applied_returns checks that a normal close completes before the return.
  • Full suite passes, including the NATS integration tests.
  • All six stateright models pass. The change only runs after the loop has exited and its final flush has committed, so cursor authority is untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XoaQgogNq1z3dNoM9feGoH

watch_applied dropped its store wherever the loop exited, on the tokio
worker. fjall 3.1.x's Database::drop can deadlock joining its worker
pool (a blocking send(Close) into its full channel after the last worker
exits), and on a single-worker runtime (a CPUQuota=100% service) that
froze the whole process: instd stopped pulling NATS, logging and
handling signals after a KV-watch heartbeat miss, and never reached the
error path that restarts it.

The loop now only borrows the fold; on every exit the store is dropped
on a dedicated thread and the return waits up to 30s for it. A normal
close still completes before return, so callers can reopen the path; a
hung close leaves its thread behind instead of the runtime. A std thread,
not spawn_blocking, since runtime shutdown waits on blocking tasks.

Release 0.8.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XoaQgogNq1z3dNoM9feGoH
@jaredLunde
jaredLunde merged commit 1fc6f90 into main Oct 17, 2026
1 check passed
@jaredLunde
jaredLunde deleted the jared/close-store-off-runtime branch October 17, 2026 16:38
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