Skip to content

Commit efb6dde

Browse files
committed
Merge #11911: Free BerkeleyEnvironment instances when not in use
14bc2a1 Trivial: add doxygen-compatible comments relating to BerkeleyEnvironment (Pierre Rochard) 88b1d95 Tests: add unit tests for GetWalletEnv (Pierre Rochard) f1f4bb7 Free BerkeleyEnvironment instances when not in use (Russell Yanofsky) Pull request description: 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 @TheBlueMatt 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. Tree-SHA512: 219d77a9e2268298435b86088f998795e059fdab1d2050ba284a9ab8d8a44961c9b5cf96e94ee521688108d23c6db680e3e3a999b8cb2ac2a8590f691d50668b
2 parents 252fd15 + 14bc2a1 commit efb6dde

File tree

5 files changed

+124
-33
lines changed

5 files changed

+124
-33
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ endif
138138

139139
if ENABLE_WALLET
140140
BITCOIN_TESTS += \
141+
wallet/test/db_tests.cpp \
141142
wallet/test/psbt_wallet_tests.cpp \
142143
wallet/test/wallet_tests.cpp \
143144
wallet/test/wallet_crypto_tests.cpp \

src/wallet/db.cpp

Lines changed: 29 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,29 @@ 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+
/**
88+
* @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
89+
* @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
90+
* @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment
91+
* erases the weak pointer from the g_dbenvs map.
92+
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
93+
*/
94+
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
8795
{
8896
fs::path env_directory;
8997
SplitWalletPath(wallet_path, env_directory, database_filename);
9098
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;
99+
auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>());
100+
if (inserted.second) {
101+
auto env = std::make_shared<BerkeleyEnvironment>(env_directory.string());
102+
inserted.first->second = env;
103+
return env;
104+
}
105+
return inserted.first->second.lock();
96106
}
97107

98108
//
@@ -137,6 +147,7 @@ BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir
137147

138148
BerkeleyEnvironment::~BerkeleyEnvironment()
139149
{
150+
g_dbenvs.erase(strPath);
140151
Close();
141152
}
142153

@@ -214,10 +225,10 @@ bool BerkeleyEnvironment::Open(bool retry)
214225
return true;
215226
}
216227

217-
void BerkeleyEnvironment::MakeMock()
228+
//! Construct an in-memory mock Berkeley environment for testing and as a place-holder for g_dbenvs emplace
229+
BerkeleyEnvironment::BerkeleyEnvironment()
218230
{
219-
if (fDbEnvInit)
220-
throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized");
231+
Reset();
221232

222233
boost::this_thread::interruption_point();
223234

@@ -305,7 +316,7 @@ BerkeleyBatch::SafeDbt::operator Dbt*()
305316
bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
306317
{
307318
std::string filename;
308-
BerkeleyEnvironment* env = GetWalletEnv(file_path, filename);
319+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
309320

310321
// Recovery procedure:
311322
// move wallet file to walletfilename.timestamp.bak
@@ -374,7 +385,7 @@ bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, boo
374385
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
375386
{
376387
std::string walletFile;
377-
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
388+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
378389
fs::path walletDir = env->Directory();
379390

380391
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(nullptr, nullptr, nullptr));
@@ -398,7 +409,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er
398409
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
399410
{
400411
std::string walletFile;
401-
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
412+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
402413
fs::path walletDir = env->Directory();
403414

404415
if (fs::exists(walletDir / walletFile))
@@ -502,7 +513,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
502513
{
503514
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
504515
fFlushOnClose = fFlushOnCloseIn;
505-
env = database.env;
516+
env = database.env.get();
506517
if (database.IsDummy()) {
507518
return;
508519
}
@@ -559,7 +570,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
559570
// versions of BDB have an set_lk_exclusive method for this
560571
// purpose, but the older version we use does not.)
561572
for (const auto& env : g_dbenvs) {
562-
CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
573+
CheckUniqueFileid(*env.second.lock().get(), strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
563574
}
564575

565576
pdb = pdb_temp.release();
@@ -660,7 +671,7 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
660671
if (database.IsDummy()) {
661672
return true;
662673
}
663-
BerkeleyEnvironment *env = database.env;
674+
BerkeleyEnvironment *env = database.env.get();
664675
const std::string& strFile = database.strFile;
665676
while (true) {
666677
{
@@ -791,7 +802,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
791802
return true;
792803
}
793804
bool ret = false;
794-
BerkeleyEnvironment *env = database.env;
805+
BerkeleyEnvironment *env = database.env.get();
795806
const std::string& strFile = database.strFile;
796807
TRY_LOCK(cs_db, lockDb);
797808
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/test/db_tests.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) 2018 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <memory>
6+
7+
#include <boost/test/unit_test.hpp>
8+
9+
#include <fs.h>
10+
#include <test/test_bitcoin.h>
11+
#include <wallet/db.h>
12+
13+
14+
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
15+
16+
BOOST_AUTO_TEST_CASE(getwalletenv_file)
17+
{
18+
std::string test_name = "test_name.dat";
19+
fs::path datadir = SetDataDir("tempdir");
20+
fs::path file_path = datadir / test_name;
21+
std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR);
22+
f.close();
23+
24+
std::string filename;
25+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
26+
BOOST_CHECK(filename == test_name);
27+
BOOST_CHECK(env->Directory() == datadir);
28+
}
29+
30+
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
31+
{
32+
std::string expected_name = "wallet.dat";
33+
fs::path datadir = SetDataDir("tempdir");
34+
35+
std::string filename;
36+
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
37+
BOOST_CHECK(filename == expected_name);
38+
BOOST_CHECK(env->Directory() == datadir);
39+
}
40+
41+
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
42+
{
43+
fs::path datadir = SetDataDir("tempdir");
44+
fs::path datadir_2 = SetDataDir("tempdir_2");
45+
std::string filename;
46+
47+
std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
48+
std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename);
49+
std::shared_ptr<BerkeleyEnvironment> env_3 = GetWalletEnv(datadir_2, filename);
50+
51+
BOOST_CHECK(env_1 == env_2);
52+
BOOST_CHECK(env_2 != env_3);
53+
}
54+
55+
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance)
56+
{
57+
fs::path datadir = SetDataDir("tempdir");
58+
fs::path datadir_2 = SetDataDir("tempdir_2");
59+
std::string filename;
60+
61+
std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename);
62+
std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename);
63+
env_1_a.reset();
64+
65+
std::shared_ptr<BerkeleyEnvironment> env_1_b = GetWalletEnv(datadir, filename);
66+
std::shared_ptr<BerkeleyEnvironment> env_2_b = GetWalletEnv(datadir_2, filename);
67+
68+
BOOST_CHECK(env_1_a != env_1_b);
69+
BOOST_CHECK(env_2_a == env_2_b);
70+
}
71+
72+
BOOST_AUTO_TEST_SUITE_END()

src/wallet/wallet.cpp

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

3925+
// Keep same database environment instance across Verify/Recover calls below.
3926+
std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path);
3927+
39253928
try {
39263929
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
39273930
return false;

0 commit comments

Comments
 (0)