From 693dc8aec1f276472f44326ea10c5960334038fc Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Thu, 12 Dec 2024 15:09:02 +0100 Subject: [PATCH] fix(event cache store): always use immediate mode when handling linked chunk updates If a linked chunk update starts with a RemoveChunk update, then the transaction may start with a SELECT query and be considered a read transaction. Soon enough, it will be upgraded into a write transaction, because of the next UPDATE/DELETE operations that happen thereafter. If there's another write transaction already happening, this may result in a SQLITE_BUSY error, according to https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions One solution is to always start the transaction in immediate mode. This may also fail with SQLITE_BUSY according to the documentation, but it's unclear whether it will happen in general, since we're using WAL mode too. Let's try it out. --- .../src/event_cache_store.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index 33baf630b41..c82aba18bd5 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -27,7 +27,7 @@ use matrix_sdk_base::{ }; use matrix_sdk_store_encryption::StoreCipher; use ruma::{MilliSecondsSinceUnixEpoch, RoomId}; -use rusqlite::{OptionalExtension, Transaction}; +use rusqlite::{OptionalExtension, Transaction, TransactionBehavior}; use tokio::fs; use tracing::{debug, trace}; @@ -380,7 +380,14 @@ impl EventCacheStore for SqliteEventCacheStore { self.acquire() .await? - .with_transaction(move |txn| -> Result<_, Self::Error> { + .interact(move |conn| -> Result<_, Self::Error> { + // Start the transaction in IMMEDIATE mode since all updates may cause writes, to + // avoid read transactions upgrading to write mode and causing SQLITE_BUSY errors. + // See also: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions + conn.set_transaction_behavior(TransactionBehavior::Immediate); + + let txn = conn.transaction()?; + for up in updates { match up { Update::NewItemsChunk { previous, new, next } => { @@ -394,7 +401,7 @@ impl EventCacheStore for SqliteEventCacheStore { ); insert_chunk( - txn, + &txn, &hashed_room_id, previous, new, @@ -418,7 +425,7 @@ impl EventCacheStore for SqliteEventCacheStore { // Insert the chunk as a gap. insert_chunk( - txn, + &txn, &hashed_room_id, previous, new, @@ -532,9 +539,11 @@ impl EventCacheStore for SqliteEventCacheStore { } } + txn.commit()?; + Ok(()) }) - .await?; + .await.unwrap()?; Ok(()) }