Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

c.destroy() does not destroy an actor with a db when state changed in the same action (transaction_closed on shutdown state save)

Aperta
#5,850 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@octopols ci sta già lavorando.

Dal 19/10/2026.

  • #5893 di @octopols — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, rust, sqlite, typescript
Ambito
backend, database

Direzione di ricerca

The report includes a TypeScript/Vitest reproduction but names no Rust source files or tests. Start by running the reproduction and tracing the actor shutdown path from the reported DestroyGrace and Destroying transitions to serialize_state_save and the SQLite VFS close. Done when the state-change-then-destroy case reliably destroys the actor without a write to a closed database; compare it with the clean-state destroy case.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Description

c.destroy() does not destroy an actor that has a db when c.state changed in the same action and has not been saved yet.

During the destroy, the actor's SQLite VFS closes in the grace phase. After that, the Destroying phase saves the pending state deltas (serialize_state_save) to the closed database. The write fails with transaction_closed, the stop ends as Stopped{code: Error}, and the actor stays alive with the same actor id. With the crash-as-sleep change, the next request wakes the same actor as generation 2.

The same actor with no state change before c.destroy() is destroyed correctly.

Environment
  • rivetkit: 2.3.25 (also 2.3.15, 2.3.16, 2.3.17, 2.3.18, 2.3.21). Passes on 2.3.10 and 2.3.11.
  • Engine: rivetdev/engine:2.3.25 in Docker on localhost:6420, with RIVET__AUTH__ADMIN_TOKEN and RIVET__AUTH__INSECURE_ALLOW_UNAUTHENTICATED=true
  • RIVET_RUN_SERVICES=0
  • Node 22.23.1, macOS arm64, Vitest 3
Reproduction
import { randomUUID } from "node:crypto";
import { actor, setup } from "rivetkit";
import { db } from "rivetkit/db";
import { setupTest } from "rivetkit/test";
import { expect, it } from "vitest";

const counter = actor({
  createState: () => ({ count: 0 }),
  db: db({ onMigrate: (d) => d.execute("CREATE TABLE IF NOT EXISTS t (v TEXT)") }),
  actions: {
    ping: () => true,
    destroy: (c) => {
      c.destroy();
    },
    bumpThenDestroy: (c) => {
      c.state.count += 1;
      c.destroy();
    },
  },
});

const registry = setup({ use: { counter }, envoy: { poolName: `repro-${randomUUID()}` } });

async function destroyedAfter(t: any, action: "destroy" | "bumpThenDestroy") {
  const { client } = await setupTest(t, registry);
  const key = [randomUUID()];
  const handle = client.counter.getOrCreate(key);
  await handle.ping();
  const before = await handle.resolve();
  await handle[action]();
  await new Promise((r) => setTimeout(r, 1500));
  const getFails = await client.counter.get(key).resolve().then(() => false, () => true);
  const after = await client.counter.getOrCreate(key).resolve();
  return { getFails, newActorId: after !== before };
}

it("destroy with clean state destroys the actor", async (t) => {
  expect(await destroyedAfter(t, "destroy")).toEqual({ getFails: true, newActorId: true });
});

it("destroy right after a state change destroys the actor", async (t) => {
  expect(await destroyedAfter(t, "bumpThenDestroy")).toEqual({ getFails: true, newActorId: true });
});

Result on 2.3.25: the first test passes. The second test fails every run:

AssertionError: expected { getFails: false, newActorId: false } to deeply equal { getFails: true, newActorId: true }
Logs (debug, shortened)
actor lifecycle transition old=Started new=DestroyGrace
invoking napi TSF callback kind=onDestroy
sqlite vfs close summary generation=Some(1) ... commit_count=27
napi run handler exited cleanly
actor lifecycle transition old=DestroyGrace new=Destroying
actor event enqueued operation=shutdown_serialize_state event=serialize_state_save
applying actor state deltas delta_count=1 delta_bytes=0 state_revision=2 save_request_revision=3
sqlite operation failed backend=LocalNative operation=execute_batch sql=<batch> binding_count=2 statement_count=2 code=transaction_closed error_message="SQLite transaction coordinator is closed."
actor stop had both shutdown and join failures; only the shutdown error is returned shutdown_error="transaction_closed: SQLite transaction coordinator is closed." discarded_join_error="actor task failed"
actor stop failed after asynchronous completion handoff
EventActorStateUpdate{state: Stopped{code: Error, message: "transaction_closed: SQLite transaction coordinator is closed."}}
Bisect (same engine 2.3.25, only the rivetkit version changes)
rivetkit Result
2.3.10, 2.3.11 both pass
2.3.12, 2.3.13 both tests time out (perhaps not compatible with engine 2.3.25)
2.3.14 did not install from npm
2.3.15 to 2.3.25 state-change case fails
Expected

The pending state save happens before the database closes (or it is skipped on destroy), and the actor is destroyed.

Impact

Any actor that sets state and then self-destroys in the same action is never destroyed. We found it in two actors: a tool approval actor that self-destroys from a scheduled action, and a loop actor that resets through c.destroy().

Possibly related: #5554, #5555 (transaction_closed after the SQLite coordinator closes).

Lingua principale
Rust
Stelle
6.5k
Fork
291
Merge medio
14h 38m
PR unite (30g)
38

Preparare l'ambiente

  • Nessun Dockerfile né file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di rivet-dev/rivet

Tutte le issue di rivet-dev/rivet

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.