I have the following drop handler in my code:
struct CursorBackend {
inner: *mut lmdb::MDB_cursor,
closed: std::sync::Mutex<bool>,
inner_txn: *mut lmdb::MDB_txn,
}
impl Drop for CursorBackend {
fn drop(&mut self) {
if !*self.closed.get_mut().unwrap() {
unsafe {
lmdb::mdb_cursor_close(self.inner);
}
}
}
}
And somewhere else in the code:
impl CursorBackend {
fn assert_txn_backend(&self, txn_backend: &TxnBackend) {
let closed = self.closed.lock().unwrap();
if *closed || self.inner_txn != txn_backend.inner {
panic!("cursor does not belong to transaction");
}
drop(closed);
}
}
Now I have a test like this one:
#[test]
#[should_panic(expected = "cursor does not belong to transaction")]
fn test_cursor_wrong_txn() {
let db_opts = DbOptions::new()
.key_type::<str>()
.value_type::<str>()
.keys_duplicate()
.unnamed();
let td = tempdir().unwrap();
let env_builder = EnvBuilder::new().dir(td.path());
let mut env = unsafe { env_builder.open_rw() }.unwrap();
let db = unsafe { env.create_db(&db_opts).unwrap() };
let txn1 = env.txn().unwrap();
let cursor = txn1.new_cursor(&db).unwrap();
txn1.abort();
let txn2 = env.txn().unwrap();
txn2.cursor_set_first(&cursor).unwrap();
}
Calling cargo test
gives:
…
test tests::test_cursor_delete ... ok
test tests::test_cursor_dupkey ... ok
test tests::test_create_and_drop_db ... ok
test tests::test_wrong_db - should panic ... ok
thread panicked while panicking. aborting.
error: test failed, to rerun pass '--lib'
Caused by:
process didn't exit successfully: `/usr/home/jbe/mycrate/target/debug/deps/mycrate-a76b036331fa3043` (signal: 6, SIGABRT: process abort signal)
YIKES! I don't even get a proper error message!
Using GDB, I could traceback the error to this line in the Drop
implementation for CursorBackend
, where a PoisonError
gets unwrap
'ed:
if !*self.closed.get_mut().unwrap() {
I was able to fix the problem using this modifiction:
fn assert_txn_backend(&self, txn_backend: &TxnBackend) {
let closed = self.closed.lock().unwrap();
if *closed || self.inner_txn != txn_backend.inner {
+ drop(closed);
panic!("cursor does not belong to transaction");
}
drop(closed);
}
While this fixes my immediate problem (I could also use parking_lot
, which doesn't have poisoned mutexes), I have a couple of questions now :
-
How do I write my drop handler correctly? Is it okay to
.unwrap()
aLockResult
or must/should I handle thePoisonError
always in a drop handler? And if so, how? -
If I use locks that can be poisoned, am I always responsible for my code to not panic while the lock is held? I always thought that panicking ends everything anyway (by default), but apparently that's not true. It looks like the stack is unwound and during that procedure a couple of values are dropped and their drop handlers executed?
-
Does that mean my state must always be safe in such a way that when I panic nothing bad happens when values are dropped? This might also affect other conditions that do not involve locks but other states my values could be in, which are bad when dropped.