tursodatabase/turso · error
Transaction dropped unexpectedly.
Error message
Transaction dropped unexpectedly.
What it means
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.
Source
Thrown at bindings/rust/src/connection.rs:117
}
pub(crate) async fn maybe_handle_dangling_tx(&self) -> Result<()> {
match self.dangling_tx.load(Ordering::SeqCst) {
DropBehavior::Rollback => {
let mut stmt = self.prepare("ROLLBACK").await?;
stmt.execute(()).await?;
self.dangling_tx
.store(DropBehavior::Ignore, Ordering::SeqCst);
}
DropBehavior::Commit => {
let mut stmt = self.prepare("COMMIT").await?;
stmt.execute(()).await?;
self.dangling_tx
.store(DropBehavior::Ignore, Ordering::SeqCst);
}
DropBehavior::Ignore => {}
DropBehavior::Panic => {
panic!("Transaction dropped unexpectedly.");
}
}
Ok(())
}
/// Query the database with SQL.
pub async fn query(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<Rows> {
self.maybe_handle_dangling_tx().await?;
let mut stmt = self.prepare(sql).await?;
stmt.query(params).await
}
/// Execute SQL statement on the database.
pub async fn execute(&self, sql: impl AsRef<str>, params: impl IntoParams) -> Result<u64> {
self.maybe_handle_dangling_tx().await?;
let mut stmt = self.prepare(sql).await?;
stmt.execute(params).await
}View on GitHub (pinned to c1e5928725)
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
Example fix
// before
let tx = conn.begin().await?;
let v = do_work(&tx).await?; // early return drops tx
// ... later conn.query(..) panics: "Transaction dropped unexpectedly."
// after
let tx = conn.begin().await?;
match do_work(&tx).await {
Ok(v) => { tx.commit().await?; Ok(v) }
Err(e) => { tx.rollback().await?; Err(e) }
} Defensive patterns
Strategy: validation
Validate before calling
// guarantee an explicit outcome before the transaction can drop
pub async fn with_tx<T>(conn: &Connection, f: impl FnOnce(&Transaction<'_>) -> BoxFuture<'_, Result<T>>) -> Result<T> {
let tx = conn.begin().await?;
match f(&tx).await {
Ok(v) => { tx.commit().await?; Ok(v) }
Err(e) => { let _ = tx.rollback().await; Err(e) }
}
} Try / catch
// it is a panic, not a Result; isolate only if unavoidable
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
futures::executor::block_on(conn.query("SELECT 1"))
})); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Transaction dropped unexpectedly.
- failed to generate random bytes
- The transaction has already completed
- Invalid drop behavior: {value}
- Could not determine home directory
AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-20).
Data as JSON: /api/errors/40807fac1761a496.
Report an issue: GitHub.