Skip to main content

rusqlite/
busy.rs

1//! Busy handler (when the database is locked)
2use std::ffi::{c_int, c_void};
3use std::panic::catch_unwind;
4use std::time::Duration;
5
6use crate::ffi;
7use crate::{Connection, InnerConnection, Result};
8
9impl Connection {
10    /// Set a busy handler that sleeps for a specified amount of time when a
11    /// table is locked. The handler will sleep multiple times until at
12    /// least "ms" milliseconds of sleeping have accumulated.
13    ///
14    /// Calling this routine with an argument equal to zero turns off all busy
15    /// handlers.
16    ///
17    /// There can only be a single busy handler for a particular database
18    /// connection at any given moment. If another busy handler was defined
19    /// (using [`busy_handler`](Connection::busy_handler)) prior to calling this
20    /// routine, that other busy handler is cleared.
21    ///
22    /// Newly created connections currently have a default busy timeout of
23    /// 5000ms, but this may be subject to change.
24    pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
25        let ms: i32 = timeout
26            .as_secs()
27            .checked_mul(1000)
28            .and_then(|t| t.checked_add(timeout.subsec_millis().into()))
29            .and_then(|t| t.try_into().ok())
30            .expect("too big");
31        self.db.borrow_mut().busy_timeout(ms)
32    }
33
34    /// Register a callback to handle `SQLITE_BUSY` errors.
35    ///
36    /// If the busy callback is `None`, then `SQLITE_BUSY` is returned
37    /// immediately upon encountering the lock. The argument to the busy
38    /// handler callback is the number of times that the
39    /// busy handler has been invoked previously for the
40    /// same locking event. If the busy callback returns `false`, then no
41    /// additional attempts are made to access the
42    /// database and `SQLITE_BUSY` is returned to the
43    /// application. If the callback returns `true`, then another attempt
44    /// is made to access the database and the cycle repeats.
45    ///
46    /// There can only be a single busy handler defined for each database
47    /// connection. Setting a new busy handler clears any previously set
48    /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
49    /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
50    /// and thus clear any previously set busy handler.
51    ///
52    /// Newly created connections default to a
53    /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
54    /// of 5000ms, although this is subject to change.
55    pub fn busy_handler<F>(&mut self, callback: Option<F>) -> Result<()>
56    where
57        F: FnMut(i32) -> bool + Send + 'static,
58    {
59        unsafe extern "C" fn busy_handler_callback<F>(p_arg: *mut c_void, count: c_int) -> c_int
60        where
61            F: FnMut(i32) -> bool,
62        {
63            unsafe {
64                c_int::from(
65                    catch_unwind(|| {
66                        let handler_fn = p_arg.cast::<F>();
67                        (*handler_fn)(count)
68                    })
69                    .unwrap_or_default(),
70                )
71            }
72        }
73        let x = callback.as_ref().map(|_| busy_handler_callback::<F> as _);
74        let mut c = self.db.borrow_mut();
75        c.set_clientdata(c"sqlite3_busy_handler", callback, |db, bh| unsafe {
76            ffi::sqlite3_busy_handler(db, x, bh)
77        })?;
78        Ok(())
79    }
80}
81
82impl InnerConnection {
83    #[inline]
84    fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
85        let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
86        let res = self.decode_result(r);
87        if res.is_ok() {
88            self.set_clientdata(c"sqlite3_busy_handler", None::<c_void>, |_, _| {
89                ffi::SQLITE_OK
90            })?;
91        }
92        res
93    }
94}
95
96#[cfg(all(test, not(miri)))]
97mod test {
98    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
99    use wasm_bindgen_test::wasm_bindgen_test as test;
100
101    use crate::{Connection, ErrorCode, Result, TransactionBehavior};
102    use std::sync::atomic::{AtomicBool, Ordering};
103
104    #[cfg_attr(
105        all(target_family = "wasm", target_os = "unknown"),
106        ignore = "no filesystem on this platform"
107    )]
108    #[test]
109    fn test_default_busy() -> Result<()> {
110        let temp_dir = tempfile::tempdir().unwrap();
111        let path = temp_dir.path().join("test.db3");
112
113        let mut db1 = Connection::open(&path)?;
114        let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
115        let db2 = Connection::open(&path)?;
116        let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
117        assert_eq!(
118            r.unwrap_err().sqlite_error_code(),
119            Some(ErrorCode::DatabaseBusy)
120        );
121        tx1.rollback()
122    }
123
124    #[cfg_attr(
125        all(target_family = "wasm", target_os = "unknown"),
126        ignore = "no filesystem on this platform"
127    )]
128    #[test]
129    fn test_busy_handler() -> Result<()> {
130        static CALLED: AtomicBool = AtomicBool::new(false);
131        fn busy_handler(n: i32) -> bool {
132            if n > 2 {
133                false
134            } else {
135                CALLED.swap(true, Ordering::Relaxed)
136            }
137        }
138
139        let temp_dir = tempfile::tempdir().unwrap();
140        let path = temp_dir.path().join("busy-handler.db3");
141
142        let mut db1 = Connection::open(&path)?;
143        db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?;
144        let mut db2 = Connection::open(&path)?;
145        db2.busy_handler(Some(busy_handler))?;
146        db1.execute_batch("BEGIN EXCLUSIVE")?;
147        let err = db2.prepare("SELECT * FROM t").unwrap_err();
148        assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy));
149        assert!(CALLED.load(Ordering::Relaxed));
150        db2.busy_handler(None::<fn(i32) -> bool>)?;
151
152        db1.busy_handler(Some(busy_handler))?;
153        db1.busy_timeout(std::time::Duration::from_millis(5))?;
154        assert!(
155            unsafe { db1.get_clientdata::<std::ffi::c_void, _>(c"sqlite3_busy_handler")? }
156                .is_none()
157        );
158        Ok(())
159    }
160}