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 isView on GitHub (pinned to 492c4a71cd)
Solutions
- Report to Turso with the ATTACH/rollback sequence
- Detach or reopen the connection and retry; uncommitted attached-db state is recoverable from disk
- Run integrity checks on the attached databases afterwards
- 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
- Keep transactions spanning ATTACHed databases short to limit subjournal growth
- Watch memory pressure when many savepoints are open across attached databases
- Reopen connections (main + attached) after any rollback panic rather than retrying in place
- Verify attached databases with integrity checks after recovery
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
- in practice, clear_savepoints() should never fail as it uses
- subjournal must be opened
- buffer not loaded
- DB should not be initialized and should not do any IO
- Page size too small, a ptrmap page cannot map any db pages.
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/9412113021bf58bd.
Report an issue: GitHub.