• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 17807091342

17 Sep 2025 06:28PM UTC coverage: 60.975% (+1.1%) from 59.9%
17807091342

push

github

web-flow
fix: always cancel transactions (#7500)

Description
---
Cancel tx and outputs. Don't assume they exist or not. Sending 1-sided
offline transactions doesn't have an existing transaction to cancel

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability of canceling pending transactions: missing or
already-processed pending records and unlock failures are now logged as
warnings and do not abort cancellation.
* Reduces spurious errors during cancellation while preserving normal
cleanup, signals, and event emission for more stable wallet behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

0 of 12 new or added lines in 1 file covered. (0.0%)

493 existing lines in 17 files now uncovered.

74571 of 122298 relevant lines covered (60.97%)

223264.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.43
/common_sqlite/src/connection_options.rs
1
// Copyright 2020. The Tari Project
2
//
3
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
// following conditions are met:
5
//
6
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
// disclaimer.
8
//
9
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
// following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
// products derived from this software without specific prior written permission.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22

23
use core::time::Duration;
24

25
use diesel::{connection::SimpleConnection, dsl::sql, sql_types::Text, RunQueryDsl, SqliteConnection};
26
use log::trace;
27

28
use crate::connection::DbConnection;
29

30
const LOG_TARGET: &str = "common_sqlite::connection_options";
31

32
/// A default busy timeout for SQLite pool connection to throw a 'database is locked' error.
33
pub const PRAGMA_BUSY_TIMEOUT: Duration = Duration::from_secs(60);
34

35
/// Connection-level options that are applied to **every** SQLite connection
36
/// as it is acquired from the r2d2 pool (via the `CustomizeConnection` hook).
37
///
38
/// # Timeout interplay
39
/// `busy_timeout` controls how long a **checked-out** connection will wait
40
/// *inside SQLite* when it encounters a lock (e.g. another writer). This is
41
/// independent of the r2d2 **pool checkout** timeout
42
/// (`Pool::builder().connection_timeout(...)`).
43
///
44
/// To avoid spurious “timed out waiting for connection” pool errors under
45
/// write contention, prefer `connection_timeout >= busy_timeout + small buffer`,
46
/// or keep `busy_timeout` lower and implement retries with backoff.
47
#[derive(Debug, Clone)]
48
pub struct ConnectionOptions {
49
    /// If `true`, enables Write-Ahead Logging and sets
50
    /// `PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;`
51
    /// for a good durability/throughput trade-off.
52
    enable_wal: bool,
53
    /// If `true`, enforces SQLite foreign key constraints
54
    /// (`PRAGMA foreign_keys = ON;`).
55
    enable_foreign_keys: bool,
56
    /// How long SQLite should wait for locks before returning `SQLITE_BUSY`.
57
    /// When `None`, no busy timeout is configured.
58
    busy_timeout: Option<Duration>,
59
}
60

61
impl ConnectionOptions {
62
    /// Construct a new set of connection options.
63
    ///
64
    /// * `enable_wal` — enable WAL + `synchronous=NORMAL`
65
    /// * `enable_foreign_keys` — enforce FK constraints
66
    /// * `busy_timeout` — SQLite lock wait duration
67
    pub fn new(enable_wal: bool, enable_foreign_keys: bool, busy_timeout: Duration) -> Self {
340✔
68
        Self {
340✔
69
            enable_wal,
340✔
70
            enable_foreign_keys,
340✔
71
            busy_timeout: Some(busy_timeout),
340✔
72
        }
340✔
73
    }
340✔
74

75
    /// Returns the busy timeout duration, if set.
76
    pub fn get_busy_timeout(&self) -> Option<Duration> {
340✔
77
        self.busy_timeout
340✔
78
    }
340✔
79
}
80

81
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error> for ConnectionOptions {
82
    /// Applies PRAGMAs on each acquired connection:
83
    /// - `PRAGMA busy_timeout`
84
    /// - `PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;` (if enabled)
85
    /// - `PRAGMA foreign_keys = ON;` (if enabled)
86
    fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
3,311✔
87
        (|| {
3,311✔
88
            let start = std::time::Instant::now();
3,311✔
89
            if let Some(d) = self.busy_timeout {
3,311✔
90
                conn.batch_execute(&format!("PRAGMA busy_timeout = {};", d.as_millis()))?;
3,311✔
UNCOV
91
            }
×
92

93
            if self.enable_wal {
3,311✔
94
                // Read current mode (cheap, read-only)
95
                let current: String = sql::<Text>("PRAGMA journal_mode;").get_result(conn)?;
3,311✔
96

97
                if current.eq_ignore_ascii_case("wal") {
3,311✔
98
                    // Already WAL: just ensure the sync level
99
                    conn.batch_execute("PRAGMA synchronous = NORMAL;")?;
1,474✔
100
                } else if DbConnection::migration_lock_active() {
1,837✔
101
                    // Only attempt the WAL flip when we know we hold the process-wide write lock.
102
                    // This avoids racy WAL changes from concurrent connection acquisitions.
103
                    conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?;
1,177✔
104
                } else {
660✔
105
                    // Not WAL and no migration lock: do nothing (avoid SQLITE_BUSY on startup).
660✔
106
                    // Next time on_acquire runs under the lock (during migrate), it will flip.
660✔
107
                }
660✔
UNCOV
108
            }
×
109

110
            if self.enable_foreign_keys {
3,215✔
111
                conn.batch_execute("PRAGMA foreign_keys = ON;")?;
3,215✔
UNCOV
112
            }
×
113

114
            trace!(target: LOG_TARGET, "Applied SQLite PRAGMAs on_acquire in {:.2?}", start.elapsed());
3,215✔
115
            Ok(())
3,215✔
116
        })()
3,311✔
117
        .map_err(diesel::r2d2::Error::QueryError)
3,311✔
118
    }
3,311✔
119
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc