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.
This commit is contained in:
link2xt
2023-02-20 00:28:50 +00:00
parent 840497d356
commit eaa2ef5a44
2 changed files with 9 additions and 2 deletions

View File

@@ -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<G, H>(&self, callback: G) -> Result<H>
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 {