diff options
| author | Bitflash <btf01@proton.me> | 2026-07-31 16:37:10 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-31 16:37:10 -0300 |
| commit | 417bfed08ded5c6afaed25f4ef724b5629b13fae (patch) | |
| tree | 611df88a86d7b73af6b3d65d05647e3f25c682d6 | |
| parent | 59777ee34a5ab6297684b7ab8d5d93facd4ad6f3 (diff) | |
db: close Berkeley DB cursors instead of leaving them to the handle (#71)
Three functions open a cursor and none of them close it: CTxDB::LoadBlockIndex,
CTxDB::ReadOwnerTxes and CWalletDB::LoadWallet.
Berkeley DB requires every cursor to be closed before the handle it came
from. ~CDB() calls Close(), which calls pdb->close() inside a catch-all
that swallows whatever it returns -- so the violation was real and silent,
which is this codebase's favourite kind.
An open cursor also holds read locks, and that is not theoretical here: the
first version of the startup chain repair (#61) opened a write transaction
beside the cursor LoadBlockIndex was still holding and waited on it
forever, coming up, printing its diagnosis and hanging with nothing in any
log. The repair had to be moved to the caller to get clear of it.
A scope guard rather than a close call at each return, because all three
functions have several exits and the point is to stop relying on anyone
remembering. Same fix Bitcoin made in c5c7911da.
Verified: node starts, verifies 288 blocks, loads the wallet, runs the
spent-flag rescan and syncs the live chain to 4422 with no Berkeley DB
error anywhere and db.log still empty.
| -rw-r--r-- | src/db.cpp | 36 |
1 files changed, 36 insertions, 0 deletions
| @@ -13,6 +13,39 @@ | |||
| 13 | // CDB | 13 | // CDB |
| 14 | // | 14 | // |
| 15 | 15 | ||
| 16 | // Closes a Berkeley DB cursor however the function exits. | ||
| 17 | // | ||
| 18 | // Three functions opened one and none of them ever closed it. Berkeley DB | ||
| 19 | // requires every cursor to be closed before the handle it came from, and | ||
| 20 | // ~CDB() calls Close(), which calls pdb->close() inside a catch-all that | ||
| 21 | // swallows whatever it returns. So the violation was real and silent. | ||
| 22 | // | ||
| 23 | // An open cursor also holds read locks. That is what made the first version of | ||
| 24 | // the startup chain repair (#61) hang: it opened a write transaction beside | ||
| 25 | // the cursor CTxDB::LoadBlockIndex was still holding, and waited on it forever | ||
| 26 | // with nothing in any log. The repair had to be moved to the caller. | ||
| 27 | // | ||
| 28 | // Each of these functions has several early returns, so a guard is safer than | ||
| 29 | // remembering to close on every one of them. Same fix Bitcoin made in | ||
| 30 | // c5c7911da, alongside its zombie-socket and double-close work. | ||
| 31 | class CAutoCursor | ||
| 32 | { | ||
| 33 | public: | ||
| 34 | explicit CAutoCursor(Dbc* pcursorIn) : pcursor(pcursorIn) { } | ||
| 35 | ~CAutoCursor() | ||
| 36 | { | ||
| 37 | if (pcursor) | ||
| 38 | { | ||
| 39 | try { pcursor->close(); } | ||
| 40 | catch (...) { } // closing on the way out must not throw | ||
| 41 | } | ||
| 42 | } | ||
| 43 | private: | ||
| 44 | Dbc* pcursor; | ||
| 45 | CAutoCursor(const CAutoCursor&); | ||
| 46 | void operator=(const CAutoCursor&); | ||
| 47 | }; | ||
| 48 | |||
| 16 | static CCriticalSection cs_db; | 49 | static CCriticalSection cs_db; |
| 17 | static bool fDbEnvInit = false; | 50 | static bool fDbEnvInit = false; |
| 18 | DbEnv dbenv(0u); | 51 | DbEnv dbenv(0u); |
| @@ -266,6 +299,7 @@ bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector<CTransaction>& | |||
| 266 | Dbc* pcursor = GetCursor(); | 299 | Dbc* pcursor = GetCursor(); |
| 267 | if (!pcursor) | 300 | if (!pcursor) |
| 268 | return false; | 301 | return false; |
| 302 | CAutoCursor cursorGuard(pcursor); | ||
| 269 | 303 | ||
| 270 | unsigned int fFlags = DB_SET_RANGE; | 304 | unsigned int fFlags = DB_SET_RANGE; |
| 271 | loop | 305 | loop |
| @@ -375,6 +409,7 @@ bool CTxDB::LoadBlockIndex() | |||
| 375 | Dbc* pcursor = GetCursor(); | 409 | Dbc* pcursor = GetCursor(); |
| 376 | if (!pcursor) | 410 | if (!pcursor) |
| 377 | return false; | 411 | return false; |
| 412 | CAutoCursor cursorGuard(pcursor); | ||
| 378 | 413 | ||
| 379 | unsigned int fFlags = DB_SET_RANGE; | 414 | unsigned int fFlags = DB_SET_RANGE; |
| 380 | loop | 415 | loop |
| @@ -544,6 +579,7 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | |||
| 544 | Dbc* pcursor = GetCursor(); | 579 | Dbc* pcursor = GetCursor(); |
| 545 | if (!pcursor) | 580 | if (!pcursor) |
| 546 | return false; | 581 | return false; |
| 582 | CAutoCursor cursorGuard(pcursor); | ||
| 547 | 583 | ||
| 548 | loop | 584 | loop |
| 549 | { | 585 | { |