1 ///! Busy handler (when the database is locked)
2 use std::convert::TryInto;
3 use std::mem;
4 use std::os::raw::{c_int, c_void};
5 use std::panic::catch_unwind;
6 use std::ptr;
7 use std::time::Duration;
8 
9 use crate::ffi;
10 use crate::{Connection, InnerConnection, Result};
11 
12 impl Connection {
13     /// Set a busy handler that sleeps for a specified amount of time when a
14     /// table is locked. The handler will sleep multiple times until at
15     /// least "ms" milliseconds of sleeping have accumulated.
16     ///
17     /// Calling this routine with an argument equal to zero turns off all busy
18     /// handlers.
19     ///
20     /// There can only be a single busy handler for a particular database
21     /// connection at any given moment. If another busy handler was defined
22     /// (using `busy_handler`) prior to calling this routine, that other
23     /// busy handler is cleared.
busy_timeout(&self, timeout: Duration) -> Result<()>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()` or evaluating `PRAGMA
49     /// busy_timeout=N` will change the busy handler and thus
50     /// clear any previously set busy handler.
busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()>51     pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
52         unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
53             let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
54             if let Ok(true) = catch_unwind(|| handler_fn(count)) {
55                 1
56             } else {
57                 0
58             }
59         }
60         let mut c = self.db.borrow_mut();
61         let r = match callback {
62             Some(f) => unsafe {
63                 ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void)
64             },
65             None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) },
66         };
67         c.decode_result(r)
68     }
69 }
70 
71 impl InnerConnection {
busy_timeout(&mut self, timeout: c_int) -> Result<()>72     fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
73         let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
74         self.decode_result(r)
75     }
76 }
77 
78 #[cfg(test)]
79 mod test {
80     use std::sync::atomic::{AtomicBool, Ordering};
81     use std::sync::mpsc::sync_channel;
82     use std::thread;
83     use std::time::Duration;
84 
85     use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS};
86 
87     #[test]
test_default_busy()88     fn test_default_busy() {
89         let temp_dir = tempfile::tempdir().unwrap();
90         let path = temp_dir.path().join("test.db3");
91 
92         let mut db1 = Connection::open(&path).unwrap();
93         let tx1 = db1
94             .transaction_with_behavior(TransactionBehavior::Exclusive)
95             .unwrap();
96         let db2 = Connection::open(&path).unwrap();
97         let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!());
98         match r.unwrap_err() {
99             Error::SqliteFailure(err, _) => {
100                 assert_eq!(err.code, ErrorCode::DatabaseBusy);
101             }
102             err => panic!("Unexpected error {}", err),
103         }
104         tx1.rollback().unwrap();
105     }
106 
107     #[test]
108     #[ignore] // FIXME: unstable
test_busy_timeout()109     fn test_busy_timeout() {
110         let temp_dir = tempfile::tempdir().unwrap();
111         let path = temp_dir.path().join("test.db3");
112 
113         let db2 = Connection::open(&path).unwrap();
114         db2.busy_timeout(Duration::from_secs(1)).unwrap();
115 
116         let (rx, tx) = sync_channel(0);
117         let child = thread::spawn(move || {
118             let mut db1 = Connection::open(&path).unwrap();
119             let tx1 = db1
120                 .transaction_with_behavior(TransactionBehavior::Exclusive)
121                 .unwrap();
122             rx.send(1).unwrap();
123             thread::sleep(Duration::from_millis(100));
124             tx1.rollback().unwrap();
125         });
126 
127         assert_eq!(tx.recv().unwrap(), 1);
128         let _ = db2
129             .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
130                 row.get::<_, i32>(0)
131             })
132             .expect("unexpected error");
133 
134         child.join().unwrap();
135     }
136 
137     #[test]
138     #[ignore] // FIXME: unstable
test_busy_handler()139     fn test_busy_handler() {
140         lazy_static::lazy_static! {
141             static ref CALLED: AtomicBool = AtomicBool::new(false);
142         }
143         fn busy_handler(_: i32) -> bool {
144             CALLED.store(true, Ordering::Relaxed);
145             thread::sleep(Duration::from_millis(100));
146             true
147         }
148 
149         let temp_dir = tempfile::tempdir().unwrap();
150         let path = temp_dir.path().join("test.db3");
151 
152         let db2 = Connection::open(&path).unwrap();
153         db2.busy_handler(Some(busy_handler)).unwrap();
154 
155         let (rx, tx) = sync_channel(0);
156         let child = thread::spawn(move || {
157             let mut db1 = Connection::open(&path).unwrap();
158             let tx1 = db1
159                 .transaction_with_behavior(TransactionBehavior::Exclusive)
160                 .unwrap();
161             rx.send(1).unwrap();
162             thread::sleep(Duration::from_millis(100));
163             tx1.rollback().unwrap();
164         });
165 
166         assert_eq!(tx.recv().unwrap(), 1);
167         let _ = db2
168             .query_row("PRAGMA schema_version", NO_PARAMS, |row| {
169                 row.get::<_, i32>(0)
170             })
171             .expect("unexpected error");
172         assert_eq!(CALLED.load(Ordering::Relaxed), true);
173 
174         child.join().unwrap();
175     }
176 }
177