Skip to content

Commit 8db2334

Browse files
committed
Merge #19335: wallet: Cleanup and separate BerkeleyDatabase and BerkeleyBatch
74507ce walletdb: Remove BerkeleyBatch friend class from BerkeleyDatabase (Andrew Chow) 00f0041 No need to check for duplicate fileids in all dbenvs (Andrew Chow) d86efab walletdb: Move Db->open to BerkeleyDatabase::Open (Andrew Chow) 4fe4b3b walletdb: track database file use as m_refcount within BerkeleyDatabase (Andrew Chow) 65fb880 Combine BerkeleyEnvironment::Verify into BerkeleyDatabase::Verify (Andrew Chow) Pull request description: `BerkeleyBatch` and `BerkeleyDatabase` are kind of messy. The goal of this is to clean up them up so that they are logically separated. `BerkeleyBatch` currently handles the creation of the `BerkeleyDatabase`'s `Db` handle. This is instead moved into `BerkeleyDatabase` and is called by `BerkeleyBatch`. Instead of having `BerkeleyEnvironment` track each database's usage, have `BerkeleyDatabase` track this usage itself with the `m_refcount` variable that is present in `WalletDatabase`. Lastly, instead of having each `BerkeleyEnvironment` store the fileids of the databases open in it, have a global `g_fileids` to track those fileids. We were already checking fileid uniqueness globally (by checking the fileids in every environment when opening a database) so it's cleaner to do this with a global variable. All of these changes allow us to make `BerkeleyBatch` and `BerkeleyDatabase` no longer be friend classes. The diff of this PR is currently the same as in ##18971 Requires #19334 ACKs for top commit: laanwj: Code review ACK 74507ce ryanofsky: Code review ACK 74507ce. No changes since last review other than rebase Tree-SHA512: 845d84ee1a470e2bf5d2e2e3d7738183d8ce43ddd06a0bbd57edecf5779b2f55d70728b1b57f5daab0f078650a8d60c3e19dc30b75b36e7aa952ce268399d5f6
2 parents 400f45e + 74507ce commit 8db2334

File tree

3 files changed

+74
-90
lines changed

3 files changed

+74
-90
lines changed

src/wallet/bdb.cpp

Lines changed: 68 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filena
3232

3333
int ret = db.get_mpf()->get_fileid(fileid.value);
3434
if (ret != 0) {
35-
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
35+
throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (get_fileid failed with %d)", filename, ret));
3636
}
3737

3838
for (const auto& item : env.m_fileids) {
3939
if (fileid == item.second && &fileid != &item.second) {
40-
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
40+
throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (duplicates fileid %s from %s)", filename,
4141
HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
4242
}
4343
}
@@ -97,9 +97,8 @@ void BerkeleyEnvironment::Close()
9797
fDbEnvInit = false;
9898

