Skip to content

Unclear Database drop semantics and deadlock risk #1072

@aecsocket

Description

@aecsocket

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions