Unclear `Database` drop semantics and deadlock risk
Consider this code:
fn main() {
let txn = make_txn();
println!("Got txn");
}
fn make_txn() -> redb::WriteTransaction {
println!("Making db");
let db = redb::Database::create("foo.redb").unwrap();
println!("Starting txn");
let txn = db.begin_write().unwrap();
println!("Started txn");
txn
}
I expected this to print:
Making db
Starting txn
Started txn
Got txn
(process exited)
Instead, redb hangs at the end of make_txn:
Making db
Starting txn
Started txn
(hangs)
I believe this is due to impl Drop for Database, which waits for self.mem.close:
if self.mem.close().is_err() {
#[cfg(feature = "logging")]
warn!("Failed to flush database file. Repair may be required at restart.");
}
But the txn we're returning isn't dropped, so the memory never closes, and the Drop impl for Database deadlocks.
I expected the WriteTransaction to keep the database alive by a ref-count or something similar, but this deadlock is not documented anywhere on Database, Database::begin_write, or WriteTransaction. It would be nice if this was documented.
In my use-case, I'm not keeping a Database value around for long. I'm one-shot creating a database file, write into it, and immediately close the txn and database (then re-open it for read-only). So I don't need to keep the database value around, only the txn.
Ah ya, thanks for the report. I think this is due to the change I made in 3.0 about closing databases when they drop to avoid a situation where read transactions could unexpectedly keep a database open. But ya, maybe write transactions should keep it alive. And ya, it certainly shouldn't deadlock