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