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}