• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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`](Connection::busy_handler)) prior to calling this
23     /// routine, that other busy handler is cleared.
24     ///
25     /// Newly created connections currently have a default busy timeout of
26     /// 5000ms, but this may be subject to change.
busy_timeout(&self, timeout: Duration) -> Result<()>27     pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
28         let ms: i32 = timeout
29             .as_secs()
30             .checked_mul(1000)
31             .and_then(|t| t.checked_add(timeout.subsec_millis().into()))
32             .and_then(|t| t.try_into().ok())
33             .expect("too big");
34         self.db.borrow_mut().busy_timeout(ms)
35     }
36 
37     /// Register a callback to handle `SQLITE_BUSY` errors.
38     ///
39     /// If the busy callback is `None`, then `SQLITE_BUSY` is returned
40     /// immediately upon encountering the lock. The argument to the busy
41     /// handler callback is the number of times that the
42     /// busy handler has been invoked previously for the
43     /// same locking event. If the busy callback returns `false`, then no
44     /// additional attempts are made to access the
45     /// database and `SQLITE_BUSY` is returned to the
46     /// application. If the callback returns `true`, then another attempt
47     /// is made to access the database and the cycle repeats.
48     ///
49     /// There can only be a single busy handler defined for each database
50     /// connection. Setting a new busy handler clears any previously set
51     /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
52     /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
53     /// and thus clear any previously set busy handler.
54     ///
55     /// Newly created connections default to a
56     /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
57     /// of 5000ms, although this is subject to change.
busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()>58     pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
59         unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
60             let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
61             if let Ok(true) = catch_unwind(|| handler_fn(count)) {
62                 1
63             } else {
64                 0
65             }
66         }
67         let c = self.db.borrow_mut();
68         let r = match callback {
69             Some(f) => unsafe {
70                 ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void)
71             },
72             None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) },
73         };
74         c.decode_result(r)
75     }
76 }
77 
78 impl InnerConnection {
79     #[inline]
busy_timeout(&mut self, timeout: c_int) -> Result<()>80     fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
81         let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
82         self.decode_result(r)
83     }
84 }
85 
86 #[cfg(test)]
87 mod test {
88     use std::sync::atomic::{AtomicBool, Ordering};
89     use std::sync::mpsc::sync_channel;
90     use std::thread;
91     use std::time::Duration;
92 
93     use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior};
94 
95     #[test]
test_default_busy() -> Result<()>96     fn test_default_busy() -> Result<()> {
97         let temp_dir = tempfile::tempdir().unwrap();
98         let path = temp_dir.path().join("test.db3");
99 
100         let mut db1 = Connection::open(&path)?;
101         let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
102         let db2 = Connection::open(&path)?;
103         let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
104         match r.unwrap_err() {
105             Error::SqliteFailure(err, _) => {
106                 assert_eq!(err.code, ErrorCode::DatabaseBusy);
107             }
108             err => panic!("Unexpected error {}", err),
109         }
110         tx1.rollback()
111     }
112 
113     #[test]
114     #[ignore] // FIXME: unstable
test_busy_timeout()115     fn test_busy_timeout() {
116         let temp_dir = tempfile::tempdir().unwrap();
117         let path = temp_dir.path().join("test.db3");
118 
119         let db2 = Connection::open(&path).unwrap();
120         db2.busy_timeout(Duration::from_secs(1)).unwrap();
121 
122         let (rx, tx) = sync_channel(0);
123         let child = thread::spawn(move || {
124             let mut db1 = Connection::open(&path).unwrap();
125             let tx1 = db1
126                 .transaction_with_behavior(TransactionBehavior::Exclusive)
127                 .unwrap();
128             rx.send(1).unwrap();
129             thread::sleep(Duration::from_millis(100));
130             tx1.rollback().unwrap();
131         });
132 
133         assert_eq!(tx.recv().unwrap(), 1);
134         let _ = db2
135             .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
136             .expect("unexpected error");
137 
138         child.join().unwrap();
139     }
140 
141     #[test]
142     #[ignore] // FIXME: unstable
test_busy_handler()143     fn test_busy_handler() {
144         static CALLED: AtomicBool = AtomicBool::new(false);
145         fn busy_handler(_: i32) -> bool {
146             CALLED.store(true, Ordering::Relaxed);
147             thread::sleep(Duration::from_millis(100));
148             true
149         }
150 
151         let temp_dir = tempfile::tempdir().unwrap();
152         let path = temp_dir.path().join("test.db3");
153 
154         let db2 = Connection::open(&path).unwrap();
155         db2.busy_handler(Some(busy_handler)).unwrap();
156 
157         let (rx, tx) = sync_channel(0);
158         let child = thread::spawn(move || {
159             let mut db1 = Connection::open(&path).unwrap();
160             let tx1 = db1
161                 .transaction_with_behavior(TransactionBehavior::Exclusive)
162                 .unwrap();
163             rx.send(1).unwrap();
164             thread::sleep(Duration::from_millis(100));
165             tx1.rollback().unwrap();
166         });
167 
168         assert_eq!(tx.recv().unwrap(), 1);
169         let _ = db2
170             .query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
171             .expect("unexpected error");
172         assert!(CALLED.load(Ordering::Relaxed));
173 
174         child.join().unwrap();
175     }
176 }
177