core_crypto_keystore/transaction/finalize.rs
1//! Methods which terminate the lifecycle of a [`TransactionWrapper`]
2
3use rusqlite::Connection;
4
5use super::{GUARD_EXPECTATION, Transaction};
6use crate::{CryptoKeystoreError, CryptoKeystoreResult, UniqueArc};
7
8impl Transaction {
9 /// Execute rollback. Abstracted here because there are two paths to this destination.
10 fn execute_rollback(conn: &Connection) -> CryptoKeystoreResult<()> {
11 if !Self::is_active(conn) {
12 return Ok(());
13 }
14
15 let mut stmt = conn.prepare_cached("ROLLBACK TRANSACTION")?;
16 // rollback doesn't tell us how many rows were changed, so ignore it
17 stmt.execute([])?;
18
19 Ok(())
20 }
21}
22
23impl UniqueArc<Transaction> {
24 /// Consume this arc-wrapper and drop the internal transaction, first performing an operation
25 /// on the connection.
26 async fn consume(
27 self,
28 operation: impl FnOnce(&Connection) -> CryptoKeystoreResult<()>,
29 ) -> CryptoKeystoreResult<()> {
30 // We're doing a little dance here, which is tricky but legal: this statement
31 // _consumes_ the `UniqueArc` which is `self`, but
32 // _borrows_ the `TransactionWrapper` contained in there. That struct impls `Drop`,
33 // which means it can't be consumably destructured like this, but because we know
34 // we have the only arc reference, we know it's going to run its destructor as soon
35 // as this function ends.
36 let Transaction {
37 ref database,
38 ref mut conn,
39 ..
40 } = UniqueArc::into_inner(self).await;
41
42 // clear the weak reference to this transaction
43 *database.transaction.lock().await = None;
44
45 // locking here is guaranteed not to block because we consumed the unique arc; there are no other
46 // live references to the data
47 let mut guard = conn.lock();
48
49 // we need to ensure we only actually clear the connection (preventing rollback)
50 // after we know the operation succeeded. Otherwise if something in this block errors,
51 // the transaction doesn't get rolled back and no new transaction can ever be created
52 {
53 let conn = guard.as_ref().expect(GUARD_EXPECTATION);
54 operation(conn)?;
55 }
56 let _ = guard.take();
57
58 Ok(())
59 }
60
61 /// Persists all the operations in the database.
62 pub async fn commit(self) -> Result<(), CryptoKeystoreError> {
63 self.consume(|conn| {
64 let mut stmt = conn.prepare_cached("COMMIT TRANSACTION")?;
65 stmt.execute([])?;
66 Ok(())
67 })
68 .await
69 }
70
71 /// Roll back this transaction in the database.
72 ///
73 /// There are two differences between manual rollback and implicit:
74 ///
75 /// - manual rollback clears the database's weak transaction ref
76 /// - manual rollback permits propagation of db-level errors
77 pub async fn rollback(self) -> CryptoKeystoreResult<()> {
78 self.consume(Transaction::execute_rollback).await
79 }
80}
81
82impl Drop for Transaction {
83 fn drop(&mut self) {
84 // a dropped wrapper has rollback behavior, but we can't clear the weak arc
85 // in the database. that's fine, that's why it was weak in the first place.
86
87 // locking here is guaranteed not to wait because this is `Drop`; the strong count is 0
88 if let Some(conn) = self.conn.lock().take() {
89 // we have to just kind of hope for the best here
90 let _ = Self::execute_rollback(&conn);
91 }
92 }
93}