tursodatabase/turso · critical
result should be set
Error message
result should be set
What it means
Turso's pager runs WAL checkpoints as an async state machine. After a checkpoint's sync phase completes, next_post_sync_checkpoint_phase (core/storage/pager.rs:4583) locks the shared checkpoint state and unwraps state.result (a CheckpointResult stored in the state struct, pager.rs:1035) with expect("result should be set"). The panic means the pager tried to compute the next checkpoint phase while checkpoint_state.result was None - an internal invariant violation, typically a race where the checkpoint state was reset (e.g. by cleanup_after_checkpoint_failure -> reset_checkpoint_state after an I/O error) while a post-sync phase transition at pager.rs:4813 was still in flight.
Source
Thrown at core/storage/pager.rs:4858
state.lock_source = CheckpointLockSource::Acquire;
}
/// Clean up after a auto-checkpoint failure.
/// Auto-checkpoint executed outside of the main transaction - so WAL transaction was already finalized
pub fn cleanup_after_auto_checkpoint_failure(&self) {
self.cleanup_after_checkpoint_failure();
}
pub fn cleanup_after_checkpoint_failure(&self) {
self.reset_checkpoint_state();
if let Some(wal) = self.wal.as_ref() {
wal.abort_checkpoint();
}
}
fn next_post_sync_checkpoint_phase(&self, clear_page_cache: bool) -> CheckpointPhase {
let state = self.checkpoint_state.read();
let result = state.result.as_ref().expect("result should be set");
let mode = state.mode.expect("mode should be set");
if result.wal_checkpoint_backfilled > 0
&& !matches!(
mode,
CheckpointMode::Restart | CheckpointMode::Truncate { .. }
)
{
// if we are using a custom codec, then we might have to read the whole page 1 so that
// it can be decoded. Otherwise reading the header is enough.
let read_page = self.io_ctx.read().has_codec_transform();
let read_size = if read_page {
self.get_page_size_unchecked().get() as usize
} else {
PageSize::MIN as usize
};
return CheckpointPhase::ReadDbIdentity {
clear_page_cache,
read: PendingCheckpointDbIdentityRead {View on GitHub (pinned to 492c4a71cd)
Solutions
- Re-run with RUST_BACKTRACE=1 to capture the full panic trace and note the exact sequence of checkpoint calls, then report it as a Turso engine bug with a minimal reproduction - this is an internal state-machine bug, not caller error.
- Upgrade to the latest tursodb release; the checkpoint state machine is actively developed and the race may already be fixed.
- Serialize checkpoints: let a single connection run PRAGMA wal_checkpoint instead of several connections checkpointing concurrently.
- If it reproduces on an encrypted/codec database or after an injected I/O failure, include that detail in the bug report (both branches are visible in the code path).
Example fix
-- before: multiple connections checkpointing concurrently -- conn A: PRAGMA wal_checkpoint(TRUNCATE); -- conn B: PRAGMA wal_checkpoint(TRUNCATE); -- after: single dedicated connection, others just write -- checkpoint conn: PRAGMA wal_checkpoint(TRUNCATE);
Defensive patterns
Strategy: try-catch
Try / catch
Rust host: wrap the checkpoint call in std::panic::catch_unwind (AssertUnwindSafe) to contain the engine panic, then treat the checkpoint as failed and retry later. Python/JS bindings: catch the RuntimeError the panic is surfaced as around `PRAGMA wal_checkpoint` execution.
Prevention
- Run PRAGMA wal_checkpoint from a single dedicated connection instead of checkpointing concurrently from many connections.
- Keep tursodb pinned to a version you have stress-tested; re-test checkpoints (RESTART/TRUNCATE) after every engine upgrade.
- Run with RUST_BACKTRACE=1 in staging so any invariant panic carries a usable trace.
- Avoid issuing checkpoints while injecting I/O failures or running codec/encrypted databases together with concurrent checkpoint load until the state machine is fixed.
When it happens
Trigger: Issuing PRAGMA wal_checkpoint (especially RESTART/TRUNCATE/FULL) that reaches the post-sync phase transition at pager.rs:4813 after checkpoint state was reset by cleanup_after_checkpoint_failure(); concurrent wal_checkpoint calls from multiple connections racing an abort; checkpoints on databases where wal_checkpoint_backfilled > 0, which takes the page-1 re-read branch when the DB has a codec transform (has_codec_transform).
Common situations: Concurrent checkpointing under load, checkpoint retry after a transient I/O error, encrypted/codec-wrapped databases, custom VFS with fault injection, WAL stress or MVCC test workloads, engine version skew after a checkpoint-state-machine refactor.
Related errors
- Memory allocation failed here
- PreparingWalStart requires a WAL
- spill_append_frames_to_wal requires a WAL
- No WAL to checkpoint
- buffer not loaded
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/333099a97d2014f8.
Report an issue: GitHub.