feat(container-runner): self-sleep on repeated actor start - #5585
Conversation
|
Stack for rivet-dev/actors
Get stack: change qspsskoy |
aee8b0b to
ef4d957
Compare
3cda8ae to
892453d
Compare
Review:
|
ReviewReviewed the container-runner self-sleep-on-repeated-start feature (RIVET_REJECT_SECOND_START). Overall the implementation is careful: the guard is placed after the existing actor-already-running idempotency check (so live retries aren't misclassified), it reuses the existing ctx.sleep() -> on_sleep -> stop_child machinery instead of adding a new shutdown path, and the ActorInput -> ActorState migration via #[serde(flatten)] + #[serde(default)] correctly handles decoding state persisted by older binaries. A few things worth a look: Worth confirming (semantics). mark_started_once permanently latches started_once = true after the first real start. In non-idle mode (idle_timeout().is_none()) this happens unconditionally right after the child spawns successfully, not just for a duplicate-start-message race. That means any later wake of this actor generation-lineage (e.g. an engine/dashboard-initiated sleep for eviction, or crash-policy restart) will hit the guard and immediately self-sleep instead of respawning the child, since the flag survives sleep. Given on_sleep's own comment (the engine can still sleep an actor for dashboard, crash policy, eviction) implies these are meant to be resumable pauses, it would be good to confirm this is the intended one-shot-match semantics (a woken match cannot resume state anyway) rather than an accidental foreclosure of legitimate engine-driven wake-after-sleep for this runner. If intentional, a short doc note on RIVET_REJECT_SECOND_START clarifying that actors using this flag are treated as single-lifetime, even across engine-initiated sleeps, would save the next reader from re-deriving this from the code. Minor: formatting. container-runner/src/main.rs's new REJECT_SECOND_START closure line (the .map(...matches!(...)) chain) looks like it exceeds the default 100-col rustfmt width (project uses hard_tabs = true with default max_width), and the identical boolean-env pattern in monitor.rs::monitor_enabled wraps the matches! macro across multiple lines. Worth running the formatter on this file to match and avoid a CI fmt-check failure. Test coverage. The new tests (actor_state_cbor_round_trips, legacy_bare_input_state_decodes_into_actor_state) solidly cover the serde/CBOR migration shape, which is the main this-will-silently-break-on-a-rollback risk. The actual reject-second-start control flow in actor.rs (on_start's guard, run's self-sleep branch, note_request's idle-mode deferral) has no test coverage, though actor.rs has no unit/integration tests at all today (only input.rs and boot_id.rs are covered under tests/inline/), so this may be consistent with how the rest of the actor lifecycle is tested elsewhere (e2e/driver tests) rather than a gap introduced by this PR specifically. Nit. note_request's mark_started_once read-then-write isn't atomic across concurrent on_fetch/on_websocket calls in idle mode, but since it's idempotent (same end state, at most a redundant request_save()), this looks fine. No security or performance concerns: the env var is process-local config, ciborium is a dev-dependency only, and everything else runs on existing hot paths already covered by the design constraints in CLAUDE.md. |
ef4d957 to
d82ea5e
Compare
892453d to
8617b0f
Compare
No description provided.