core_crypto_keystore/transaction/finalize.rs
1//! Methods which terminate the lifecycle of a [`Transaction`]
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 `Transaction` 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 _sqlite = crate::connection::SqliteGuard::lock();
48 let mut guard = conn.lock();
49
50 // we need to ensure we only actually clear the connection (preventing rollback)
51 // after we know the operation succeeded. Otherwise if something in this block errors,
52 // the transaction doesn't get rolled back and no new transaction can ever be created
53 {
54 let conn = guard.as_ref().expect(GUARD_EXPECTATION);
55 operation(conn)?;
56 }
57 let _ = guard.take();
58
59 Ok(())
60 }
61
62 /// Persists all the operations in the database.
63 pub async fn commit(self) -> Result<(), CryptoKeystoreError> {
64 self.consume(|conn| {
65 let mut stmt = conn.prepare_cached("COMMIT TRANSACTION")?;
66 stmt.execute([])?;
67 Ok(())
68 })
69 .await
70 }
71
72 /// Roll back this transaction in the database.
73 ///
74 /// There are two differences between manual rollback and implicit:
75 ///
76 /// - manual rollback clears the database's weak transaction ref
77 /// - manual rollback permits propagation of db-level errors
78 pub async fn rollback(self) -> CryptoKeystoreResult<()> {
79 self.consume(Transaction::execute_rollback).await
80 }
81}
82
83impl Drop for Transaction {
84 fn drop(&mut self) {
85 // a dropped wrapper has rollback behavior, but we can't clear the weak arc
86 // in the database. that's fine, that's why it was weak in the first place.
87
88 // locking here is guaranteed not to wait because this is `Drop`; the strong count is 0
89 if let Some(conn) = self.conn.lock().take() {
90 #[cfg(target_os = "unknown")]
91 crate::connection::managed_connection::defer(move || {
92 if let Err(error) = Self::execute_rollback(&conn) {
93 log::warn!(err:err = error; "failed to roll back dropped SQLite transaction");
94 }
95 });
96 #[cfg(not(target_os = "unknown"))]
97 let _ = Self::execute_rollback(&conn);
98 }
99 }
100}