Repository navigation
fix(pegboard): retry an actor's due alarm when no envoy can start it - #5951
eersnington wants to merge 1 commit into
Conversation
TitleOverdue alarm is consumed before an envoy reconnects after an engine restart (3.0.0-preview.5) SummaryIn case it's helpful: A one-shot schedule due while the standalone engine is down does not run automatically after the engine restarts, although its worker process stays alive and the actor's schedule survives. The same minimal engine-only outage succeeds with the existing PR #5951 retry change applied to the exact preview.5 source. This report is specifically about Sleeping + due alarm + no eligible envoy, not every startup/reallocation failure. Versions and topology
The npm engine hash is the executable pin; the npm SDK gitHead is not independent proof of the released executable's build commit. The local source/build controls remove that ambiguity for the patched/unpatched comparison. Minimal reproductionUse a normal persistent worker pool named arm: async (c, token) => {
const armedAt = Date.now();
const dueAt = armedAt + 60_000;
const scheduleId = await c.schedule.at(dueAt, "fire", token, dueAt);
c.state.arm = { token, dueAt, scheduleId };
await c.saveState({ immediate: true }); // normal request, not scheduled handler
return { actorId: c.actorId, armedAt, dueAt, scheduleId };
},
fire: (c, token, dueAt, scheduledFire) => {
// Append and fsync an external JSONL record here, including Date.now(),
// actor ID, process PID, token, dueAt and native scheduledFire metadata.
// Do not use an actor query to observe the result.
}The observer records native Install the pinned packages with ./node_modules/@rivetkit/engine-cli-linux-x64-musl/rivet-engine --config engine.jsonThe tested single-engine configuration binds guard, peer and metrics to export RIVET__STORAGE__LOG_DIRECTORY="$PWD/engine-data/log"
export RIVET__OBJECT_STORAGE__PRIMARY__FILE_SYSTEM__PATH="$PWD/engine-data/objects"
export RIVET__STORAGE__AUTO_PROVISION_PARTITIONS=1
export RIVET__STORAGE__BALANCING__ENABLED=false
export RIVET__STORAGE__WRITE_TO_COLD_AFTER_MS=5000
export RIVET__STORAGE__LOG_SHARD__MAX_DISK_BYTES=4294967296
export RIVET__STORAGE__COLD_STORE__DISK_CACHE_BYTES=0
export RIVET__STORAGE__STOP_TIMEOUT_MS=6000
export RIVET__STORAGE__STOP_TEARDOWN_MS=2000
export RIVET__PEGBOARD__NODE_LOST_THRESHOLD=40000These are the preview.5 launcher's accepted environment names; current docs use some newer
After the frozen window, an explicitly separate caller may invoke a no-op/inspect action on the original actor to establish engine/worker readiness and test whether the original overdue schedule is retained. Such a caller-triggered fire must never turn the preceding negative result into a pass. Expected and observedExpected: once the engine and worker reconnect, the overdue alarm autonomously starts the sleeping actor and invokes the retained schedule; recurring work resumes without a request to that actor. Observed full 600-second windows: stock preview.5 0/3 autonomous fires; patched preview.5 3/3; 2.3.31 3/3. The same-toolchain/profile local unpatched engine also produced 0/3. A separate stock readiness cohort reproduced 0/3 through its frozen windows; a later caller then successfully started the original actor and caused its original overdue schedule to fire in 3/3, proving engine/worker usability and retention without upgrading any no-caller result. The matched recurrence control produced 0 ticks on stock and 11 distinct post-restart ticks on patched, with no caller and no outage-time ticks. All trial engines/workers were stopped with terminal/ownership read-back. One initial pre-arm storage-routing failure is excluded, not counted as an alarm regression. These are bounded observations, not a claim that a zero-fire run could never recover later. The three patched minimal trials fired 3.990, 4.010 and 4.019 seconds after engine restart. Example on 2026-11-01 UTC: arm Suspected cause and existing fixAt the pinned preview.5 base:
PR #5951 reuses the existing durable timer mechanism to rearm this case after 1000 ms plus the existing 0–1999 ms jitter. It does not add a scanner, schema or protocol. The direct regression test fails on original code and passes with this change; immediate available-allocation startup remains passing. Runtime results support this mechanism, but the original incident's exact allocation result was not instrumented, so its causal attribution remains a root-cause guess, not a captured transition trace. Known limits — not observed in these trials and not introduced by this patch
This is distinct from #5833's claimed SQL action loss, scheduled-handler immediate-save failures, and general fenced-storage restart failures. No broader recovery guarantee or exactly-once side-effect guarantee is asserted. |
An actor alarm that comes due while no envoy is connected is lost. The actor never wakes.
This happens when the engine starts with overdue alarms, before envoys reconnect, or when an alarm comes due during shutdown.