Skip to content

fix(envoy-client): declare actors lost before the engine can reallocate them - #5823

Open
abcxff wants to merge 1 commit into
stack/fix-rivetkit-stop-lost-actor-generations-immediately-opswolspfrom
stack/fix-envoy-client-declare-actors-lost-before-the-engine-can-reallocate-them-nkyokown
Open

abcxff wants to merge 1 commit into
stack/fix-rivetkit-stop-lost-actor-generations-immediately-opswolspfrom
stack/fix-envoy-client-declare-actors-lost-before-the-engine-can-reallocate-them-nkyokown

Conversation

@abcxff

@abcxff abcxff commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

No description provided.

@abcxff

abcxff commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@claude

claude Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review

The change is small and the shared declare_actors_lost helper is a good refactor. The tests cover the deadline math and the cancel-before-send ordering. I found one likely bug and a few smaller points.

Likely bug: stale last_ping_ts after a reconnect

last_ping_ts is never reset when the connection drops, when lost fires, or when a new connection opens. engine_ping_silence_deadline_ms only returns None when the value is 0, which only happens before the first ping ever.

  1. The envoy is lost and declare_actors_lost clears the actors. last_ping_ts keeps its old value.
  2. The envoy reconnects and the engine starts a new actor. The actor is in ctx.actors, so the new silence branch is enabled.
  3. The deadline is still old_ping + threshold - margin, which is in the past. The branch fires on the next loop iteration, before the first ping of the new connection arrives. It declares the new actor lost immediately.

Pings are about 3s apart, so there is a window for this on every reconnect after a lost event. A fix is to reset last_ping_ts (to 0 or the connect time) when a new connection opens, or inside declare_actors_lost. A test that declares lost, inserts an actor, and asserts the silence deadline is not already expired would cover it.

Smaller points

  • last_ping_ts is stamped and compared with wall-clock now_millis, so a clock step can trigger or delay lost. Consider a monotonic Instant for the receipt time.
  • handle_conn_close still starts its own lost_timeout from the close time, using the full threshold. The engine's clock started at its last ping refresh, so that timer can fire after the engine has reallocated. The silence branch covers this when pings were flowing. The docs could mention both paths together.
  • declare_actors_lost, engine_ping_silence_deadline_ms and engine_ping_silence_expired are pub only for the tests, which widens the crate API.
  • The tests cover the deadline math but not the envoy_loop branch itself, for example that it does not fire when ctx.actors is empty.
  • The structured tracing::warn! fields follow the logging convention.

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 1 high-severity finding

Reviewed commit 26a3077.

Comment on lines 491 to +494
let iter_start = crate::time::Instant::now();
#[allow(unused_assignments)]
let mut branch: &'static str = "unknown";
let ping_silence_wait = engine_ping_silence_wait(&ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 High · Ping liveness is not synchronized with the select loop or connection session

last_ping_ts is updated in forward_to_envoy without waking this loop. On a fresh connection, command replay can start actors before the ping task sends its first ping; this iteration therefore builds None, and subsequent pings do not arm a deadline until another envoy message or the 15-second KV cleanup tick. With the default 15-second lost threshold, a link that goes half-open in that window can let the engine expire the envoy before this branch runs, defeating the single-writer protection this change is meant to add. The timestamp also survives reconnects, so a reconnect after the old deadline immediately loses still-running or replayed actors before the new connection's first ping. Make ping reception/session changes an event observed by this loop (for example, a watch channel carrying the current session's last-ping value), reset it when a connection is established, and derive/restart the deadline from that event.

@abcxff
abcxff force-pushed the stack/fix-rivetkit-stop-lost-actor-generations-immediately-opswolsp branch from 2b3bd85 to 8d4a713 Compare October 3, 2026 02:05
@abcxff
abcxff force-pushed the stack/fix-envoy-client-declare-actors-lost-before-the-engine-can-reallocate-them-nkyokown branch from 26a3077 to 6a97303 Compare October 3, 2026 02:05

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