Existing actor stays unreachable (no_envoys, sliding reschedule) after runner restart if woken while no envoy is connected — Wake ignored in Reallocating (2.3.27)
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 35/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- rust
- Área
- backend, distributed-systems
Línea de trabajo
Read engine/packages/pegboard/src/workflows/actor2/mod.rs, especially the Reallocating transition and Main::Wake handling, then inspect the retry logic in runtime.rs and the threshold in config/pegboard.rs. Reproduce the runner restart with repeated wake requests and compare it with the quiet control. Done when an existing actor can reallocate after an envoy returns despite continuing wake traffic, with regression coverage for both cases.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Disclosure: this report and the reproduction behind it were produced by AI agents (coding agents running a validation campaign for a project built on Rivet), reviewed by an AI orchestrator, and filed with the project owner's approval. We've tried to separate what we observed from what we inferred from source. Apologies for anything we got wrong. Happy to rerun anything you'd like.
Summary
On Rivet Engine 2.3.27 in serverful (envoy) mode, if a request wakes an existing actor while no envoy is connected, the actor fails allocation with no_envoys. It then stays unreachable after the runner comes back, as long as requests keep arriving. Each new request pushes reschedule_ts further out and is not served. A fresh actor key on the same runner works immediately. The stuck actor recovers right away after POST /actors/{id}/sleep plus one request, with its durable state intact.
In our original incident, a runner restart under normal client traffic (clients reconnecting and pinging) made the actor unavailable for about 318 s. That's consistent with the default actor_retry_duration_threshold of 5 min, after which the actor goes to Sleeping and the next wake succeeds.
Environment
rivetdev/engine:2.3.27(Docker), single node, local filesystem/native SQLite storage (backend=LocalNative)rivetkit2.3.27, serverful / native envoy mode, Node 24 runner- One runner, runner selector
default, namespacedefault, actor crash policysleep - Isolated engine with its own data directory, with no other traffic
Reproduction (confirmed)
Times are relative to the runner kill (t = 0).
- Start the engine and one runner. Create an actor (
getOrCreateby key) and write some durable state to it. - SIGKILL the runner and keep it down.
- Poll the actor record (
GET /actors?namespace=default&actor_ids=<id>) only, sending no requests to the actor, until it showserror = envoy_connection_lost. That happened at t ≈ 15.3 s, withsleep_tsset andreschedule_ts = null. - While the runner is still down (t ≈ 15.3 s), start sending requests to that existing actor through the gateway (
POST /gateway/<actor>/action/ping?rvt-method=getOrCreate&rvt-key=<key>&rvt-runner=default&rvt-crash-policy=sleep). Send them one at a time with an 8 s client timeout, so requests arrive at t ≈ 15.3 s, 23.6 s and 31.9 s.- About 3 s later the record shows
error = no_envoysandreschedule_ts≈ t + 19.3 s.
- About 3 s later the record shows
- Start the runner again (start issued at t ≈ 18.6 s) while requests to the existing key continue.
- Control: a request to a fresh key at t ≈ 22.6 s → HTTP 200 in 77 ms. The runner is healthy.
- Existing key: it stays at
error = no_envoys. Across samples,reschedule_tsmoved to t + 35.3 s, then t + 55.6 s (first seen right after the t ≈ 23.6 s request arrived), then t + 95.9 s (right after the t ≈ 31.9 s request). Three requests each timed out at our 8 s client timeout, with zero successes. POST /actors/{id}/sleep?namespace=default→ HTTP 200 in 2 ms. One request afterwards → HTTP 200 in 62 ms, the error clears andconnectable_tsis set. The actor's durable state was unchanged: its single stored ledger row, pending count and cursor/epoch all matched pre-fault values.
Quiet control (confirmed)
The same steps, except no requests are sent to the actor until after the runner is back. We SIGKILLed, waited about 24 s until envoy_connection_lost, restarted the runner, then sent the first request. The actor never showed no_envoys. The first request succeeded in 64 ms with the same durable state.
So the trigger is a wake while zero envoys are available, followed by continued wake traffic. Losing the runner alone doesn't trigger it.
Observed actor-record transitions
connectable
→ error=envoy_connection_lost, sleep_ts set, reschedule_ts=null (runner killed)
→ error=no_envoys, reschedule_ts set (first wake while no runner)
→ error=no_envoys, reschedule_ts keeps sliding later (runner back, requests continuing)
→ error cleared, connectable_ts set (POST /sleep, then one request)
The public record doesn't expose the workflow transition or generation. The Reallocating interpretation below comes from source, not from a log line we observed.
Expected vs actual
- Expected: once an envoy is registered again, the next request (or the next retry) allocates the existing actor, as it does for a fresh key and in the quiet control.
- Actual: the existing actor stays unreachable while traffic continues, for up to
actor_retry_duration_threshold(5 min by default). Gateway requests time out instead of being served.
Likely mechanism (from source, v2.3.27; inferred, not instrumented)
engine/packages/pegboard/src/workflows/actor2/mod.rs:
- L604–631,
Transition::Reallocating: each loop callsget_next_retry_ts(), which incrementsretry_count(inruntime.rs). It then doeslisten_n_until(next_retry_ts).reschedule_actoris only attempted if no signal arrived (if signals.is_empty()). Any incoming signal therefore cancels that retry attempt, and the next loop has a longer backoff. - L913–931,
Main::WakeduringReallocatingfalls into_ =>, logs"cannot wake actor that is not sleeping"and does nothing. - L607–613: the actor goes to
Sleepingonly oncelast_retry_ts > since_ts + actor_retry_duration_threshold(config/pegboard.rsL251–253, default5 * 60 * 1000). - L965–968,
Main::SleepduringReallocating→Sleeping. A following Wake then callsreschedule_actor, which matches the sleep→wake workaround we observed.
So with a steady stream of wakes arriving faster than the growing backoff, the actor never attempts reallocation until the 300 s threshold.
Not yet confirmed by us: we haven't run a variant where requests stop entirely after the runner returns, to check that the actor then recovers at reschedule_ts by itself.
Hypotheses we checked and ruled out as the trigger
- SQLite close at shutdown (#5554 / PR #5860): we see
failed to sync scheduled actor alarm … sqlite transaction coordinator is closed (transaction_closed)andSELECT MIN(trigger_at) FROM _rivet_schedule_events … transaction_closedwhen the runner stops. Fresh actors that stayed healthy logged the same error, and the quiet control recovers despite it. Our app doesn't schedule alarms, and_rivet_schedule_eventswas empty when we inspected it. It's real, but not the trigger here. - #5859 (dropped event dispatch): its
actor event processor failedandfailed to ingest event/channel closedsignatures are absent from our logs. - #5738 (drain eviction / VFS loss): its signatures are absent, and it's a different version and setup.
- #5484 (protocol mismatch): it doesn't fit, because fresh actors work on the same runner.
- #5835 (non-hibernating WebSocket default) is already included in 2.3.27.
We found no released fix after 2.3.27, and main appears to have the same Reallocating / Wake handling.
Workaround we're using
A supervisor in our runner, after envoy registration, probes each actor with a short timeout. On a timeout or no_envoys with a future reschedule_ts, it calls POST /actors/{id}/sleep then sends one normal request. It's idempotent, with bounded retries and backoff. This preserves actor storage in our tests.
Possible fixes (suggestions only)
- In
Reallocating, treatMain::Wakeas "retry now", i.e. attemptreschedule_actorimmediately. Alternatively, don't let an interrupting signal skip the pending retry or advance the backoff. - And/or re-trigger reallocation for actors in
Reallocatingwhen a matching envoy registers.
We can share the repro script, with endpoints and tokens removed, if that helps.
- Lenguaje dominante
- Rust
- Estrellas
- 6.3k
- Forks
- 279
- Merge medio
- 1 d 9 h
- PR fusionados (30 d)
- 53
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de rivet-dev/rivet
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
rivet-dev/rivet#5891 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Rust client drops connection-level errors when actionId is null (JSON/CBOR)Posiblemente ocupada @DibbayajyotiRoy la tomó hace 19 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
rivet-dev/rivet#5819 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Queue docs say completable messages are removed on receive, but they stay stored until complete()Posiblemente ocupada @Saisharathchandranandnetha la tomó hace 29 días. Abierto
Dificultad 1/5 1-3 horas Aptitud para principiantes 91/100
rivet-dev/rivet#5762 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Serverless listener forwards /start to the application when basePath is "/"Posiblemente ocupada @Saisharathchandranandnetha la tomó hace 32 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 90/100
rivet-dev/rivet#5755 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
rivet-dev/rivet#5909 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de rivet-dev/rivet
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
peteonrails/voxtype#909 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 69/100
alchemyplatform/rundler#1358 ·
Los mantenedores suelen responder en 3 días
-
[BUG] VA-API HEVC: out-of-range cu_qp_delta (measured -130..+56) produces a corrupted bitstreamAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
selkies-project/pixelflux#52 · 4 comentarios ·
Los mantenedores suelen responder en 1 día
-
llm translation
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
BerriAI/litellm#47884 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area:testing bug good first issue platform:windows priority:medium
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
rtk-ai/rtk#4728 · 1 comentario ·
Los mantenedores suelen responder en 1 día