Skip to main content

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}