From eaa2ef5a44068da89d1f3b6a17f9584ef2cfa91e Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 20 Feb 2023 00:28:50 +0000 Subject: [PATCH] sql: start transactions as IMMEDIATE With the introduction of transactions in Contact::add_or_lookup(), python tests sometimes fail to create contacts with the folowing error: Cannot create contact: add_or_lookup: database is locked: Error code 5: The database file is locked `PRAGMA busy_timeout=60000` does not affect this case as the error is returned before 60 seconds pass. DEFERRED transactions with write operations need to be retried from scratch if they are rolled back due to a write operation on another connection. Using IMMEDIATE transactions for writing is an attempt to fix this problem without a retry loop. If we later need DEFERRED transactions, e.g. for reading a snapshot without locking the database, we may introduce another function for this. --- CHANGELOG.md | 1 + src/sql.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 098244902..1dfa0c98d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - use transaction in `Contact::add_or_lookup()` #4059 ### Fixes +- Start SQL transactions with IMMEDIATE behaviour rather than default DEFERRED one. #4063 ### API-Changes diff --git a/src/sql.rs b/src/sql.rs index 6d6a53fec..4b4b190eb 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -6,7 +6,7 @@ use std::path::Path; use std::path::PathBuf; use anyhow::{bail, Context as _, Result}; -use rusqlite::{self, config::DbConfig, Connection, OpenFlags}; +use rusqlite::{self, config::DbConfig, Connection, OpenFlags, TransactionBehavior}; use tokio::sync::RwLock; use crate::blob::BlobObject; @@ -377,6 +377,12 @@ impl Sql { /// /// If the function returns an error, the transaction will be rolled back. If it does not return an /// error, the transaction will be committed. + /// + /// Transactions started use IMMEDIATE behavior + /// rather than default DEFERRED behavior + /// to avoid "database is busy" errors + /// which may happen when DEFERRED transaction + /// is attempted to be promoted to a write transaction. pub async fn transaction(&self, callback: G) -> Result where H: Send + 'static, @@ -384,7 +390,7 @@ impl Sql { { let mut conn = self.get_conn().await?; tokio::task::block_in_place(move || { - let mut transaction = conn.transaction()?; + let mut transaction = conn.transaction_with_behavior(TransactionBehavior::Immediate)?; let ret = callback(&mut transaction); match ret {