Skip to content

Commit f1f4bb7

Browse files
committed
Free BerkeleyEnvironment instances when not in use
Instead of adding BerkeleyEnvironment objects permanently to the g_dbenvs map, use reference counted shared pointers and remove map entries when the last BerkeleyEnvironment reference goes out of scope. This change was requested by Matt Corallo <[email protected]> and makes code that sets up mock databases cleaner. The mock database environment will now go out of scope and be reset on destruction so there is no need to call BerkeleyEnvironment::Reset() during wallet construction to clear out prior state. This change does affect bitcoin behavior slightly. On startup, instead of same wallet environments staying open throughout VerifyWallets() and OpenWallets() calls, VerifyWallets() will open and close an environment once for each wallet, and OpenWallets() will create its own environment(s) later.
1 parent b5c3d7a commit f1f4bb7

File tree

3 files changed

+43
-33
lines changed

3 files changed

+43
-33
lines changed

src/wallet/db.cpp

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filena
4848
}
4949

5050
CCriticalSection cs_db;
51-
std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment.
51+
std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment.
5252
} // namespace
5353

5454
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
@@ -80,19 +80,22 @@ bool IsWalletLoaded(const fs::path& wallet_path)
8080
LOCK(cs_db);
8181
auto env = g_dbenvs.find(env_directory.string());
8282
if (env == g_dbenvs.end()) return false;
83-
return env->second.IsDatabaseLoaded(database_filename);
83+
auto database = env->second.lock();
84+
return database && database->IsDatabaseLoaded(database_filename);
8485
}
8586

86-
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
87+
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
8788
{
8889
fs::path env_directory;
8990
SplitWalletPath(wallet_path, env_directory, database_filename);
9091
LOCK(cs_db);
91-
// Note: An unused temporary BerkeleyEnvironment object may be created inside the
92-
// emplace function if the key already exists. This is a little inefficient,
93-
// but not a big concern since the map will be changed in the future to hold
94-
// pointers instead of objects, anyway.
95-
return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
92+
auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>());
93+
if (inserted.second) {
94+
auto env = std::make_shared<BerkeleyEnvironment>(env_directory.string());
95+
inserted.first->second = env;
96+
return env;
97+
}
98+
return inserted.first->second.lock();
9699
}
97100

98101
//
@@ -137,6 +140,7 @@ BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir
137140

138141
BerkeleyEnvironment::~BerkeleyEnvironment()
139142
{
143+
g_dbenvs.erase(strPath);
140144
Close();
141145
}
142146

@@ -214,10 +218,9 @@ bool BerkeleyEnvironment::Open(bool retry)
214218
return true;
215219
}
216220

