You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(rivetkit-core): close drained connections with ws.going_away - #5867
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.
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).
The close still happens after the onDisconnect hook, so the hook runs once.
Adds tests/driver/actor-conn-drain.test.ts, which drains a runtime and checks that the client gets ws.going_away and onDisconnect runs once. The driver runtime fixture drains on SIGTERM when RIVETKIT_TEST_DRAIN_ON_SIGTERM=1.
let reason = ifself.actor_aborted(){Some(WS_GOING_AWAY_CLOSE_REASON.to_owned())}else{None};
The change is small and focused, and it matches the repo conventions. I found no blocking issues. Nothing was compiled or run.
Notes
Public disconnect APIs now share the drain behavior.disconnect_transport_only (actor/connection.rs) also backs the public ActorContext::disconnect_conn and disconnect_conns (actor/context.rs:1273-1281). Any user-initiated disconnect that happens once actor_aborted() is true now closes with 1001 and ws.going_away. That is probably fine, since the actor is going away anyway. If you want only the drain path to use it, the reason could be passed in explicitly instead of read from actor_aborted().
The 1000 to 1001 mapping depends on a string match. In registry/websocket.rs the code is chosen by comparing the reason string to WS_GOING_AWAY_CLOSE_REASON. The shared constant keeps the two sides in sync, but a typed close-code argument on the disconnect callback would be sturdier. Optional.
Test fixture.
The onDisconnect sleep of 100ms is a timing-based way to provoke a duplicate hook run, so the test can pass without exercising the race if the timing shifts.
Counting hook runs by splitting captured stdout is fragile and depends on getRuntimeOutput being available. If it is undefined, the ?? "" fallback gives hookRuns === 0 and the test fails with a misleading message.
The test only covers the native runtime, the bare encoding and the local SQLite backend. The wasm path is not covered.
Test runtime.process.once("SIGTERM", () => void nativeRegistry.shutdown()) discards the promise, so a shutdown failure becomes an unhandled rejection. Adding a .catch that logs and exits non-zero would make failures easier to diagnose.
Tests and style. There is no Rust test for the new code, such as a unit test of the reason-to-code mapping or of disconnect_transport_only with actor_aborted() set. Per CLAUDE.md, any Rust test should live under tests/. The import ordering in registry-static.ts (conn-drain after conn-error-serialization) is slightly off alphabetically, which is a nit.
Good: the close still happens after the onDisconnect hook, so the hook runs once, and clients already treat ws.going_away as reconnect-only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On a runner drain, the actor closed client WebSockets with code
1000and no reason. Clients could not tell a drain from a normal close, so in-flight actions rejected with an unstructured error.ActorContext::disconnect_transport_onlyinrivetkit-rust/packages/rivetkit-core/src/actor/connection.rscloses with thews.going_awayreason once the actor has started to shut down (actor_aborted()).rivetkit-rust/packages/rivetkit-core/src/registry/websocket.rssends code1001for that reason. Other closes keep1000.ws.going_awayas reconnect-only (rivetkit-typescript/packages/rivetkit/src/client/lifecycle-errors.ts).onDisconnecthook, so the hook runs once.tests/driver/actor-conn-drain.test.ts, which drains a runtime and checks that the client getsws.going_awayandonDisconnectruns once. The driver runtime fixture drains on SIGTERM whenRIVETKIT_TEST_DRAIN_ON_SIGTERM=1.This is part 3 of 6 in a stack: