wallet/db: keep the BDB logs when the shutdown flush fails
CDBEnv::Flush(true) removed database/ (log_archive DB_ARCH_REMOVE, then remove_all) once mapFileUseCount was empty. It did that even when the checkpoint or lsn_reset before it had failed, or the environment had panicked (BDB0060). In those states wallet.dat can be missing committed changes that only the logs hold: - a page write failed (EBADF/ESPIPE through an fd closed behind BDB's back, the wolfSSL CloseSocket bug), so the page stayed dirty in the mpool cache and the checkpoint never completed; - pages written after the last lsn_reset carry real LSNs that point into those logs. Deleting the logs turned a wallet that the next start's DB_RECOVER would repair into one that silently lost those changes. A std::terminate kept the logs; the clean shutdown that ThreadNotifyWallets now does on a wallet DB error deleted them. Remember a failed txn_checkpoint or lsn_reset (in Flush and in CheckpointLSN, which the periodic flush and backupwallet use). Before removing anything, also check the environment for a panic. get_flags reports it, and itself fails with DB_RUNRECOVERY once the environment has panicked. In any of those cases keep database/ and log why. If the kept logs are unusable, the next start already moves database/ aside and retries (CWallet::Verify). WalletDbEnv tests: - a clean shutdown still removes the logs; - a panicked environment (DB_PANIC_ENVIRONMENT) keeps them; - a failed lsn_reset keeps them. With the old db.cpp only the first passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+32
-9
@@ -61,6 +61,7 @@ void CDBEnv::Reset()
|
||||
dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
|
||||
fDbEnvInit = false;
|
||||
fMockDb = false;
|
||||
fKeepLogs = false;
|
||||
}
|
||||
|
||||
CDBEnv::CDBEnv() : dbenv(NULL)
|
||||
@@ -276,10 +277,12 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
|
||||
|
||||
void CDBEnv::CheckpointLSN(const std::string& strFile)
|
||||
{
|
||||
dbenv->txn_checkpoint(0, 0, 0);
|
||||
if (dbenv->txn_checkpoint(0, 0, 0) != 0)
|
||||
fKeepLogs = true;
|
||||
if (fMockDb)
|
||||
return;
|
||||
dbenv->lsn_reset(strFile.c_str(), 0);
|
||||
if (dbenv->lsn_reset(strFile.c_str(), 0) != 0)
|
||||
fKeepLogs = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -498,10 +501,16 @@ void CDBEnv::Flush(bool fShutdown)
|
||||
// Move log data to the dat file
|
||||
CloseDb(strFile);
|
||||
LogPrint("db", "CDBEnv::Flush: %s checkpoint\n", strFile);
|
||||
dbenv->txn_checkpoint(0, 0, 0);
|
||||
int ret = dbenv->txn_checkpoint(0, 0, 0);
|
||||
if (ret != 0) {
|
||||
LogPrintf("CDBEnv::Flush: %s checkpoint failed: %s\n", strFile, DbEnv::strerror(ret));
|
||||
fKeepLogs = true;
|
||||
}
|
||||
LogPrint("db", "CDBEnv::Flush: %s detach\n", strFile);
|
||||
if (!fMockDb)
|
||||
dbenv->lsn_reset(strFile.c_str(), 0);
|
||||
if (!fMockDb && (ret = dbenv->lsn_reset(strFile.c_str(), 0)) != 0) {
|
||||
LogPrintf("CDBEnv::Flush: %s lsn_reset failed: %s\n", strFile, DbEnv::strerror(ret));
|
||||
fKeepLogs = true;
|
||||
}
|
||||
LogPrint("db", "CDBEnv::Flush: %s closed\n", strFile);
|
||||
mapFileUseCount.erase(mi++);
|
||||
} else
|
||||
@@ -511,10 +520,24 @@ void CDBEnv::Flush(bool fShutdown)
|
||||
if (fShutdown) {
|
||||
char** listp;
|
||||
if (mapFileUseCount.empty()) {
|
||||
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
|
||||
Close();
|
||||
if (!fMockDb)
|
||||
boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
|
||||
// After a failed flush, or once the environment has panicked (BDB0060, e.g. after a page
|
||||
// write failed), wallet.dat can be missing committed changes that only the logs hold.
|
||||
// Removing them would turn a wallet the next start recovers (DB_RECOVER) into one that
|
||||
// silently lost them. If the logs are unusable, the next start moves database/ aside.
|
||||
// (get_flags itself fails with DB_RUNRECOVERY once the environment has panicked.)
|
||||
u_int32_t envFlags = 0;
|
||||
if (dbenv->get_flags(&envFlags) != 0 || (envFlags & DB_PANIC_ENVIRONMENT))
|
||||
fKeepLogs = true;
|
||||
if (fKeepLogs && !fMockDb) {
|
||||
LogPrintf("CDBEnv::Flush: the wallet database was not flushed cleanly; keeping %s for recovery at the next start\n",
|
||||
(boost::filesystem::path(strPath) / "database").string());
|
||||
Close();
|
||||
} else {
|
||||
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
|
||||
Close();
|
||||
if (!fMockDb)
|
||||
boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user