Skip to content

fix(rivetkit): keep actor event loop alive through shutdown state serialization - #5546

Merged
abcxff merged 8 commits into
mainfrom
stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt
Aug 11, 2026
Merged

fix(rivetkit): keep actor event loop alive through shutdown state serialization#5546
abcxff merged 8 commits into
mainfrom
stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt

Conversation

@abcxff

@abcxff abcxff commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review

Summary: handle_actor_event no longer returns Ok(true) from the RunGracefulCleanup arm, so run_actor's event loop keeps draining events after on_sleep/on_destroy runs instead of breaking immediately. All affected inline tests get a drop(tx) to close the channel and let the loop terminate, plus a new regression test.

Correctness — looks right

I traced this against rivetkit-core's ActorTask::run_shutdown (rivetkit-rust/packages/rivetkit-core/src/actor/task.rs):

async fn run_shutdown(&mut self, reason: ShutdownKind) -> Result<()> {
    ...
    self.save_final_state().await?;       // sends SerializeState, awaits reply
    self.close_actor_event_channel();      // only then drops actor_event_tx
    self.join_aborted_run_handle().await;
    ...
}

save_final_state sends ActorEvent::SerializeState after RunGracefulCleanup and blocks on the reply (bounded by SERIALIZE_STATE_SHUTDOWN_SANITY_CAP / the configured sleep grace period), and the channel is only closed afterward. Before this fix, run_actor broke out of its loop as soon as the cleanup event was handled, so it never read the follow-up SerializeState, its reply_rx.await in core would time out, and any state written by on_sleep/on_destroy would silently be dropped in favor of empty deltas. The fix (stay in the loop until the channel closes) matches the actual sequencing and closes a real state-loss bug. The new test run_actor_serializes_state_after_cleanup reproduces exactly this ordering and asserts the on_sleep log entry survives serialization.

Worth noting: the NAPI/TS bridge (rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs) already handles RunGracefulCleanup by spawning a detached task rather than ending its dispatch loop, so it never had this bug — this brings the Rust flagship wrapper (rivetkit-rust/packages/rivetkit) to parity, consistent with the "keep rivetkit (Rust) up to date with rivetkit-typescript" guidance in CLAUDE.md.

Nit: now-dead Result<bool> / should_stop

With the RunGracefulCleanup arm no longer returning Ok(true), no arm of handle_actor_event returns true anymore (confirmed via grep — the removed line was the only Ok(true) in the match). That makes this in run_actor dead:

while let Some(event) = events.recv_raw().await {
    let should_stop = handle_actor_event(actor.clone(), ctx.clone(), event).await?;
    if should_stop {
        break;
    }
}

handle_actor_event's signature could shrink to Result<()> and the loop simplify to while let Some(event) = events.recv_raw().await { handle_actor_event(...).await?; }, since the loop now exclusively terminates via the channel closing. Not a bug, but worth a follow-up cleanup so the vestigial "can this event stop the loop" branch doesn't confuse future readers into thinking some event type still short-circuits.

Minor test nit

run_actor_serializes_state_after_cleanup (and the shared request_serialize helper) awaits the reply with no timeout. That's fine now, but if this exact regression reappeared, the test would hang instead of failing fast (no reader left to answer the oneshot). Not blocking — CI will still eventually flag a hung test — but a tokio::time::timeout(...) around the reply_rx.await in request_serialize would make a future regression fail with a clear message rather than a stall.

Other

  • No security or performance concerns — this is actor-local lifecycle sequencing, not a trust boundary, and the extra loop iteration(s) before shutdown are negligible.
  • Test coverage is good: the new test isolates the exact repro (sleep cleanup → serialize before channel close) and the mechanical drop(tx) additions correctly preserve every other existing test's shutdown behavior.

@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from a4f9445 to a08e479 Compare August 4, 2026 16:15
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch 2 times, most recently from 2e35cdc to 3f1016b Compare August 5, 2026 17:09
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from a08e479 to 40d03bf Compare August 5, 2026 17:09
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch from 3f1016b to 41fad8a Compare August 10, 2026 17:16
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from 40d03bf to 13cd549 Compare August 10, 2026 17:16
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from 13cd549 to fbd0e22 Compare August 10, 2026 19:03
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch 2 times, most recently from ddda4b1 to a3dec62 Compare August 10, 2026 20:35
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from fbd0e22 to 7df3e38 Compare August 10, 2026 20:35
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from 7df3e38 to 5c46256 Compare August 10, 2026 22:09
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch from a3dec62 to 1b7821e Compare August 10, 2026 22:09
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch from 1b7821e to f623340 Compare August 11, 2026 13:39
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch 2 times, most recently from b8b0fd3 to f8766f0 Compare August 11, 2026 14:24
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch from f623340 to 2adf6c7 Compare August 11, 2026 14:24
@abcxff
abcxff force-pushed the stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk branch from 2adf6c7 to c78a0b7 Compare August 11, 2026 17:22
@abcxff
abcxff force-pushed the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch from f8766f0 to 4451538 Compare August 11, 2026 17:22
@abcxff
abcxff changed the base branch from stack/feat-container-runner-report-actors-as-crashed-on-unexpected-platform-sigterm-lzuymmyk to main August 11, 2026 17:24
@abcxff
abcxff merged commit 4451538 into main Aug 11, 2026
2 of 7 checks passed
@abcxff
abcxff deleted the stack/fix-rivetkit-keep-actor-event-loop-alive-through-shutdown-state-serialization-losmyvnt branch August 11, 2026 17:24
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