fix(envoy-client): dedupe replayed commands by index#4811
fix(envoy-client): dedupe replayed commands by index#4811NathanFlurry wants to merge 2 commits intoengine-stabilize/envoy-client-lifecyclefrom
Conversation
87d1a9d to
ab8fd17
Compare
415545d to
8801509
Compare
ab8fd17 to
20576a9
Compare
PR Review:
|
e4a53d3 to
a6d7cb6
Compare
31dfe70 to
45f80a0
Compare
a6d7cb6 to
93e48e9
Compare
93e48e9 to
4ef7f4e
Compare
3dbc40f to
1cd5bb4
Compare
1cd5bb4 to
d5c12d7
Compare
4ef7f4e to
71ea925
Compare
d5c12d7 to
436dba7
Compare
71ea925 to
058a579
Compare
|
Landed in main via stack-merge fast-forward push. Commits are in main; closing to match. |
Preview packages published to npmInstall with: npm install rivetkit@pr-4811All packages published as Engine binary is shipped via Docker images: docker pull rivetdev/engine:slim-6b7e318
docker pull rivetdev/engine:full-6b7e318Individual packagesnpm install rivetkit@pr-4811
npm install @rivetkit/react@pr-4811
npm install @rivetkit/rivetkit-napi@pr-4811
npm install @rivetkit/workflow-engine@pr-4811 |
1 similar comment
Preview packages published to npmInstall with: npm install rivetkit@pr-4811All packages published as Engine binary is shipped via Docker images: docker pull rivetdev/engine:slim-6b7e318
docker pull rivetdev/engine:full-6b7e318Individual packagesnpm install rivetkit@pr-4811
npm install @rivetkit/react@pr-4811
npm install @rivetkit/rivetkit-napi@pr-4811
npm install @rivetkit/workflow-engine@pr-4811 |

Stack Context
Builds on #4801 (
engine-stabilize/envoy-client-lifecycle), which already addedan immediate
send_command_ackafter everyhandle_commandsbatch. This PRcloses the residual race where a command arrives, is processed, but the ack
frame doesn't reach the engine before the WS drops — on reconnect,
pegboard-envoyre-streams the same command from UDB and the envoy processesit twice.
Why?
EnvoyContext::handle_commandshad no dedup on the receive path. Two concretebugs:
CommandStartActorclobbered live actors.insert_actorunconditionally re-
inserts intoctx.actors[actor_id][generation],dropping the existing handle,
event_history, andactive_http_request_counton the floor. The spawned actor task was orphaned.
CommandStopActorre-firedToActor::Stop. Idempotent inbest case, double-shutdown signal in worst case.
ActorEntry::last_command_idxalready existed but was only read bysend_command_ack— never checked against incoming command indices.What?
EnvoyContextgainsprocessed_command_idx: HashMap<(String, u32), i64>.Persists across
remove_actorso a replayed start for analready-stopped actor cannot resurrect it.
handle_commandsskips any command whosecheckpoint.index <= last_processedfor the
(actor_id, generation)pair, then records the new index beforedispatching.
tests/command_dedup.rscovers same-index replay,stale lower-index replay, monotonic processing, and per-actor / per-generation
isolation. Lives in
tests/rather than inline#[cfg(test)] mod testsbecause a pre-existing 7-vs-8 arg mismatch in
actor.rstest code blocks thelib-test target on this branch — the integration test target compiles
independently.
Test plan
cargo check -p rivet-envoy-clientcargo test -p rivet-envoy-client --test command_dedup(2 passed)