1use 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 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 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}