redb icon indicating copy to clipboard operation
redb copied to clipboard

Unclear `Database` drop semantics and deadlock risk

Open aecsocket opened this issue 3 months ago • 1 comments

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.

aecsocket avatar Sep 12 '25 20:09 aecsocket

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

cberner avatar Sep 13 '25 02:09 cberner