From 5d91fb088f13b8728d0012fb57d28040a4568a17 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 20 Sep 2017 21:28:19 +0200 Subject: [PATCH 01/10] Unlock notification --- Cargo.toml | 1 + libsqlite3-sys/Cargo.toml | 2 + libsqlite3-sys/build.rs | 11 ++++-- src/lib.rs | 2 + src/unlock_notify.rs | 80 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/unlock_notify.rs diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..dfea145 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] limits = [] hooks = [] sqlcipher = ["libsqlite3-sys/sqlcipher"] +unlock_notify = ["libsqlite3-sys/unlock_notify"] [dependencies] time = "0.1.0" diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..d038713 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -21,6 +21,8 @@ min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_3 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] +# sqlite3_unlock_notify >= 3.6.12 +unlock_notify = [] [build-dependencies] bindgen = { version = "0.32", optional = true } diff --git a/libsqlite3-sys/build.rs b/libsqlite3-sys/build.rs index 0757d9f..955f565 100644 --- a/libsqlite3-sys/build.rs +++ b/libsqlite3-sys/build.rs @@ -18,8 +18,8 @@ mod build { fs::copy("sqlite3/bindgen_bundled_version.rs", out_path) .expect("Could not copy bindings to output directory"); - cc::Build::new() - .file("sqlite3/sqlite3.c") + let mut cfg = cc::Build::new(); + cfg.file("sqlite3/sqlite3.c") .flag("-DSQLITE_CORE") .flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1") .flag("-DSQLITE_ENABLE_API_ARMOR") @@ -38,8 +38,11 @@ mod build { .flag("-DSQLITE_SOUNDEX") .flag("-DSQLITE_THREADSAFE=1") .flag("-DSQLITE_USE_URI") - .flag("-DHAVE_USLEEP=1") - .compile("libsqlite3.a"); + .flag("-DHAVE_USLEEP=1"); + if cfg!(feature = "unlock_notify") { + cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY"); + } + cfg.compile("libsqlite3.a"); } } diff --git a/src/lib.rs b/src/lib.rs index 0ccd22f..238ed09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,8 @@ pub mod limits; mod hooks; #[cfg(feature = "hooks")] pub use hooks::*; +#[cfg(feature = "unlock_notify")] +pub mod unlock_notify; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs new file mode 100644 index 0000000..ac0a2d3 --- /dev/null +++ b/src/unlock_notify.rs @@ -0,0 +1,80 @@ +//! [Unlock Notification](http://sqlite.org/unlock_notify.html) + +use std::sync::{Mutex, Condvar}; +use std::os::raw::{c_char, c_int, c_void}; + +use ffi; +use InnerConnection; + +struct UnlockNotification { + cond: Condvar, // Condition variable to wait on + mutex: Mutex, // Mutex to protect structure +} + +impl UnlockNotification { + fn new() -> UnlockNotification { + UnlockNotification { + cond: Condvar::new(), + mutex: Mutex::new(false), + } + } + + fn fired(&mut self) { + *self.mutex.lock().unwrap() = true; + self.cond.notify_one(); + } + + fn wait(&mut self) -> bool { + let mut fired = self.mutex.lock().unwrap(); + if !*fired { + fired = self.cond.wait(fired).unwrap(); + } + *fired + } +} + +/// This function is an unlock-notify callback +unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { + /*int i; + for(i=0; imutex); + p->fired = 1; + pthread_cond_signal(&p->cond); + pthread_mutex_unlock(&p->mutex); + }*/ +} + +impl InnerConnection { + fn blocking_prepare(&mut self, + z_sql: *const c_char, + n_byte: c_int, + pp_stmt: *mut *mut ffi::sqlite3_stmt, + pz_tail: *mut *const c_char) -> c_int { + let mut rc; + loop { + rc = unsafe { + ffi::sqlite3_prepare_v2(self.db, z_sql, n_byte, pp_stmt, pz_tail) + }; + if rc != ffi::SQLITE_LOCKED { + break; + } + rc = self.wait_for_unlock_notify(); + if rc != ffi::SQLITE_OK { + break; + } + } + rc + } + + fn wait_for_unlock_notify(&mut self) -> c_int { + let mut un = UnlockNotification::new(); + /* Register for an unlock-notify callback. */ + let rc = unsafe { ffi::sqlite3_unlock_notify(self.db, Some(unlock_notify_cb), &mut un as *mut UnlockNotification as *mut c_void) }; + debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); + if rc == ffi::SQLITE_OK { + un.wait(); + } + rc + } +} \ No newline at end of file From 455e7d40605b6f5ef879dee1f8d226965c636b29 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Sep 2017 19:27:07 +0200 Subject: [PATCH 02/10] WIP: Unlock Notification To do: unlock_notify_cb --- src/lib.rs | 43 ++++++++++++++++++------ src/raw_statement.rs | 31 +++++++++++++++--- src/statement.rs | 4 +-- src/unlock_notify.rs | 78 ++++++++++++++++++++++++-------------------- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 238ed09..8a2df1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,8 +125,7 @@ pub mod limits; mod hooks; #[cfg(feature = "hooks")] pub use hooks::*; -#[cfg(feature = "unlock_notify")] -pub mod unlock_notify; +mod unlock_notify; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; @@ -864,16 +863,40 @@ impl InnerConnection { } let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let c_sql = try!(str_to_cstring(sql)); + let len_with_nul = (sql.len() + 1) as c_int; let r = unsafe { - let len_with_nul = (sql.len() + 1) as c_int; - ffi::sqlite3_prepare_v2(self.db(), - c_sql.as_ptr(), - len_with_nul, - &mut c_stmt, - ptr::null_mut()) + if cfg!(feature = "unlock_notify") { + let mut rc; + loop { + rc = ffi::sqlite3_prepare_v2( + self.db(), + c_sql.as_ptr(), + len_with_nul, + &mut c_stmt, + ptr::null_mut(), + ); + if rc != ffi::SQLITE_LOCKED { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.db); + if rc != ffi::SQLITE_OK { + break; + } + } + rc + } else { + ffi::sqlite3_prepare_v2( + self.db(), + c_sql.as_ptr(), + len_with_nul, + &mut c_stmt, + ptr::null_mut(), + ) + } }; - self.decode_result(r) - .map(|_| Statement::new(conn, RawStatement::new(c_stmt))) + self.decode_result(r).map(|_| { + Statement::new(conn, RawStatement::new(c_stmt, self.db())) + }) } fn changes(&mut self) -> c_int { diff --git a/src/raw_statement.rs b/src/raw_statement.rs index d25c09f..b92e122 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -2,14 +2,15 @@ use std::ffi::CStr; use std::ptr; use std::os::raw::c_int; use super::ffi; +use super::unlock_notify; // Private newtype for raw sqlite3_stmts that finalize themselves when dropped. #[derive(Debug)] -pub struct RawStatement(*mut ffi::sqlite3_stmt); +pub struct RawStatement(*mut ffi::sqlite3_stmt, *mut ffi::sqlite3); impl RawStatement { - pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement { - RawStatement(stmt) + pub fn new(stmt: *mut ffi::sqlite3_stmt, db: *mut ffi::sqlite3) -> RawStatement { + RawStatement(stmt, db) } pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { @@ -29,7 +30,29 @@ impl RawStatement { } pub fn step(&self) -> c_int { - unsafe { ffi::sqlite3_step(self.0) } + if cfg!(feature = "unlock_notify") { + let mut rc; + loop { + rc = unsafe { ffi::sqlite3_step(self.0) }; + if rc == ffi::SQLITE_LOCKED { + if unsafe { ffi::sqlite3_extended_errcode(self.1) } != + ffi::SQLITE_LOCKED_SHAREDCACHE + { + break; + } + } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.1); + if rc != ffi::SQLITE_OK { + break; + } + self.reset(); + } + rc + } else { + unsafe { ffi::sqlite3_step(self.0) } + } } pub fn reset(&self) -> c_int { diff --git a/src/statement.rs b/src/statement.rs index 2674c32..2c5227b 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -462,7 +462,7 @@ impl<'conn> Statement<'conn> { } fn finalize_(&mut self) -> Result<()> { - let mut stmt = RawStatement::new(ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); self.conn.decode_result(stmt.finalize()) } @@ -470,7 +470,7 @@ impl<'conn> Statement<'conn> { impl<'conn> Into for Statement<'conn> { fn into(mut self) -> RawStatement { - let mut stmt = RawStatement::new(ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); stmt } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index ac0a2d3..4ef4dd5 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -1,16 +1,20 @@ //! [Unlock Notification](http://sqlite.org/unlock_notify.html) -use std::sync::{Mutex, Condvar}; -use std::os::raw::{c_char, c_int, c_void}; +#[cfg(feature = "unlock_notify")] +use std::sync::{Condvar, Mutex}; +use std::os::raw::c_int; +#[cfg(feature = "unlock_notify")] +use std::os::raw::c_void; use ffi; -use InnerConnection; +#[cfg(feature = "unlock_notify")] struct UnlockNotification { - cond: Condvar, // Condition variable to wait on + cond: Condvar, // Condition variable to wait on mutex: Mutex, // Mutex to protect structure } +#[cfg(feature = "unlock_notify")] impl UnlockNotification { fn new() -> UnlockNotification { UnlockNotification { @@ -34,8 +38,9 @@ impl UnlockNotification { } /// This function is an unlock-notify callback +#[cfg(feature = "unlock_notify")] unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { - /*int i; + /*int i; for(i=0; imutex); @@ -45,36 +50,37 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { }*/ } -impl InnerConnection { - fn blocking_prepare(&mut self, - z_sql: *const c_char, - n_byte: c_int, - pp_stmt: *mut *mut ffi::sqlite3_stmt, - pz_tail: *mut *const c_char) -> c_int { - let mut rc; - loop { - rc = unsafe { - ffi::sqlite3_prepare_v2(self.db, z_sql, n_byte, pp_stmt, pz_tail) - }; - if rc != ffi::SQLITE_LOCKED { - break; - } - rc = self.wait_for_unlock_notify(); - if rc != ffi::SQLITE_OK { - break; - } - } - rc +/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` +/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the +/// associated database connection. +/// +/// This function calls `sqlite3_unlock_notify()` to register for an +/// unlock-notify callback, then blocks until that callback is delivered +/// and returns `SQLITE_OK`. The caller should then retry the failed operation. +/// +/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock +/// the system, then this function returns `SQLITE_LOCKED` immediately. In +/// this case the caller should not retry the operation and should roll +/// back the current transaction (if any). +#[cfg(feature = "unlock_notify")] +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { + let mut un = UnlockNotification::new(); + /* Register for an unlock-notify callback. */ + let rc = unsafe { + ffi::sqlite3_unlock_notify( + db, + Some(unlock_notify_cb), + &mut un as *mut UnlockNotification as *mut c_void, + ) + }; + debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); + if rc == ffi::SQLITE_OK { + un.wait(); } + rc +} - fn wait_for_unlock_notify(&mut self) -> c_int { - let mut un = UnlockNotification::new(); - /* Register for an unlock-notify callback. */ - let rc = unsafe { ffi::sqlite3_unlock_notify(self.db, Some(unlock_notify_cb), &mut un as *mut UnlockNotification as *mut c_void) }; - debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); - if rc == ffi::SQLITE_OK { - un.wait(); - } - rc - } -} \ No newline at end of file +#[cfg(not(feature = "unlock_notify"))] +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { + unreachable!() +} From 154a70d41b5aa324941e404cc6c712cccd6fd148 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Sep 2017 20:19:23 +0200 Subject: [PATCH 03/10] WIP: Unlock Notification To be tested --- src/unlock_notify.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 4ef4dd5..9676dd3 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -40,14 +40,12 @@ impl UnlockNotification { /// This function is an unlock-notify callback #[cfg(feature = "unlock_notify")] unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { - /*int i; - for(i=0; imutex); - p->fired = 1; - pthread_cond_signal(&p->cond); - pthread_mutex_unlock(&p->mutex); - }*/ + use std::slice::from_raw_parts; + let args = from_raw_parts(ap_arg, n_arg as usize); + for arg in args { + let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification); + un.fired(); + } } /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` From 5fd76aa54b0a4cf19b0964cb2aae6e1f4c0c460d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 22 Sep 2017 23:27:05 +0200 Subject: [PATCH 04/10] Unlock notification Test added --- src/raw_statement.rs | 4 ++-- src/unlock_notify.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/raw_statement.rs b/src/raw_statement.rs index b92e122..08d6873 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -35,8 +35,8 @@ impl RawStatement { loop { rc = unsafe { ffi::sqlite3_step(self.0) }; if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } != - ffi::SQLITE_LOCKED_SHAREDCACHE + if unsafe { ffi::sqlite3_extended_errcode(self.1) } + != ffi::SQLITE_LOCKED_SHAREDCACHE { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 9676dd3..724358f 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -82,3 +82,36 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { unreachable!() } + +#[cfg(feature = "unlock_notify")] +#[cfg(test)] +mod test { + use std::sync::mpsc::sync_channel; + use std::thread; + use std::time; + use {Connection, Result, Transaction, TransactionBehavior, SQLITE_OPEN_READ_WRITE, + SQLITE_OPEN_URI}; + + #[test] + fn test_unlock_notify() { + let url = "file::memory:?cache=shared"; + let flags = SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_URI; + let db1 = Connection::open_with_flags(url, flags).unwrap(); + db1.execute_batch("CREATE TABLE foo (x)").unwrap(); + let (sender, receiver) = sync_channel(0); + let sender2 = sender.clone(); + let child = thread::spawn(move || { + let mut db2 = Connection::open_with_flags(url, flags).unwrap(); + let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap(); + tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap(); + sender2.send(1).unwrap(); + let ten_millis = time::Duration::from_millis(10); + thread::sleep(ten_millis); + tx2.commit().unwrap(); + }); + assert_eq!(receiver.recv().unwrap(), 1); + let the_answer: Result = db1.query_row("SELECT x FROM foo", &[], |r| r.get(0)); + assert_eq!(42i64, the_answer.unwrap()); + child.join().unwrap(); + } +} From c612a44207ba6eeb6dd6051b0d6640b0e6649838 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Mar 2018 10:15:10 +0100 Subject: [PATCH 05/10] Fix errors relative to OpenFlags Also handle extended error codes. --- .travis.yml | 1 + src/lib.rs | 2 +- src/unlock_notify.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index de9a910..d3aa772 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ script: - cargo test --features serde_json - cargo test --features bundled - cargo test --features sqlcipher + - cargo test --features "unlock_notify bundled" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" diff --git a/src/lib.rs b/src/lib.rs index 8a2df1c..d9389ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,7 +875,7 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - if rc != ffi::SQLITE_LOCKED { + if (rc & 0xFF) != ffi::SQLITE_LOCKED { break; } rc = unlock_notify::wait_for_unlock_notify(self.db); diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 724358f..5aabfd2 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -71,7 +71,9 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { &mut un as *mut UnlockNotification as *mut c_void, ) }; - debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); + debug_assert!(rc == ffi::SQLITE_LOCKED || + rc == ffi::SQLITE_LOCKED_SHAREDCACHE || + rc == ffi::SQLITE_OK); if rc == ffi::SQLITE_OK { un.wait(); } @@ -89,27 +91,25 @@ mod test { use std::sync::mpsc::sync_channel; use std::thread; use std::time; - use {Connection, Result, Transaction, TransactionBehavior, SQLITE_OPEN_READ_WRITE, - SQLITE_OPEN_URI}; + use {Connection, OpenFlags, Result, Transaction, TransactionBehavior}; #[test] fn test_unlock_notify() { let url = "file::memory:?cache=shared"; - let flags = SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_URI; + let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI; let db1 = Connection::open_with_flags(url, flags).unwrap(); db1.execute_batch("CREATE TABLE foo (x)").unwrap(); - let (sender, receiver) = sync_channel(0); - let sender2 = sender.clone(); + let (rx, tx) = sync_channel(0); let child = thread::spawn(move || { let mut db2 = Connection::open_with_flags(url, flags).unwrap(); let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap(); tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap(); - sender2.send(1).unwrap(); + rx.send(1).unwrap(); let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); tx2.commit().unwrap(); }); - assert_eq!(receiver.recv().unwrap(), 1); + assert_eq!(tx.recv().unwrap(), 1); let the_answer: Result = db1.query_row("SELECT x FROM foo", &[], |r| r.get(0)); assert_eq!(42i64, the_answer.unwrap()); child.join().unwrap(); From cccdf9735f797f0c71901e8591a26d033436fb20 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 21:49:09 +0200 Subject: [PATCH 06/10] Factorize check on code returned by prepare/step. --- src/lib.rs | 5 +---- src/raw_statement.rs | 11 +---------- src/unlock_notify.rs | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9389ab..97f28f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,10 +875,7 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - if (rc & 0xFF) != ffi::SQLITE_LOCKED { - break; - } - rc = unlock_notify::wait_for_unlock_notify(self.db); + rc = unlock_notify::wait_for_unlock_notify(self.db, rc); if rc != ffi::SQLITE_OK { break; } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 08d6873..a09725d 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -34,16 +34,7 @@ impl RawStatement { let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } - != ffi::SQLITE_LOCKED_SHAREDCACHE - { - break; - } - } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { - break; - } - rc = unlock_notify::wait_for_unlock_notify(self.1); + rc = unlock_notify::wait_for_unlock_notify(self.1, rc); if rc != ffi::SQLITE_OK { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 5aabfd2..2743dd3 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -61,7 +61,14 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { /// this case the caller should not retry the operation and should roll /// back the current transaction (if any). #[cfg(feature = "unlock_notify")] -pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { + if rc == ffi::SQLITE_LOCKED { + if unsafe { ffi::sqlite3_extended_errcode(self.1) } != ffi::SQLITE_LOCKED_SHAREDCACHE { + return rc; + } + } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { + return rc; + } let mut un = UnlockNotification::new(); /* Register for an unlock-notify callback. */ let rc = unsafe { @@ -71,9 +78,9 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { &mut un as *mut UnlockNotification as *mut c_void, ) }; - debug_assert!(rc == ffi::SQLITE_LOCKED || - rc == ffi::SQLITE_LOCKED_SHAREDCACHE || - rc == ffi::SQLITE_OK); + debug_assert!( + rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK + ); if rc == ffi::SQLITE_OK { un.wait(); } @@ -81,7 +88,7 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { } #[cfg(not(feature = "unlock_notify"))] -pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3, _code: c_int) -> c_int { unreachable!() } From a0151f907320d99a1f74cfb5da4384c0ca48788d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 31 Mar 2018 10:22:19 +0200 Subject: [PATCH 07/10] Introduce is_locked --- src/lib.rs | 5 ++++- src/raw_statement.rs | 5 ++++- src/unlock_notify.rs | 24 +++++++++++++++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97f28f5..0a30078 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,7 +875,10 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - rc = unlock_notify::wait_for_unlock_notify(self.db, rc); + if !unlock_notify::is_locked(self.db, rc) { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.db); if rc != ffi::SQLITE_OK { break; } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index a09725d..9b756da 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -34,7 +34,10 @@ impl RawStatement { let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - rc = unlock_notify::wait_for_unlock_notify(self.1, rc); + if !unlock_notify::is_locked(self.1, rc) { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.1); if rc != ffi::SQLITE_OK { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 2743dd3..985958b 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -48,6 +48,14 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { } } +#[cfg(feature = "unlock_notify")] +pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { + return rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { + ffi::sqlite3_extended_errcode(db) + } + == ffi::SQLITE_LOCKED_SHAREDCACHE; +} + /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` /// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the /// associated database connection. @@ -61,14 +69,7 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { /// this case the caller should not retry the operation and should roll /// back the current transaction (if any). #[cfg(feature = "unlock_notify")] -pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { - if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } != ffi::SQLITE_LOCKED_SHAREDCACHE { - return rc; - } - } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { - return rc; - } +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { let mut un = UnlockNotification::new(); /* Register for an unlock-notify callback. */ let rc = unsafe { @@ -88,7 +89,12 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { } #[cfg(not(feature = "unlock_notify"))] -pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3, _code: c_int) -> c_int { +pub fn is_locked(_db: *mut ffi::sqlite3, _rc: c_int) -> bool { + unreachable!() +} + +#[cfg(not(feature = "unlock_notify"))] +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { unreachable!() } From 83775ee62d6ca8d281c9dc25c96fb669127bd8cd Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Apr 2018 20:18:51 +0200 Subject: [PATCH 08/10] Remove second field from RawStatement Use sqlite3_db_handle instead --- src/lib.rs | 2 +- src/raw_statement.rs | 11 ++++++----- src/statement.rs | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a30078..3f71362 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -895,7 +895,7 @@ impl InnerConnection { } }; self.decode_result(r).map(|_| { - Statement::new(conn, RawStatement::new(c_stmt, self.db())) + Statement::new(conn, RawStatement::new(c_stmt)) }) } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 9b756da..3c33ea3 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -6,11 +6,11 @@ use super::unlock_notify; // Private newtype for raw sqlite3_stmts that finalize themselves when dropped. #[derive(Debug)] -pub struct RawStatement(*mut ffi::sqlite3_stmt, *mut ffi::sqlite3); +pub struct RawStatement(*mut ffi::sqlite3_stmt); impl RawStatement { - pub fn new(stmt: *mut ffi::sqlite3_stmt, db: *mut ffi::sqlite3) -> RawStatement { - RawStatement(stmt, db) + pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement { + RawStatement(stmt) } pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { @@ -31,13 +31,14 @@ impl RawStatement { pub fn step(&self) -> c_int { if cfg!(feature = "unlock_notify") { + let db = unsafe { ffi::sqlite3_db_handle(self.0) }; let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - if !unlock_notify::is_locked(self.1, rc) { + if !unlock_notify::is_locked(db, rc) { break; } - rc = unlock_notify::wait_for_unlock_notify(self.1); + rc = unlock_notify::wait_for_unlock_notify(db); if rc != ffi::SQLITE_OK { break; } diff --git a/src/statement.rs b/src/statement.rs index 2c5227b..2674c32 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -462,7 +462,7 @@ impl<'conn> Statement<'conn> { } fn finalize_(&mut self) -> Result<()> { - let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); self.conn.decode_result(stmt.finalize()) } @@ -470,7 +470,7 @@ impl<'conn> Statement<'conn> { impl<'conn> Into for Statement<'conn> { fn into(mut self) -> RawStatement { - let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); stmt } From b0e22fc372d1cd6cdc443067547f92b1df16ab1b Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 4 May 2018 18:07:11 +0200 Subject: [PATCH 09/10] Wait in a loop --- src/unlock_notify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 985958b..491815c 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -30,7 +30,7 @@ impl UnlockNotification { fn wait(&mut self) -> bool { let mut fired = self.mutex.lock().unwrap(); - if !*fired { + while !*fired { fired = self.cond.wait(fired).unwrap(); } *fired From 403e840c4a030fa6e4d04a1c4b2414fa48a38e0d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 4 May 2018 19:05:48 +0200 Subject: [PATCH 10/10] Fix clippy warnings --- src/unlock_notify.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 491815c..b11c432 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -28,12 +28,11 @@ impl UnlockNotification { self.cond.notify_one(); } - fn wait(&mut self) -> bool { + fn wait(&mut self) { let mut fired = self.mutex.lock().unwrap(); while !*fired { fired = self.cond.wait(fired).unwrap(); } - *fired } } @@ -50,10 +49,10 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { #[cfg(feature = "unlock_notify")] pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { - return rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { + rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { ffi::sqlite3_extended_errcode(db) } - == ffi::SQLITE_LOCKED_SHAREDCACHE; + == ffi::SQLITE_LOCKED_SHAREDCACHE } /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()`