Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs-internal/engine/rivetkit-core-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ Persistence order:

1. Immediate state save.
2. Pending state write wait.
3. Alarm write wait.
4. SQLite cleanup.
5. Driver alarm cancellation.
3. Sync the driver alarm for sleep, or cancel it without querying SQLite for destroy.
4. Alarm write wait.
5. SQLite cleanup.

## ActorConfig

Expand Down
21 changes: 11 additions & 10 deletions rivetkit-rust/packages/rivetkit-core/src/actor/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1864,17 +1864,10 @@ impl ActorTask {
step = "wait_for_pending_state_writes",
"actor shutdown cleanup step completed"
);
ctx.sync_alarm_logged().await;
tracing::debug!(
actor_id = %actor_id,
reason = reason_label,
step = "sync_alarm",
"actor shutdown cleanup step completed"
);
// Destroy cancels the engine alarm here so the persist it spawns is awaited by
// `wait_for_pending_alarm_writes` below and cannot race the SQLite teardown.
match reason {
ShutdownKind::Destroy => {
// A foreign runtime may close SQLite during its destroy callback. Skip
// the SQLite-backed alarm sync because this alarm is cleared anyway.
ctx.cancel_driver_alarm_logged();
tracing::debug!(
actor_id = %actor_id,
Expand All @@ -1883,7 +1876,15 @@ impl ActorTask {
"actor shutdown cleanup step completed"
);
}
ShutdownKind::Sleep => {}
ShutdownKind::Sleep => {
ctx.sync_alarm_logged().await;
tracing::debug!(
actor_id = %actor_id,
reason = reason_label,
step = "sync_alarm",
"actor shutdown cleanup step completed"
);
}
}
ctx.wait_for_pending_alarm_writes().await;
tracing::debug!(
Expand Down
36 changes: 36 additions & 0 deletions rivetkit-rust/packages/rivetkit-core/tests/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3289,6 +3289,42 @@ pub(crate) mod moved_tests {
assert_eq!(ctx.test_driver_alarm_cancel_count(), 1);
}

#[tokio::test]
async fn destroy_cleanup_does_not_sync_alarm_after_runtime_closes_sqlite() {
let ctx = new_with_kv(
"actor-destroy-closed-sqlite",
"task-destroy-closed-sqlite",
Vec::new(),
"local",
new_in_memory(),
);
ctx.sql()
.close()
.await
.expect("foreign runtime sqlite close should succeed");

let records = Arc::new(Mutex::new(Vec::new()));
let subscriber = Registry::default().with(ActorTaskLogLayer {
records: records.clone(),
});
let dispatch = tracing::Dispatch::new(subscriber);
ActorTask::finish_shutdown_cleanup_with_ctx(ctx.clone(), ShutdownKind::Destroy)
.with_subscriber(dispatch)
.await
.expect("destroy cleanup should succeed after sqlite closes");

assert_eq!(ctx.test_driver_alarm_cancel_count(), 1);
assert!(
!records
.lock()
.expect("actor-task log lock poisoned")
.iter()
.any(|record| record.message.as_deref()
== Some("failed to sync scheduled actor alarm")),
"destroy cleanup must not query the closed sqlite coordinator"
);
}

#[tokio::test(start_paused = true)]
async fn sleep_shutdown_without_in_flight_work_finishes_under_baseline() {
let ctx = new_with_kv(
Expand Down
Loading