{"record":{"id":"40807fac1761a496","repo":"tursodatabase/turso","slug":"transaction-dropped-unexpectedly","errorCode":null,"errorMessage":"Transaction dropped unexpectedly.","messagePattern":"Transaction dropped unexpectedly\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"bindings/rust/src/connection.rs","lineNumber":117,"sourceCode":"    }\n\n    pub(crate) async fn maybe_handle_dangling_tx(&self) -> Result<()> {\n        match self.dangling_tx.load(Ordering::SeqCst) {\n            DropBehavior::Rollback => {\n                let mut stmt = self.prepare(\"ROLLBACK\").await?;\n                stmt.execute(()).await?;\n                self.dangling_tx\n                    .store(DropBehavior::Ignore, Ordering::SeqCst);\n            }\n            DropBehavior::Commit => {\n                let mut stmt = self.prepare(\"COMMIT\").await?;\n                stmt.execute(()).await?;\n                self.dangling_tx\n                    .store(DropBehavior::Ignore, Ordering::SeqCst);\n            }\n            DropBehavior::Ignore => {}\n            DropBehavior::Panic => {\n                panic!(\"Transaction dropped unexpectedly.\");\n            }\n        }\n        Ok(())\n    }\n\n    /// Query the database with SQL.\n    pub async fn query(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<Rows> {\n        self.maybe_handle_dangling_tx().await?;\n        let mut stmt = self.prepare(sql).await?;\n        stmt.query(params).await\n    }\n\n    /// Execute SQL statement on the database.\n    pub async fn execute(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<u64> {\n        self.maybe_handle_dangling_tx().await?;\n        let mut stmt = self.prepare(sql).await?;\n        stmt.execute(params).await\n    }","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/bindings/rust/src/connection.rs#L99-L135","documentation":"Panic from the Turso Rust binding's dangling-transaction handler. When a Transaction is dropped without commit or rollback, its DropBehavior is stored on the connection and resolved lazily by the next query/execute/prepare (maybe_handle_dangling_tx in connection.rs). With DropBehavior::Panic the resolution deliberately panics so a leaked transaction is never silent.","triggerScenarios":"Calling conn.begin(), letting the Transaction drop (early `?` return, cancelled task, dropped future) while DropBehavior::Panic is stored, then issuing any conn.query()/execute()/prepare() - those entry points call maybe_handle_dangling_tx first and hit the panic arm.","commonSituations":"Teams enabling Panic as a strict-discipline mode to catch missing commits; `?` operators between begin() and the end of the scope; timeouts or task cancellation dropping the future that owned the transaction.","solutions":["Explicitly tx.commit().await or tx.rollback().await on every path (match or scope guard) before the Transaction can drop","Call tx.finish().await to resolve the transaction consciously under the current behavior","Switch to DropBehavior::Rollback (default) or Ignore while you fix the leak","Restrict DropBehavior::Panic to tests where the panic itself is the assertion"],"exampleFix":"// before\nlet tx = conn.begin().await?;\nlet v = do_work(&tx).await?; // early return drops tx\n// ... later conn.query(..) panics: \"Transaction dropped unexpectedly.\"\n\n// after\nlet tx = conn.begin().await?;\nmatch do_work(&tx).await {\n    Ok(v) => { tx.commit().await?; Ok(v) }\n    Err(e) => { tx.rollback().await?; Err(e) }\n}","handlingStrategy":"validation","validationCode":"// guarantee an explicit outcome before the transaction can drop\npub async fn with_tx<T>(conn: &Connection, f: impl FnOnce(&Transaction<'_>) -> BoxFuture<'_, Result<T>>) -> Result<T> {\n    let tx = conn.begin().await?;\n    match f(&tx).await {\n        Ok(v) => { tx.commit().await?; Ok(v) }\n        Err(e) => { let _ = tx.rollback().await; Err(e) }\n    }\n}","typeGuard":null,"tryCatchPattern":"// it is a panic, not a Result; isolate only if unavoidable\nlet r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    futures::executor::block_on(conn.query(\"SELECT 1\"))\n}));","preventionTips":["Never let `?` appear between begin() and the end of the transaction scope","Wrap transaction bodies in a helper that always commits or rolls back","Treat DropBehavior::Panic as a test-only tripwire"],"tags":["rust","transaction","drop-behavior","panic","async"],"backgroundTag":"transaction-leak-detected","analyzedSha":"c1e59287258d99b309e362a63f48822256e2f65f","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}