Skip to main content

core_crypto_keystore/transaction/
mod.rs

1mod conn;
2mod fetch_from_database;
3mod finalize;
4mod mls;
5#[cfg(feature = "proteus-keystore")]
6mod proteus;
7mod savepoint;
8
9use std::sync::Arc;
10
11use async_lock::{MutexGuardArc, SemaphoreGuardArc};
12use rusqlite::Connection;
13
14pub(crate) use self::{conn::TransactionConnection, mls::read_mls_entity};
15use crate::{CryptoKeystoreResult, Database, UniqueArc};
16
17const GUARD_EXPECTATION: &str = "connection guard is present for the lifetime of the transaction wrapper";
18
19/// This is a guard over an in-flight transaction.
20///
21/// In a perfect world we'd be able to use [`rusqlite::Transaction`], but that type
22/// is intentionally `!Send + !Sync`, which prevents us from being able to keep a
23/// long-lived transaction around.
24///
25/// Dropping the transaction without committing performs an implicit rollback.
26///
27/// This type is always wrapped in a [`UniqueArc`], which keeps things efficient,
28/// at the cost of prohibiting `Clone`. In case you need to share this around,
29/// there are weak references available via [`UniqueArc::downgrade`].
30/// Alternately, wrap the entire thing in an `Arc<Mutex<Option<UniqueArc<Self>>>>` or similar.
31/// Just be aware that you'll need to take the unique arc out in order to commit.
32pub struct Transaction {
33    _semaphore_guard: SemaphoreGuardArc,
34    /// The database reference is kept only to invalidate the weak pointer when this transaction
35    /// is either committed or rolled back.
36    ///
37    /// **IMPORTANT**: do not attempt to take `self.database.conn().await`. THIS WILL DEADLOCK.
38    /// Use `self.conn` instead.
39    ///
40    /// We have to hold a separate long-lived lock to the database's internal connection because
41    /// the mutex guarding it is async, but we depend on non-async `Drop` behavior.
42    database: Arc<Database>,
43    /// Guard over the actual database connection. We have to hold this for the entire lifetime
44    /// of this transaction. That's a bit selfish on one hand, because it opens the door to
45    /// potential deadlocks if we write things wrong. On the other hand it is required because
46    /// our `Drop` impl would otherwise need to asynchronously lock the database to get the connection,
47    /// and async `Drop` is not yet a thing.
48    ///
49    /// The `Option` ensures we can safely invalidate this transaction after commit and rollback.
50    /// Can't mess with the database after either of those!
51    ///
52    /// The synchronous Mutex here does a few things:
53    ///
54    /// - turns `Connection: Send + !Sync` -> `TransactionWrapper: Send + Sync`
55    /// - by using the synchronous version, the compiler ensures we don't hold a guard over an await point, which would
56    ///   deadlock everything
57    /// - ensures that no two threads race on `conn.prepare` / `prepare_cached`
58    conn: Arc<parking_lot::Mutex<Option<MutexGuardArc<Connection>>>>,
59}
60
61impl Transaction {
62    /// Instantiate a new transaction.
63    ///
64    /// Requires a semaphore guard to ensure that only one exists at a time.
65    pub(crate) async fn new(
66        semaphore_guard: SemaphoreGuardArc,
67        database: Arc<Database>,
68    ) -> CryptoKeystoreResult<UniqueArc<Self>> {
69        let conn = database.raw_conn().await;
70
71        {
72            // initialize the DB-level transaction before doing any construction work on the type-level
73            // transaction; failure here invalidates everything to follow. We don't need to worry about
74            // concurrency; we already hold the lock guard.
75            let mut stmt = conn.prepare_cached("BEGIN IMMEDIATE TRANSACTION")?;
76            stmt.execute([])?;
77        }
78
79        let transaction = UniqueArc::from(Self {
80            _semaphore_guard: semaphore_guard,
81            database,
82            conn: Arc::new(parking_lot::Mutex::new(Some(conn))),
83        });
84
85        let weak = UniqueArc::downgrade(&transaction);
86
87        {
88            let mut transaction_guard = transaction.database.transaction.lock().await;
89            // this transaction guard may be `None` if the database is new or the previous transaction
90            // was committed.
91            // it may be `Some(_)` if the previous transaction was rolled back by means of dropping the
92            // `UniqueArc<Transaction>`. either way, it's correct to simply replace it without checking the
93            // previous value.
94            *transaction_guard = Some(weak);
95        }
96
97        Ok(transaction)
98    }
99
100    /// `true` when the transaction is active.
101    ///
102    /// You'd think that given the presence of this wrapper, you could assume that a transaction
103    /// is active: instantiating one creates a transaction, and the consuming methods close it.
104    /// However, Sqlite can sometimes automatically roll back transactions without telling the
105    /// user:
106    ///
107    /// <https://sqlite.org/c3ref/get_autocommit.html>
108    ///
109    /// > The only way to find out whether SQLite automatically rolled back the transaction
110    /// > after an error is to use this function.
111    fn is_active(conn: &Connection) -> bool {
112        !conn.is_autocommit()
113    }
114}