Merge bitcoin/bitcoin#27556: wallet: fix deadlock in bdb read write operation

69d43905b7 test: add coverage for wallet read write db deadlock (furszy)
12daf6fcdc walletdb: scope bdb::EraseRecords under a single db txn (furszy)
043fcb0b05 wallet: bugfix, GetNewCursor() misses to provide batch ptr to BerkeleyCursor (furszy)

Pull request description:

  Decoupled from #26644 so it can closed in favor of #26715.

  Basically, with bdb, we can't make a write operation while we are traversing the db with the same db handler. These two operations are performed in different txn contexts and cause a deadlock.

  Added coverage by using `EraseRecords()` which is the simplest function that executes this process.

  To replicate it, need bdb support and drop the first commit. The test will run forever.

ACKs for top commit:
  achow101:
    ACK 69d43905b7
  hebasto:
    re-ACK 69d43905b7

Tree-SHA512: b3773be78925f674e962f4a5c54b398a9d0cfe697148c01c3ec0d68281cc5c1444b38165960d219ef3cf1a57c8ce6427f44a876275958d49bbc0808486e19d7d
This commit is contained in:
Andrew Chow
2023-05-18 11:02:09 -04:00
6 changed files with 56 additions and 18 deletions

View File

@@ -1136,6 +1136,9 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags)
bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
{
// Begin db txn
if (!m_batch->TxnBegin()) return false;
// Get cursor
std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
if (!cursor)
@@ -1144,8 +1147,7 @@ bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
}
// Iterate the DB and look for any records that have the type prefixes
while (true)
{
while (true) {
// Read next record
DataStream key{};
DataStream value{};
@@ -1153,6 +1155,8 @@ bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
if (status == DatabaseCursor::Status::DONE) {
break;
} else if (status == DatabaseCursor::Status::FAIL) {
cursor.reset(nullptr);
m_batch->TxnAbort(); // abort db txn
return false;
}
@@ -1163,10 +1167,16 @@ bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
key >> type;
if (types.count(type) > 0) {
m_batch->Erase(key_data);
if (!m_batch->Erase(key_data)) {
cursor.reset(nullptr);
m_batch->TxnAbort();
return false; // erase failed
}
}
}
return true;
// Finish db txn
cursor.reset(nullptr);
return m_batch->TxnCommit();
}
bool WalletBatch::TxnBegin()