tursodatabase/turso · critical

clear_savepoints should not fail for attached DB

Error message

clear_savepoints should not fail for attached DB

What it means

Pager::rollback_attached() is the attached-database variant of transaction rollback: it calls clear_savepoints().expect("clear_savepoints should not fail for attached DB") on the same memory-IO-only subjournal. As with rollback_tx, a failure here is treated as an impossible condition that must not be silently swallowed while the attached db's write lock is still held.

Source

Thrown at core/storage/pager.rs:3474

    }

    pub fn holds_write_lock(&self) -> bool {
        let Some(wal) = self.wal.as_ref() else {
            return false;
        };
        wal.holds_write_lock()
    }

    /// Rollback and clean up an attached database pager's transaction.
    /// Unlike rollback_tx, this doesn't modify connection-level state.
    pub fn rollback_attached(&self) {
        let Some(wal) = self.wal.as_ref() else {
            return;
        };
        let is_write = wal.holds_write_lock();
        if is_write {
            self.clear_savepoints()
                .expect("clear_savepoints should not fail for attached DB");
            // Clear dirty pages and page cache before releasing the write lock
            self.clear_page_cache(true);
            self.dirty_pages.write().clear();
            self.reset_internal_states();
            self.set_schema_cookie(None);
            wal.rollback(None);
            wal.end_write_tx();
        } else {
            self.cleanup_read_tx();
        }
        if wal.holds_read_lock() {
            wal.end_read_tx();
        }
    }

    /// Reads a page from disk (either WAL or DB file) bypassing page-cache
    #[tracing::instrument(skip_all, level = Level::DEBUG)]
    /// Reads a page without going through the page cache. The read is

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Report to Turso with the ATTACH/rollback sequence
  2. Detach or reopen the connection and retry; uncommitted attached-db state is recoverable from disk
  3. Run integrity checks on the attached databases afterwards
  4. Keep write transactions on attached databases short to shrink subjournal usage
Defensive patterns

Strategy: fallback

Try / catch

let result = std::panic::catch_unwind(AssertUnwindSafe(|| handle_rollback(&conn)));
if result.is_err() {
    // Attached-db rollback cleanup failed: drop the whole connection and reopen
    // both the main and attached databases from disk.
    drop(conn);
    let conn = reopen_with_attach(&main_path, &attached_path, &io)?;
}

Prevention

When it happens

Trigger: Rolling back a write transaction on an ATTACHed database (or engine-driven cleanup of attached pagers) when the in-memory subjournal write/remove errors.

Common situations: Multi-database applications using ATTACH with savepoints, memory pressure during attached-db rollbacks, engine regressions in subjournal handling for attached pagers.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/9412113021bf58bd. Report an issue: GitHub.