Skip to content

feat(server): drain connections on SIGTERM before exiting - #131

Merged
kipavy merged 1 commit into
mainfrom
feat/graceful-shutdown
Nov 11, 2026
Merged

kipavy merged 1 commit into
mainfrom
feat/graceful-shutdown

Conversation

@kipavy

@kipavy kipavy commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Part of #133. Useful on a single instance today; also a prerequisite for rolling deploys later.

Problem

No signal handler was installed. As PID 1 in the container the server ignored SIGTERM, so every docker stop / recreate waited the full 10 s and then SIGKILLed it:

  • in-flight requests were cut off
  • shared terminal participants saw a frozen terminal with no reason
  • the session row kept ended_at IS NULL and its session_key_bytes, because the host's disconnect cleanup never ran

Change

On SIGTERM or Ctrl-C:

  1. Stop accepting connections; in-flight requests finish (with_graceful_shutdown).
  2. /v1/sync/stream responses end. The client's SSE loop already reconnects with backoff.
  3. Each shared-terminal socket gets {"type":"session_ended","reason":"server_restart"} and a 1012 close. Current clients handle session_ended already. The host's socket then runs its normal cleanup, so the session is marked ended in the DB and its key bytes are cleared.
  4. Upgraded sockets are tracked in a TaskTracker, because hyper stops tracking a connection once it upgrades.
  5. Anything still open after SHUTDOWN_GRACE_SECS (default 8, below Docker's 10 s) is dropped and the process exits.

The "send end frame, then close" sequence is now one helper, end_socket, shared with the access-revoked path.

This doesn't keep anything alive across a restart. Shared sessions still end on every redeploy; they now end cleanly.

Tests

  • shutdown::tests: waits for tracked sockets, gives up after the grace period, stopping() resolves on begin
  • draining_ends_the_session_for_everyone_attached: host and guest both get the server_restart frame and a 1012 close, and the session row is ended
  • Full suite against Postgres 16: 943 passed. Clippy -D warnings clean.

The server installed no signal handler. As PID 1 in its container it
ignored SIGTERM, so every stop waited out Docker's 10 s timeout and was
then killed mid-request, leaving shared terminal sessions marked live in
the database with their key bytes still stored.

On SIGTERM or Ctrl-C the server now stops accepting connections, lets
in-flight requests finish, ends open sync streams (clients already
reconnect on their own), and tells every shared-terminal socket the
session ended with reason "server_restart" before closing it with 1012.
The host's normal disconnect cleanup then marks the session ended.
Upgraded sockets are tracked separately because hyper stops tracking a
connection once it upgrades. Whatever is still open after
SHUTDOWN_GRACE_SECS (default 8) is dropped and the process exits.

Refs VoltiusApp/voltius#800
@kipavy
kipavy merged commit ae9340e into main Nov 11, 2026
1 check passed
@kipavy
kipavy deleted the feat/graceful-shutdown branch November 11, 2026 16:21
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