9999
for (auto& db : m_databases) {
100-
auto count = mapFileUseCount.find(db.first);
101-
assert(count == mapFileUseCount.end() || count->second == 0);
102100
BerkeleyDatabase& database = db.second.get();
101+
assert(database.m_refcount <= 0);
103102
if (database.m_db) {
104103
database.m_db->close(0);
105104
database.m_db.reset();
@@ -232,16 +231,6 @@ BerkeleyEnvironment::BerkeleyEnvironment()
232231
fMockDb = true;
233232
}
234233

235-
bool BerkeleyEnvironment::Verify(const std::string& strFile)
236-
{
237-
LOCK(cs_db);
238-
assert(mapFileUseCount.count(strFile) == 0);
239-
240-
Db db(dbenv.get(), 0);
241-
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
242-
return result == 0;
243-
}
244-
245234
BerkeleyBatch::SafeDbt::SafeDbt()
246235
{
247236
m_dbt.set_flags(DB_DBT_MALLOC);
@@ -295,7 +284,11 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr)
295284

296285
if (fs::exists(file_path))
297286
{
298-
if (!env->Verify(strFile)) {
287+
assert(m_refcount == 0);
288+
289+
Db db(env->dbenv.get(), 0);
290+
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
291+
if (result != 0) {
299292
errorStr = strprintf(_("%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup."), file_path);
300293
return false;
301294
}
@@ -316,6 +309,8 @@ BerkeleyDatabase::~BerkeleyDatabase()
316309
{
317310
if (env) {
318311
LOCK(cs_db);
312+
env->CloseDb(strFile);
313+
assert(!m_db);
319314
size_t erased = env->m_databases.erase(strFile);
320315
assert(erased == 1);
321316
env->m_fileids.erase(strFile);
@@ -324,13 +319,27 @@ BerkeleyDatabase::~BerkeleyDatabase()
324319

325320
BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_cursor(nullptr), m_database(database)
326321
{
322+
database.AddRef();
323+
database.Open(pszMode);
327324
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
328325
fFlushOnClose = fFlushOnCloseIn;
329326
env = database.env.get();
330-
if (database.IsDummy()) {
327+
pdb = database.m_db.get();
328+
strFile = database.strFile;
329+
bool fCreate = strchr(pszMode, 'c') != nullptr;
330+
if (fCreate && !Exists(std::string("version"))) {
331+
bool fTmp = fReadOnly;
332+
fReadOnly = false;
333+
Write(std::string("version"), CLIENT_VERSION);
334+
fReadOnly = fTmp;
335+
}
336+
}
337+
338+
void BerkeleyDatabase::Open(const char* pszMode)
339+
{
340+
if (IsDummy()){
331341
return;
332342
}
333-
const std::string &strFilename = database.strFile;
334343

335344
bool fCreate = strchr(pszMode, 'c') != nullptr;
336345
unsigned int nFlags = DB_THREAD;
@@ -341,10 +350,9 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
341350
LOCK(cs_db);
342351
bilingual_str open_err;
343352
if (!env->Open(open_err))
344-
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
353+
throw std::runtime_error("BerkeleyDatabase: Failed to open database environment.");
345354

346-
pdb = database.m_db.get();
347-
if (pdb == nullptr) {
355+
if (m_db == nullptr) {
348356
int ret;
349357
std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
350358

@@ -353,60 +361,33 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
353361
DbMpoolFile* mpf = pdb_temp->get_mpf();
354362
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
355363
if (ret != 0) {
356-
throw std::runtime_error(strprintf("BerkeleyBatch: Failed to configure for no temp file backing for database %s", strFilename));
364+
throw std::runtime_error(strprintf("BerkeleyDatabase: Failed to configure for no temp file backing for database %s", strFile));
357365
}
358366
}
359367

360368
ret = pdb_temp->open(nullptr, // Txn pointer
361-
fMockDb ? nullptr : strFilename.c_str(), // Filename
362-
fMockDb ? strFilename.c_str() : "main", // Logical db name
369+
fMockDb ? nullptr : strFile.c_str(), // Filename
370+
fMockDb ? strFile.c_str() : "main", // Logical db name
363371
DB_BTREE, // Database type
364372
nFlags, // Flags
365373
0);
366374

367375
if (ret != 0) {
368-
throw std::runtime_error(strprintf("BerkeleyBatch: Error %d, can't open database %s", ret, strFilename));
376+
throw std::runtime_error(strprintf("BerkeleyDatabase: Error %d, can't open database %s", ret, strFile));
369377
}
378+
m_file_path = (env->Directory() / strFile).string();
370379

371380
// Call CheckUniqueFileid on the containing BDB environment to
372381
// avoid BDB data consistency bugs that happen when different data
373382
// files in the same environment have the same fileid.
374-
//
375-
// Also call CheckUniqueFileid on all the other g_dbenvs to prevent
376-
// bitcoin from opening the same data file through another
377-
// environment when the file is referenced through equivalent but
378-
// not obviously identical symlinked or hard linked or bind mounted
379-
// paths. In the future a more relaxed check for equal inode and
380-
// device ids could be done instead, which would allow opening
381-
// different backup copies of a wallet at the same time. Maybe even
382-
// more ideally, an exclusive lock for accessing the database could
383-
// be implemented, so no equality checks are needed at all. (Newer
384-
// versions of BDB have an set_lk_exclusive method for this
385-
// purpose, but the older version we use does not.)
386-
for (const auto& env : g_dbenvs) {
387-
CheckUniqueFileid(*env.second.lock().get(), strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
388-
}
383+
CheckUniqueFileid(*env, strFile, *pdb_temp, this->env->m_fileids[strFile]);
389384

390-
pdb = pdb_temp.release();
391-
database.m_db.reset(pdb);
385+
m_db.reset(pdb_temp.release());
392386

393-
if (fCreate && !Exists(std::string("version"))) {
394-
bool fTmp = fReadOnly;
395-
fReadOnly = false;
396-
Write(std::string("version"), CLIENT_VERSION);
397-
fReadOnly = fTmp;
398-
}
399387
}
400-
database.AddRef();
401-
strFile = strFilename;
402388
}
403389
}
404390

405-
void BerkeleyDatabase::Open(const char* mode)
406-
{
407-
throw std::logic_error("BerkeleyDatabase does not implement Open. This function should not be called.");
408-
}
409-
410391
void BerkeleyBatch::Flush()
411392
{
412393
if (activeTxn)
@@ -427,6 +408,12 @@ void BerkeleyDatabase::IncrementUpdateCounter()
427408
++nUpdateCounter;
428409
}
429410

411+
BerkeleyBatch::~BerkeleyBatch()
412+
{
413+
Close();
414+
m_database.RemoveRef();
415+
}
416+
430417
void BerkeleyBatch::Close()
431418
{
432419
if (!pdb)
@@ -439,8 +426,6 @@ void BerkeleyBatch::Close()
439426

440427
if (fFlushOnClose)
441428
Flush();
442-
443-
m_database.RemoveRef();
444429
}
445430

446431
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
@@ -464,8 +449,8 @@ void BerkeleyEnvironment::ReloadDbEnv()
464449
AssertLockNotHeld(cs_db);
465450
std::unique_lock<RecursiveMutex> lock(cs_db);
466451
m_db_in_use.wait(lock, [this](){
467-
for (auto& count : mapFileUseCount) {
468-
if (count.second > 0) return false;
452+
for (auto& db : m_databases) {
453+
if (db.second.get().m_refcount > 0) return false;
469454
}
470455
return true;
471456
});
@@ -493,11 +478,11 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip)
493478
while (true) {
494479
{
495480
LOCK(cs_db);
496-
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
481+
if (m_refcount <= 0) {
497482
// Flush log data to the dat file
498483
env->CloseDb(strFile);
499484
env->CheckpointLSN(strFile);
500-
env->mapFileUseCount.erase(strFile);
485+
m_refcount = -1;
501486

502487
bool fSuccess = true;
503488
LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
@@ -581,10 +566,11 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
581566
return;
582567
{
583568
LOCK(cs_db);
584-
std::map<std::string, int>::iterator mi = mapFileUseCount.begin();
585-
while (mi != mapFileUseCount.end()) {
586-
std::string strFile = (*mi).first;
587-
int nRefCount = (*mi).second;
569+
bool no_dbs_accessed = true;
570+
for (auto& db_it : m_databases) {
571+
std::string strFile = db_it.first;
572+
int nRefCount = db_it.second.get().m_refcount;
573+
if (nRefCount < 0) continue;
588574
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
589575
if (nRefCount == 0) {
590576
// Move log data to the dat file
@@ -595,14 +581,15 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
595581
if (!fMockDb)
596582
dbenv->lsn_reset(strFile.c_str(), 0);
597583
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
598-
mapFileUseCount.erase(mi++);
599-
} else
600-
mi++;
584+
nRefCount = -1;
585+
} else {
586+
no_dbs_accessed = false;
587+
}
601588
}
602589
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
603590
if (fShutdown) {
604591
char** listp;
605-
if (mapFileUseCount.empty()) {
592+
if (no_dbs_accessed) {
606593
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
607594
Close();
608595
if (!fMockDb) {
@@ -623,21 +610,20 @@ bool BerkeleyDatabase::PeriodicFlush()
623610
if (!lockDb) return false;
624611

625612
// Don't flush if any databases are in use
626-
for (const auto& use_count : env->mapFileUseCount) {
627-
if (use_count.second > 0) return false;
613+
for (auto& it : env->m_databases) {
614+
if (it.second.get().m_refcount > 0) return false;
628615
}
629616

630617
// Don't flush if there haven't been any batch writes for this database.
631-
auto it = env->mapFileUseCount.find(strFile);
632-
if (it == env->mapFileUseCount.end()) return false;
618+
if (m_refcount < 0) return false;
633619

634620
LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile);
635621
int64_t nStart = GetTimeMillis();
636622

637623
// Flush wallet file so it's self contained
638624
env->CloseDb(strFile);
639625
env->CheckpointLSN(strFile);
640-
env->mapFileUseCount.erase(it);
626+
m_refcount = -1;
641627

642628
LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
643629

@@ -653,12 +639,11 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) const
653639
{
654640
{
655641
LOCK(cs_db);
656-
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
642+
if (m_refcount <= 0)
657643
{
658644
// Flush log data to the dat file
659645
env->CloseDb(strFile);
660646
env->CheckpointLSN(strFile);
661-
env->mapFileUseCount.erase(strFile);
662647

663648
// Copy wallet file
664649
fs::path pathSrc = env->Directory() / strFile;
@@ -840,16 +825,18 @@ bool BerkeleyBatch::HasKey(CDataStream&& key)
840825
void BerkeleyDatabase::AddRef()
841826
{
842827
LOCK(cs_db);
843-
++env->mapFileUseCount[strFile];
828+
if (m_refcount < 0) {
829+
m_refcount = 1;
830+
} else {
831+
m_refcount++;
832+
}
844833
}
845834

846835
void BerkeleyDatabase::RemoveRef()
847836
{
848-
{
849-
LOCK(cs_db);
850-
--env->mapFileUseCount[strFile];
851-
}
852-
env->m_db_in_use.notify_all();
837+
LOCK(cs_db);
838+
m_refcount--;
839+
if (env) env->m_db_in_use.notify_all();
853840
}
854841

855842
std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(const char* mode, bool flush_on_close)

src/wallet/bdb.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class BerkeleyEnvironment
5252

5353
public:
5454
std::unique_ptr<DbEnv> dbenv;
55-
std::map<std::string, int> mapFileUseCount;
5655
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
5756
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
5857
std::condition_variable_any m_db_in_use;
@@ -67,8 +66,6 @@ class BerkeleyEnvironment
6766
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
6867
fs::path Directory() const { return strPath; }
6968

70-
bool Verify(const std::string& strFile);
71-
7269
bool Open(bilingual_str& error);
7370
void Close();
7471
void Flush(bool fShutdown);
@@ -100,7 +97,6 @@ class BerkeleyBatch;
10097
**/
10198
class BerkeleyDatabase : public WalletDatabase
10299
{
103-
friend class BerkeleyBatch;
104100
public:
105101
/** Create dummy DB handle */
106102
BerkeleyDatabase() : WalletDatabase(), env(nullptr)
@@ -166,11 +162,12 @@ class BerkeleyDatabase : public WalletDatabase
166162
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
167163
std::unique_ptr<Db> m_db;
168164

165+
std::string strFile;
166+
169167
/** Make a BerkeleyBatch connected to this database */
170168
std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) override;
171169

172170
private:
173-
std::string strFile;
174171

175172
/** Return whether this database handle is a dummy for testing.
176173
* Only to be used at a low level, application should ideally not care
@@ -220,7 +217,7 @@ class BerkeleyBatch : public DatabaseBatch
220217

221218
public:
222219
explicit BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
223-
~BerkeleyBatch() override { Close(); }
220+
~BerkeleyBatch() override;
224221

225222
BerkeleyBatch(const BerkeleyBatch&) = delete;
226223
BerkeleyBatch& operator=(const BerkeleyBatch&) = delete;

test/functional/wallet_multiwallet.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def wallet_file(name):
120120

121121
# should not initialize if one wallet is a copy of another
122122
shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
123-
exp_stderr = r"BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
123+
exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
124124
self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
125125

126126
# should not initialize if wallet file is a symlink
@@ -258,10 +258,10 @@ def wallet_file(name):
258258
assert_raises_rpc_error(-4, "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
259259

260260
# Fail to load if one wallet is a copy of another
261-
assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
261+
assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
262262

263263
# Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
264-
assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
264+
assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
265265

266266

267267
# Fail to load if wallet file is a symlink

0 commit comments

Comments
 (0)