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;
12use rusqlite::Connection;
13
14pub(crate) use self::{conn::TransactionConnection, mls::read_mls_entity};
15use crate::{CryptoKeystoreResult, Database, UniqueArc, connection::TransactionGuard};
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 /// The lock guard is never used directly, but is held to ensure that only one transaction
34 /// is ever live simultaneously. Sqlite already gives us that protection, but we can `.await`
35 /// the transaction guard instead of just catching the error if we try to double-up.
36 _lock_guard: TransactionGuard,
37 /// The database reference is kept only to invalidate the weak pointer when this transaction
38 /// is either committed or rolled back.
39 ///
40 /// **IMPORTANT**: do not attempt to take `self.database.conn().await`. THIS WILL DEADLOCK.
41 /// Use `self.conn` instead.
42 ///
43 /// We have to hold a separate long-lived lock to the database's internal connection because
44 /// the mutex guarding it is async, but we depend on non-async `Drop` behavior.
45 database: Arc<Database>,
46 /// Guard over the actual database connection. We have to hold this for the entire lifetime
47 /// of this transaction. That's a bit selfish on one hand, because it opens the door to
48 /// potential deadlocks if we write things wrong. On the other hand it is required because
49 /// our `Drop` impl would otherwise need to asynchronously lock the database to get the connection,
50 /// and async `Drop` is not yet a thing.
51 ///
52 /// The `Option` ensures we can safely invalidate this transaction after commit and rollback.
53 /// Can't mess with the database after either of those!
54 ///
55 /// The synchronous Mutex here does a few things:
56 ///
57 /// - turns `Connection: Send + !Sync` -> `TransactionWrapper: Send + Sync`
58 /// - by using the synchronous version, the compiler ensures we don't hold a guard over an await point, which would
59 /// deadlock everything
60 /// - ensures that no two threads race on `conn.prepare` / `prepare_cached`
61 conn: Arc<parking_lot::Mutex<Option<MutexGuardArc<Connection>>>>,
62}
63
64impl Transaction {
65 /// Instantiate a new transaction.
66 ///
67 /// Requires a transaction lock guard to ensure that only one exists at a time.
68 pub(crate) async fn new(
69 lock_guard: TransactionGuard,
70 database: Arc<Database>,
71 ) -> CryptoKeystoreResult<UniqueArc<Self>> {
72 let conn = database.raw_conn().await;
73
74 {
75 // initialize the DB-level transaction before doing any construction work on the type-level
76 // transaction; failure here invalidates everything to follow. We don't need to worry about
77 // concurrency; we already hold the lock guard.
78 let mut stmt = conn.prepare_cached("BEGIN IMMEDIATE TRANSACTION")?;
79 stmt.execute([])?;
80 }
81
82 let transaction = UniqueArc::from(Self {
83 _lock_guard: lock_guard,
84 database,
85 conn: Arc::new(parking_lot::Mutex::new(Some(conn))),
86 });
87
88 let weak = UniqueArc::downgrade(&transaction);
89
90 {
91 let mut transaction_guard = transaction.database.transaction.lock().await;
92 // this transaction guard may be `None` if the database is new or the previous transaction
93 // was committed.
94 // it may be `Some(_)` if the previous transaction was rolled back by means of dropping the
95 // `UniqueArc<Transaction>`. either way, it's correct to simply replace it without checking the
96 // previous value.
97 *transaction_guard = Some(weak);
98 }
99
100 Ok(transaction)
101 }
102
103 /// `true` when the transaction is active.
104 ///
105 /// You'd think that given the presence of this wrapper, you could assume that a transaction
106 /// is active: instantiating one creates a transaction, and the consuming methods close it.
107 /// However, Sqlite can sometimes automatically roll back transactions without telling the
108 /// user:
109 ///
110 /// <https://sqlite.org/c3ref/get_autocommit.html>
111 ///
112 /// > The only way to find out whether SQLite automatically rolled back the transaction
113 /// > after an error is to use this function.
114 fn is_active(conn: &Connection) -> bool {
115 !conn.is_autocommit()
116 }
117}