Skip to content

fix(rivetkit-core): close drained connections with ws.going_away - #5867

Open
eersnington wants to merge 2 commits into
fix/gateway-close-without-envoy-ackfrom
fix/drain-going-away-close-reason
Open

eersnington wants to merge 2 commits into
fix/gateway-close-without-envoy-ackfrom
fix/drain-going-away-close-reason

Conversation

@eersnington

@eersnington eersnington commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

On a runner drain, the actor closed client WebSockets with code 1000 and no reason. Clients could not tell a drain from a normal close, so in-flight actions rejected with an unstructured error.

On drain Before After
In-flight connection action rejects with Connection closed (code: 1000), no group or code rejects with ws.going_away
conn.onError not called not called
  • ActorContext::disconnect_transport_only in rivetkit-rust/packages/rivetkit-core/src/actor/connection.rs closes with the ws.going_away reason once the actor has started to shut down (actor_aborted()).
  • The transport close in rivetkit-rust/packages/rivetkit-core/src/registry/websocket.rs sends code 1001 for that reason. Other closes keep 1000.
  • Clients already classify ws.going_away as reconnect-only (rivetkit-typescript/packages/rivetkit/src/client/lifecycle-errors.ts).
  • conn.onError is not called for a lifecycle close that the client recovers from by reconnecting. That close is a state change, which onClose and the connection status already report. Without this, every sleep and drain would call onError.
  • The close still happens after the onDisconnect hook, so the hook runs once.
  • Adds tests/driver/actor-conn-drain.test.ts. It drains a runtime while an action runs, and checks that the action rejects with ws.going_away, onError is not called, and onDisconnect runs once. The driver runtime fixture drains on SIGTERM when RIVETKIT_TEST_DRAIN_ON_SIGTERM=1.
let reason = if self.actor_aborted() {
	Some(WS_GOING_AWAY_CLOSE_REASON.to_owned())
} else {
	None
};
// rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts, #handleOnClose
const willReconnect = wasConnected && this.#authRetryAvailable;
if (
	error instanceof errors.ActorError &&
	!(willReconnect && isRetryableLifecycleReconnectSignal(error))
) {
	this.#dispatchActorError(error);
}

This is part 3 of 6 in a stack:

@claude

claude Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review: close drained connections with ws.going_away

Small, well-scoped fix with a good end-to-end driver test. I found no blocking issues. Notes:

Correctness / design

  • connection.rs: the reason is chosen from actor_aborted(). That flag may also be true for ordinary sleep or destroy shutdowns, not only runner drains. Clients treat ws.going_away as reconnect-only, so reconnect after sleep is probably fine. For destroy, please confirm a reconnect attempt can't loop or surface a confusing error. If the flag is broader than a drain, update the PR description and the constant's doc comment.
  • websocket.rs: the 1001 mapping matches on the reason string. It works, but passing an explicit (code, reason) pair from the caller would be sturdier if more reasons are added.
  • actor-conn.ts: suppressing #dispatchActorError when willReconnect && isRetryableLifecycleReconnectSignal(error) applies to all retryable lifecycle signals, not just ws.going_away. Probably intended, but worth a line in the PR description. Make sure a failed reconnect still surfaces to onError.

Tests

  • actor-conn-drain.test.ts covers structured rejection, no onError, and onDisconnect running exactly once. Restricting to native/bare/local is reasonable given the SIGTERM fixture.
  • Counting hook runs by scanning runtime output for a log marker is somewhat brittle (depends on stdout capture being complete). A state-based counter would be more robust if it flakes.
  • No Rust test for the 1001 vs 1000 mapping. CLAUDE.md asks for Rust tests under tests/, so a small test for close-code selection would be cheap.

Other

  • Conventions and the SKILL.md table update look right. No security or performance concerns.

@eersnington
eersnington added this pull request to stack #5872 October 13, 2026 18:06
@eersnington
eersnington force-pushed the fix/drain-going-away-close-reason branch 2 times, most recently from 120df78 to 2b2bdf1 Compare October 13, 2026 21:10
@eersnington
eersnington removed this pull request from stack #5872 October 13, 2026 21:11
@eersnington
eersnington changed the base branch from fix/client-retry-actor-ready-timeout to fix/gateway-close-without-envoy-ack October 13, 2026 21:11
@eersnington
eersnington added this pull request to stack #5875 October 13, 2026 21:11
@eersnington
eersnington force-pushed the fix/drain-going-away-close-reason branch from 2b2bdf1 to adab4ef Compare October 13, 2026 21:18
@eersnington
eersnington force-pushed the fix/drain-going-away-close-reason branch from adab4ef to 924f9e0 Compare October 14, 2026 20:40
@eersnington
eersnington removed this pull request from stack #5875 October 14, 2026 20:40
@eersnington
eersnington added this pull request to stack #5879 October 14, 2026 20:40
@eersnington
eersnington force-pushed the fix/drain-going-away-close-reason branch from 924f9e0 to 3746e2c Compare October 16, 2026 23:44

This branch has not been deployed

No deployments
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