{"record":{"id":"edd880e59302899c","repo":"tursodatabase/turso","slug":"in-practice-clear-savepoints-should-never-fail","errorCode":null,"errorMessage":"in practice, clear_savepoints() should never fail as it uses memory IO","messagePattern":"in practice, clear_savepoints\\(\\) should never fail as it uses memory IO","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/storage/pager.rs","lineNumber":3412,"sourceCode":"\n    #[instrument(skip_all, level = Level::DEBUG)]\n    pub fn rollback_tx(&self, connection: &Connection) {\n        if connection.is_nested_stmt() {\n            // Parent statement will handle the transaction rollback.\n            return;\n        }\n        let Some(wal) = self.wal.as_ref() else {\n            // TODO: Unsure what the semantics of \"end_tx\" is for in-memory databases, ephemeral tables and ephemeral indexes.\n            return;\n        };\n        let (is_write, schema_did_change) = match connection.get_tx_state() {\n            TransactionState::Write { schema_did_change } => (true, schema_did_change),\n            _ => (false, false),\n        };\n        tracing::trace!(\"rollback_tx(schema_did_change={})\", schema_did_change);\n        if is_write {\n            self.clear_savepoints()\n                .expect(\"in practice, clear_savepoints() should never fail as it uses memory IO\");\n            // IMPORTANT: rollback() must be called BEFORE end_write_tx() releases the write_lock.\n            // Otherwise, another thread could commit new frames to frame_cache between\n            // end_write_tx() and rollback(), and rollback() would incorrectly remove them.\n            self.rollback(schema_did_change, connection, is_write);\n            wal.end_write_tx();\n        } else {\n            self.rollback(schema_did_change, connection, is_write);\n        }\n        wal.end_read_tx();\n    }\n\n    pub(crate) fn cleanup_read_tx(&self) {\n        let Some(wal) = self.wal.as_ref() else {\n            return;\n        };\n        self.reset_internal_states();\n        if wal.holds_read_lock() {\n            wal.end_read_tx();","sourceCodeStart":3394,"sourceCodeEnd":3430,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/storage/pager.rs#L3394-L3430","documentation":"Pager::rollback_tx(), while rolling back a write transaction, calls clear_savepoints().expect(\"in practice, clear_savepoints() should never fail as it uses memory IO\"). The subjournal is backed by MemoryIO so its writes complete inline; a failure there (allocation or subjournal bug) is converted into a loud panic instead of silently discarding savepoint state.","triggerScenarios":"ROLLBACK of a write transaction that had opened savepoints/subjournal entries, when the in-memory subjournal operation returns an error - memory exhaustion inside MemoryIO or a subjournal format regression.","commonSituations":"Severe memory pressure during long write transactions, engine regressions in subjournal bookkeeping, retrying an interrupted rollback on the same pager.","solutions":["Report to Turso with the transaction shape - memory-IO failure here is an engine defect","Retry the rollback on a fresh connection; pre-commit on-disk state is unchanged so reopening is safe","Run PRAGMA integrity_check after reopening","Shorten write transactions / reduce savepoint churn to lower subjournal memory footprint"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(AssertUnwindSafe(|| conn.execute(\"ROLLBACK\", ())));\nif result.is_err() {\n    // Do not reuse the connection: discard it and reopen from disk.\n    // WAL recovery replays only committed frames, so on-disk state stays consistent.\n    drop(conn);\n    let conn = reopen_database(&path, &io)?;\n}","preventionTips":["Keep write transactions short so the savepoint/subjournal set stays small at rollback","Monitor memory usage; the subjournal is memory-backed and allocation failure is the realistic trigger","Never continue using a connection after a rollback-path panic - reopen instead","Run integrity_check after reopening"],"tags":["pager","rollback","savepoint","panic"],"backgroundTag":"rollback-cleanup-failure","analyzedSha":"492c4a71cd7c2649e7df83da1471b74f4b1c7aa9","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}