217-
void BerkeleyEnvironment::MakeMock()
221+
BerkeleyEnvironment::BerkeleyEnvironment()
218222
{
219-
if (fDbEnvInit)
220-
throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized");
223+
Reset();
221224

222225
boost::this_thread::interruption_point();
223226

@@ -266,7 +269,7 @@ BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string&
266269
bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
267270
{
268271
std::string filename;
269-
BerkeleyEnvironment* env = GetWalletEnv(file_path, filename);
272+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
270273

271274
// Recovery procedure:
272275
// move wallet file to walletfilename.timestamp.bak
@@ -335,7 +338,7 @@ bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, boo
335338
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
336339
{
337340
std::string walletFile;
338-
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
341+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
339342
fs::path walletDir = env->Directory();
340343

341344
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
@@ -359,7 +362,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er
359362
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
360363
{
361364
std::string walletFile;
362-
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
365+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
363366
fs::path walletDir = env->Directory();
364367

365368
if (fs::exists(walletDir / walletFile))
@@ -463,7 +466,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
463466
{
464467
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
465468
fFlushOnClose = fFlushOnCloseIn;
466-
env = database.env;
469+
env = database.env.get();
467470
if (database.IsDummy()) {
468471
return;
469472
}
@@ -520,7 +523,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
520523
// versions of BDB have an set_lk_exclusive method for this
521524
// purpose, but the older version we use does not.)
522525
for (const auto& env : g_dbenvs) {
523-
CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
526+
CheckUniqueFileid(*env.second.lock().get(), strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
524527
}
525528

526529
pdb = pdb_temp.release();
@@ -621,7 +624,7 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
621624
if (database.IsDummy()) {
622625
return true;
623626
}
624-
BerkeleyEnvironment *env = database.env;
627+
BerkeleyEnvironment *env = database.env.get();
625628
const std::string& strFile = database.strFile;
626629
while (true) {
627630
{
@@ -752,7 +755,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
752755
return true;
753756
}
754757
bool ret = false;
755-
BerkeleyEnvironment *env = database.env;
758+
BerkeleyEnvironment *env = database.env.get();
756759
const std::string& strFile = database.strFile;
757760
TRY_LOCK(cs_db, lockDb);
758761
if (lockDb)

src/wallet/db.h

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ class BerkeleyEnvironment
5050
std::condition_variable_any m_db_in_use;
5151

5252
BerkeleyEnvironment(const fs::path& env_directory);
53+
BerkeleyEnvironment();
5354
~BerkeleyEnvironment();
5455
void Reset();
5556

56-
void MakeMock();
5757
bool IsMock() const { return fMockDb; }
5858
bool IsInitialized() const { return fDbEnvInit; }
5959
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
@@ -102,7 +102,7 @@ class BerkeleyEnvironment
102102
bool IsWalletLoaded(const fs::path& wallet_path);
103103

104104
/** Get BerkeleyEnvironment and database filename given a wallet path. */
105-
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
105+
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
106106

107107
/** An instance of this class represents one database.
108108
* For BerkeleyDB this is just a (env, strFile) tuple.
@@ -117,17 +117,11 @@ class BerkeleyDatabase
117117
}
118118

119119
/** Create DB handle to real database */
120-
BerkeleyDatabase(const fs::path& wallet_path, bool mock = false) :
121-
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
120+
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
121+
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename))
122122
{
123-
env = GetWalletEnv(wallet_path, strFile);
124-
auto inserted = env->m_databases.emplace(strFile, std::ref(*this));
123+
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
125124
assert(inserted.second);
126-
if (mock) {
127-
env->Close();
128-
env->Reset();
129-
env->MakeMock();
130-
}
131125
}
132126

133127
~BerkeleyDatabase() {
@@ -140,7 +134,8 @@ class BerkeleyDatabase
140134
/** Return object for accessing database at specified path. */
141135
static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
142136
{
143-
return MakeUnique<BerkeleyDatabase>(path);
137+
std::string filename;
138+
return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
144139
}
145140

146141
/** Return object for accessing dummy database with no read/write capabilities. */
@@ -152,7 +147,7 @@ class BerkeleyDatabase
152147
/** Return object for accessing temporary in-memory database. */
153148
static std::unique_ptr<BerkeleyDatabase> CreateMock()
154149
{
155-
return MakeUnique<BerkeleyDatabase>("", true /* mock */);
150+
return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
156151
}
157152

158153
/** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
@@ -176,12 +171,21 @@ class BerkeleyDatabase
176171
unsigned int nLastFlushed;
177172
int64_t nLastWalletUpdate;
178173

174+
/**
175+
* Pointer to shared database environment.
176+
*
177+
* Normally there is only one BerkeleyDatabase object per
178+
* BerkeleyEnvivonment, but in the special, backwards compatible case where
179+
* multiple wallet BDB data files are loaded from the same directory, this
180+
* will point to a shared instance that gets freed when the last data file
181+
* is closed.
182+
*/
183+
std::shared_ptr<BerkeleyEnvironment> env;
184+
179185
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
180186
std::unique_ptr<Db> m_db;
181187

182188
private:
183-
/** BerkeleyDB specific */
184-
BerkeleyEnvironment *env;
185189
std::string strFile;
186190

187191
/** Return whether this database handle is a dummy for testing.

src/wallet/wallet.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,6 +3877,9 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
38773877
return false;
38783878
}
38793879

3880+
// Keep same database environment instance across Verify/Recover calls below.
3881+
std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path);
3882+
38803883
try {
38813884
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
38823885
return false;

0 commit comments

Comments
 (0)