wallet: Refactor the classes in wallet/db.{cpp/h} #18971

pull achow101 wants to merge 14 commits into bitcoin:master from achow101:refactor-storage changing 11 files +442 −322
  1. achow101 commented at 0:04 am on May 14, 2020: member

    The data storage layer of the wallet is fairly complicated and not well encapsulated or modularized. This makes it difficult for us to introduce new data storage methods. So this PR refactors it such that there is a clear way to introduce other storage options. This is also intended to clean up the logic of the database layer so that it is easier to follow and reason about.

    In the database, there are 3 classes: BerkeleyBatch, BerkeleyDatabase, and BerkeleyEnvironment. BerkeleyDatabase is typenamed to WalletDatabase. The goal is to introduce two abstract classes: WalletDatabase and DatabaseBatch. These will be implemented by BerkeleyDatabase and BerkeleyBatch. This abstract classes can be inherited by other storage methods. All of the database specific things should be hidden in those classes.

    To achieve this, we move the database handling things in BerkeleyBatch and turn it purely into a data accessor for the BerkeleyDatabase. Particularly, various static functions that operated on a BerkeleyDatabase are changed to be member functions of BerkeleyDatabase. Read, Write, Erase, and Exists are refactored to have separate functions that can be overridden by other classes. Instead of GetCursor returning a BDB specific Dbc object, new CreateCursor and CloseCursor functions are added and the cursor is handled internally within the BerkeleyBatch. We are then able to introduce the DatabaseBatch abstract class.

    Functionality is further moved out of BerkeleyBatch into BerkeleyDatabase. Notably the Db handle creation is moved from BerkeleyBatch’s constructor and to a new Open function in BerkeleyDatabase. BerkeleyDatabase will also handle closing with a new Close function instead of having this be part of Flush.

    Additionally, the existence of BerkeleyEnvironment is hidden inside of BerkeleyDatabase. mapFileUseCount is removed. Instead WalletDatabase will track it’s use count with m_refcount. This is incremented by newly introduced AddRef and RemoveRef functions. m_refcount is used to ensure that the database and environment are not closed while the database may still be in use.

    Instead of each BerkeleyEnvironment tracking the fileids of the databases in that environment, a global map g_fileids is used. Since we were already going through every open fileid for the uniqeness check, it doesn’t matter what BerkeleyEnvironment has a database with a particular fileid. All we care about is that there is a database in any of the environments that has the same fileid of the database that is currently being opened.

    Lastly, BerkeleyBatch, BerkeleyDatabase, and BerkeleyEnvironmentare moved into new fileswallet/bdb.{cpp/h}. WalletDatabaseandDatabaseBatchare inwallet/db.{cpp/h}`. This just gives better organization of the code so they aren’t all mixed together.


    I’ve started breaking this down into separate PRs. Some of the simpler stuff is moved up to the front so they can be merged first.

    • Mostly simple moveonly things: #19290
      • walletdb: Make SpliWalletFilePath non-static
      • walletdb: Add IsBDBWalletLoaded to look for BDB wallets specifically
      • walletdb: move IsWalletLoaded to walletdb.cpp
      • walletdb: moveonly: Move BerkeleyBatch Cursor and Txn funcs to cpp
      • walletdb: Move BDB specific things into bdb.{cpp/h}

    These PRs are independent of each other and can be merged out of order after #19290 is merged

    • Refactor Read, Write, Erase, Exists: #19292
      • walletdb: refactor Read, Write, Erase, and Exists into non-template func
    • Handle cursor internally: #19308
      • walletdb: Handle cursor internally
    • Make Create, CreateMock, and CreateDummy standalone: #19310
      • Add Create*WalletDatabase functions
      • scripted-diff: Replace WalletDatabase::Create* with CreateWalletDatabase
      • Remove WalletDatabase::Create, CreateMock, and CreateDummy
    • Move BerkeleyBatch static functions: #19324
      • walletdb: Combine VerifyDatabaseFile and VerifyEnvironment
      • walletdb: Move PeriodicFlush into WalletDatabase
      • walletdb: Move Rewrite into BerkeleyDatabase

    These PRs require the previous listed to be merged first and need to be merged in order

    • Introduce DatabaseBatch: #19325
      • walletdb: Refactor DatabaseBatch abstract class from BerkeleyBatch
      • walletdb: Add MakeBatch function to BerkeleyDatabase and use it
    • Introduce WalletDatabase: #19334
      • walletdb: Move BerkeleyDatabase::Flush(true) to Close()
      • walletdb: Introduce AddRef and RemoveRef functions
      • walletdb: Add BerkeleyDatabase::Open dummy function
      • walletdb: Introduce WalletDatabase abstract class
    • Cleanup the separation: #19335
      • walletdb: track database file use as m_refcount within BerkeleyDatabase
      • walletdb: Move Db->open to BerkeleyDatabase::Open
      • walletdb: Use a global g_fileids instead of m_fileids for each env
      • walletdb: Remove BerkeleyBatch friend class from BerkeleyDatabase

    I will continue to break this down as things get merged.

  2. fanquake added the label Wallet on May 14, 2020
  3. meshcollider commented at 0:08 am on May 14, 2020: contributor
    Concept ACK
  4. DrahtBot commented at 3:06 am on May 14, 2020: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #19325 (wallet: Refactor BerkeleyDatabase to introduce DatabaseBatch abstract class by achow101)
    • #19324 (wallet: Move BerkeleyBatch static functions to BerkeleyDatabase by achow101)
    • #19320 (wallet: Replace CDataStream& with Span where possible by MarcoFalke)
    • #19308 (wallet: BerkeleyBatch Handle cursor internally by achow101)
    • #18907 (walletdb: Don’t remove database transaction logs and instead error by achow101)
    • #18904 (Don’t call lsn_reset in periodic flush by bvbfan)
    • #18608 (refactor: Remove CAddressBookData::destdata by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  5. ryanofsky commented at 10:33 pm on May 14, 2020: member

    Strong concept ACK for the effort and end result, but I’m scared it looks like the intermediate commits here are moving and changing code at the same time instead of just leaving code where it is or moving it in trivial to review --color-moved MOVEONLY commits. This was previously a hurdle for me in #16341 (comment), but maybe the problem is less severe here because it looks like the moves are happening in single commits, not in harder to reconcile add and remove commits.

    I would definitely appreciate it (can’t speak for other reviewers) if this PR could be updated to use MOVEONLY commits (multiple small ones throughout or a big one at the beginning or end), but I’ll give this a try and maybe the review will be easier than I’m expecting 👍

  6. achow101 force-pushed on May 15, 2020
  7. achow101 force-pushed on May 15, 2020
  8. achow101 commented at 1:39 am on May 15, 2020: member

    but I’m scared it looks like the intermediate commits here are moving and changing code at the same time instead of just leaving code where it is or moving it in trivial to review

    I only found one commit which was moving and changing behavior at the same time. This was wallet: Move cursor functions to WalletDatabase and handle internally which I have now split into walletdb: Handle cursor internally (changes behavior) and walletdb: Move cursor functions to WalletDatabase (move).

  9. bvbfan commented at 1:01 pm on May 15, 2020: contributor
    The patch changes ref counting of db files. Now Acquire is called only in Rewrite since previous batch do it. Now ref count is always 0 (except you call Rewrite) which makes whole read/write racy.
  10. achow101 commented at 4:15 pm on May 15, 2020: member

    The patch changes ref counting of db files. Now Acquire is called only in Rewrite since previous batch do it. Now ref count is always 0 (except you call Rewrite) which makes whole read/write racy.

    Acquire is called by WalletBatch’s constructor so the ref count is still incremented every time the database is being used. And Release is called by WalletBatch’s destructor.

  11. in src/wallet/wallettool.cpp:113 in 0f991e7335 outdated
    104@@ -103,6 +105,207 @@ static void WalletShowInfo(CWallet* wallet_instance)
    105     tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
    106 }
    107 
    108+/* End of headers, beginning of key/value data */
    109+static const char *HEADER_END = "HEADER=END";
    110+/* End of key/value data */
    111+static const char *DATA_END = "DATA=END";
    112+
    113+static bool SalvageWallet(fs::path path)
    


    Empact commented at 7:44 pm on May 15, 2020:

    nit: const fs::path&

    in commit: d4a55140ea3e47ee6e4c93c484c7c72cfed741dd


    achow101 commented at 10:08 pm on May 15, 2020:
    This commit is part of #18918 so comments should be left there.
  12. in src/wallet/init.cpp:57 in 8ef1b45b4d outdated
    53@@ -54,7 +54,6 @@ void WalletInit::AddWalletOptions() const
    54     gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
    55                                                             CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
    56     gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
    57-    gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
    


    Empact commented at 7:46 pm on May 15, 2020:
    IMO “Attempt to recover private keys from a corrupt wallet” explains salvage more clearly than “Perform salvage operations on a wallet file”. I understand that’s specific to RecoverKeysOnlyFilter, but perhaps its possible to clarify the documentation?

    achow101 commented at 10:08 pm on May 15, 2020:
    This commit is part of #18918 so comments should be left there.
  13. Empact commented at 10:39 pm on May 15, 2020: member

    @achow101 re @ryanofsky’s comment, I set out to expand and clarify https://github.com/bitcoin/bitcoin/pull/18971/commits/67e30b2b42b4d710739e6fe303cbca69be68032c into several simpler move-only commits.

    You can use git diff 67e30b2b42b4d710739e6fe303cbca69be68032c 125796d8e60b8e740857e427cbaad4aa23f74b52 to compare the results, as there are some changes I’ve left out, and slight difference, e.g. I took the approach of exposing WalletBatch::ReadKeyValue, but they don’t affect the later commits.

    https://github.com/achow101/bitcoin/compare/refactor-storage...Empact:refactor-storage-moveonly

  14. achow101 commented at 11:55 pm on May 15, 2020: member
    @Empact Thanks for doing that! Unfortunately I worked out my own way of doing the separation before I saw your comment. My version was pushed to #18918.
  15. achow101 force-pushed on May 16, 2020
  16. bvbfan commented at 4:58 pm on May 17, 2020: contributor

    Acquire is called by WalletBatch’s constructor so the ref count is still incremented every time the database is being used. And Release is called by WalletBatch’s destructor.

    Got’cha, sorry about that, i’ve not notice it. I still have concern about touching db env in multi threading. BerkeleyDatabase should guard every env usage, for example in contructor with shared env, the easiest test is to call CreateWalletFromFile from distinct threads with same db location, it will crash at auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this)); before actual assert in next line.

  17. Sjors commented at 10:53 am on May 18, 2020: member
    Concept ACK
  18. achow101 force-pushed on May 18, 2020
  19. in src/wallet/walletdb.h:322 in 6c47d41779 outdated
    318+    }
    319+};
    320+
    321+//! Unserialize a give Key-Value pair and load it into the wallet and wallet scan state
    322+bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
    323+             CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet);
    


    jonatack commented at 4:05 pm on May 19, 2020:

    When building with clang –enable-debug -Werror I’m seeing the following compiler error:

    0./wallet/walletdb.h:322:112: error: member access into incomplete type 'CWallet'
    1             CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet);
    2                                                                                                               ^
    3./wallet/ismine.h:14:7: note: forward declaration of 'CWallet'
    4class CWallet;
    5      ^
    61 error generated.
    

    jonatack commented at 4:09 pm on May 19, 2020:
    (It may not be directly related to this PR; saw a similar build error in another wallet PR today.)
  20. jonatack commented at 4:06 pm on May 19, 2020: member
    Concept ACK. A quick scan of each commit looks reasonable. I was looking at this area of the codebase the other day with an eye to fixing the TODOs in tool_wallet.py and these changes look like the code would be better organised and easier to modify and reason about.
  21. achow101 force-pushed on May 19, 2020
  22. achow101 commented at 5:19 pm on May 21, 2020: member
    Not sure what’s causing the travis failure. It seems to be caused for a non-existent fileid when get_fileid is called, but I can’t figure out why that would be the case nor can I replicate it locally.
  23. achow101 force-pushed on May 21, 2020
  24. achow101 force-pushed on May 25, 2020
  25. bvbfan commented at 1:22 pm on May 26, 2020: contributor

    Not sure what’s causing the travis failure. It seems to be caused for a non-existent fileid when get_fileid is called, but I can’t figure out why that would be the case nor can I replicate it locally.

    get_fileid should not be called in mock db (some older bdb version in Travis?)

  26. achow101 force-pushed on May 26, 2020
  27. achow101 commented at 6:52 pm on May 26, 2020: member

    get_fileid should not be called in mock db

    Ah, that’s probably it. Thanks. Odd that I can’t get the error locally though.

    (some older bdb version in Travis?)

    It’s actually newer. Travis typically uses BDB provided by Ubuntu which is 5.3. I have 4.8 installed on my system.

  28. achow101 force-pushed on May 27, 2020
  29. bvbfan commented at 10:42 am on May 27, 2020: contributor

    It’s actually newer. Travis typically uses BDB provided by Ubuntu which is 5.3. I have 4.8 installed on my system.

    I can’t reproduce it either, bdb 5.3.28.

  30. in src/wallet/wallet.cpp:3714 in 289aa21c6a outdated
    3679@@ -3680,17 +3680,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
    3680 
    3681     // Keep same database environment instance across Verify/Recover calls below.
    3682     std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path);
    3683-
    3684-    try {
    3685-        if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
    3686-            return false;
    3687-        }
    3688-    } catch (const fs::filesystem_error& e) {
    


    ryanofsky commented at 3:26 pm on May 27, 2020:

    In commit “walletdb: Combine VerifyDatabaseFile and VerifyEnvironment” (289aa21c6a6c11ce172a92f878a3ede6652fc2e4)

    Here and below filesystem_error is no longer caught. Would suggest adding it back to preserve error handling behavior (I could be missing something but it looks like BerkeleyEnvironment::Open could still throw this error)


    achow101 commented at 8:48 pm on May 28, 2020:
    Added it back.
  31. in src/wallet/db.cpp:366 in d0ccd47c35 outdated
    350@@ -351,13 +351,26 @@ void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
    351 
    352 BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr)
    353 {
    354+    database.Open(pszMode);
    355     fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
    356     fFlushOnClose = fFlushOnCloseIn;
    357     env = database.env.get();
    358-    if (database.IsDummy()) {
    


    ryanofsky commented at 3:46 pm on May 27, 2020:

    In commit “walletdb: Move Db->open to BerkeleyDatabase::Open” (d0ccd47c355319ec9709c52cdcdf0be78a26ac25)

    Note: seems like there two small changes to behavior here when IsDummy is true: CLIENT_VERSION is written and strFile member is set, but both changes seem good.

  32. ryanofsky commented at 3:48 pm on May 27, 2020: member

    Started review (will update list below with progress). Nice cleanups so far

    • e9dff450d65983644f5c60e1c6aa1f65ade5619e walletdb: Combine VerifyDatabaseFile and VerifyEnvironment (1/20)
    • e1c46d4de66d57a703a9aa1b4d1b2bd65e182445 walletdb: Move PeriodicFlush into WalletDatabase (2/20)
    • 3cdb7fd9d1e92f07c04c325c9b585ff736162198 walletdb: Move Db->open to BerkeleyDatabase::Open (3/20)
    • b6a6e0292c226f67b9ee177f9732a936aa7a0d7e walletdb: Use a global g_fileids instead of m_fileids for each env (4/20)
    • 8726d373e165087789933eb03383dd7feee31d3f walletdb: Add BerkeleyDatabase::Release (5/20)
    • 2c663e453104b2f49849d8bc80727e98abda8055 walletdb: Move log consolidation into BerkeleyEnvironment::Close (6/20)
    • f3b553f36e83c44a4edb51710ae673d6b9b3e573 walletdb: Change BerkeleyDatabase::Flush to Close (7/20)
    • 9603e233c9c4049a267282615b5e4f31600656e6 walletdb: track database file use as m_refcount within BerkeleyDatabase (8/20)
    • 17009d1e73ce894f9f5d5441c472e87efaee6163 walletdb: Replace BerkeleyDatabase::Flush with BerkeleyBatch::Flush (9/20)
    • 2711caa2a2d4f04b848c25b614b353a1a08351b0 walletdb: Handle cursor internally (10/20)
    • 7d8b1b42702357d5c25e5825dfc47326bad0568f walletdb: Move cursor functions to WalletDatabase (11/20)
    • 75f3532a70abf4331aba212f979a2e8be02d191c walletdb: Move Rewrite into BerkeleyDatabase (12/20)
    • e1ebc69a5e6628cadf480dfd6d787a6c9f631b86 walletdb: Move Txn* functions into WalletDatabase (13/20)
    • 7f6608ba7ac54935ddebec9b49817fc4a26e749e walletdb: Move Read, Write, Erase, and Exists into BerkeleyDatabase (14/20)
    • 64a5a29d692378df443ae599b81c1f4547ec3d03 walletdb: Remove BerkeleyBatch and have WalletBatch use WalletDatabase (15/20)
    • aac3483e9e0423b3414cfaf84f37041a200d773c walletdb: refactor Read, Write, Erase, and Exists into non-template func (16/20)
    • 6091f36d30f1fd9feec4bef6677ce514b5d20b2c walletdb: Move BerkeleyDatabase::Create, CreateDummy, and CreateMock out (17/20)
    • 4c6c9080d48ec5810e7d36348f7c730a586e9fb5 walletdb: Make WalletDatabase abstract class (18/20)
    • d858c9b23699edaa27166071fc0c88f55911fea8 wallet: Add IsBDBWalletLoaded to look for BDB wallets specifically (19/20)
    • b4c2765fd19e5dd8c823febd65ad5e62a58c8260 Move BDB specific things into bdb.{cpp/h} (20/20)
  33. laanwj added this to the "Blockers" column in a project

  34. achow101 force-pushed on May 28, 2020
  35. in src/wallet/db.cpp:425 in 79fe2edc44 outdated
    405-            // more ideally, an exclusive lock for accessing the database could
    406-            // be implemented, so no equality checks are needed at all. (Newer
    407-            // versions of BDB have an set_lk_exclusive method for this
    408-            // purpose, but the older version we use does not.)
    409-            for (const auto& env : g_dbenvs) {
    410-                CheckUniqueFileid(*env.second.lock().get(), strFile, *pdb_temp, this->env->m_fileids[strFile]);
    


    ryanofsky commented at 6:22 pm on May 29, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (79fe2edc4457ee70754b08d5e8d2309b86038395)

    Note: Was looking into history of why the previous code was calling CheckUniqueFileid in a loop instead of doing a map lookup. Looks like it was done to keep changes minimal in https://github.com/bitcoin/bitcoin/pull/11687/commits/d8a99f65e53019becdd8d2631396012bafb29741 and avoid needing to change CheckUniqueFileid. Looping or mapping was never needed before that commit because only one environment at a time could be opened.

  36. in src/wallet/db.cpp:30 in 79fe2edc44 outdated
    41-            throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
    42-                HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
    43-        }
    44-    }
    45-}
    46+std::unordered_map<std::string, WalletDatabaseFileId> g_fileids;
    


    ryanofsky commented at 6:33 pm on May 29, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (79fe2edc4457ee70754b08d5e8d2309b86038395)

    Would be good to add GUARDED_BY(cs_db) if possible


    achow101 commented at 7:16 pm on June 1, 2020:
    Done
  37. in src/wallet/db.cpp:420 in 79fe2edc44 outdated
    433+                    if (fileid == item.second && item.first != m_file_path) {
    434+                        throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (duplicates fileid %s from %s)", strFile,
    435+                            HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
    436+                    }
    437+                }
    438+                g_fileids[m_file_path] = fileid;
    


    ryanofsky commented at 6:40 pm on May 29, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (79fe2edc4457ee70754b08d5e8d2309b86038395)

    This is just a style comment, but there’s already a lot going on in this method, and it might be nice to move this long comment and block of code out to a void AddUniqueFileId(Db& db, const std::string& path) helper function that gets file id, checks it and adds it to the map


    achow101 commented at 7:16 pm on June 1, 2020:
    Done
  38. in src/wallet/db.h:149 in bd13bec012 outdated
    142@@ -143,9 +143,12 @@ class BerkeleyDatabase
    143         return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
    144     }
    145 
    146-    /** Open the database if it is not already opened */
    147+    /** Open the database if it is not already opened. Increments mapFileUseCount */
    148     void Open(const char* mode);
    149 
    150+    /** Indicate that database user has stopped using the database. Decrement mapFileUseCount */
    


    ryanofsky commented at 6:58 pm on May 29, 2020:

    In commit “walletdb: Add BerkeleyDatabase::Release” (bd13bec012a56447e0dae9e46ba3693bcc2b561d)

    Note: Maybe this comment could be extended to say how Release should be used. The commit message has a longer comment, but I’m not sure I understand it. Particularly: “Flush can be done before Release, and Close after.” I don’t see how calling close after release could be safe because it it would decrement the use count twice. Is the idea that caller should do something like Open Change Release, Open Change Release, Open Change Close? It’d be good to have some kind of RAII WalletBatch type class to force correct usage, if something like this isn’t already added later


    achow101 commented at 6:28 pm on June 1, 2020:

    I think you are confusing BerkeleyBatch and BerkeleyDatabase. Release is called by BerkeleyBatch::Close, not BerkeleyDatabase::Close.

    In a later commit, a corresponding Acquire function is added. In this commit, the thing that Acquire does is part of Open. The idea with the refcount is thusly: when something tries to use the database, it increments the refcount. When it is done, it decrements the refcount. The goal is to prevent the database from closing while something is still using the database. When WalletBatch is initialized, it calls BerkeleyDatabase::Open via BerkeleyBatch’s constructor. When WalletBatch is done and no longer being used, it calls BerkeleyBatch::Close which calls BerkeleyDatabase::Release. Then both the BerkeleyBatch and WalletBatch are destructed. The BerkeleyDatabase remains open so that future uses of the database don’t need to reopen it and for other concurrent WalletBatchs to do their work.

    So the flow with BerkeleyDatabase is Open, changes, Release, Close.


    ryanofsky commented at 4:58 pm on June 8, 2020:

    In commit “walletdb: Add BerkeleyDatabase::Release” (8726d373e165087789933eb03383dd7feee31d3f)

    re: #18971 (review)

    So the flow with BerkeleyDatabase is Open, changes, Release, Close.

    Thank you! I was missing the idea that Release wouldn’t actively close anything, just lazily indicate that it could be closed.

    Maybe just add “and that it could be flushed or closed” to the first sentence in the comment. I might also rename Acquire/Release to AddRef/RemoveRef to suggest that these are accounting functions and not functions that actually acquire or release resources.


    achow101 commented at 6:57 pm on June 9, 2020:
    Done
  39. in src/wallet/db.cpp:365 in 1dbc24d27d outdated
    360+    Close();
    361+    if (env) {
    362+        LOCK(cs_db);
    363+        size_t erased = env->m_databases.erase(strFile);
    364+        assert(erased == 1);
    365+        g_dbenvs.erase(env->Directory().string());
    


    ryanofsky commented at 7:18 pm on May 29, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (1dbc24d27d4b54b45c6b49ba41b4f64d0093dd71)

    Why is this calling g_dbenvs.erase? It seems like it is redundant with env = nullptr assignment below which should trigger erasing in BerkeleyEnvironment::~BerkeleyEnvironment destructor. It also seems unsafe if there’s another database open in the same environment.


    achow101 commented at 6:39 pm on June 1, 2020:

    Not entirely sure, but this is moved code.

    This was originally called during BerkeleyDatabase::Flush(true), i.e. when the database and environment are being closed. So I moved it into the destructor to go along with the other environment closing changes.


    ryanofsky commented at 5:01 pm on June 8, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (f3b553f36e83c44a4edb51710ae673d6b9b3e573)

    re: #18971 (review)

    Not entirely sure, but this is moved code.

    This was originally called during BerkeleyDatabase::Flush(true), i.e. when the database and environment are being closed. So I moved it into the destructor to go along with the other environment closing changes.

    Thanks for explaining. It looks like the previous code has the same bug and could also inappropriately erase a g_dbenvs entry for an environment with multiple open databases. However, the previous code was less likely to cause problems, because it only ran when the whole node was shutting down (fShutdown=true). Now that this code runs not just when completely shutting down (e.g. it also runs during unloadwallet calls), it could cause new corruption issues if a g_dbenvs entry is missing and there’s a later loadwallet call.

    So I think you want to drop g_dbenvs.erase() here and just let the BerkeleyEnvironment destructor do the erasing. I think you could drop the env = nullptr here too.


    achow101 commented at 6:57 pm on June 9, 2020:
    Done
  40. in src/wallet/db.cpp:637 in 1dbc24d27d outdated
    636@@ -619,17 +637,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
    637             } else
    638                 mi++;
    639         }
    640-        LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
    641-        if (fShutdown) {
    


    ryanofsky commented at 7:28 pm on May 29, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (1dbc24d27d4b54b45c6b49ba41b4f64d0093dd71)

    Q: If there are multiple databases open in the same environment, previously logs would be consolidated and removed if any database was closed and no other database was writing, but now it will not consolidate logs until the last database in the environment is closed? This seems fine, but it would be good if the commit message was clearer about what the exact change in behavior is since this doesn’t seem purely like a refactoring.

    Also, maybe the commit could be split up. It seems like log consolidation code moving from here to the environment destructor could be done independently of the flush/close api change. I could be missing the connection, though.


    achow101 commented at 6:55 pm on June 1, 2020:

    Q: If there are multiple databases open in the same environment, previously logs would be consolidated and removed if any database was closed and no other database was writing, but now it will not consolidate logs until the last database in the environment is closed?

    I don’t think that’s what happened previously either. Flush(true) seems to have been only called during a bitcoind shutdown, not during typical wallet unloading. So previously, even after a wallet was unloaded, it’s log files were never consolidated and removed. So this behavior ends up being retained. I don’t think there is a behavior change here, although the it is difficult to work out exactly when the database and environment are actually being flushed and closed.


    achow101 commented at 7:17 pm on June 1, 2020:
    Also split this commit as you suggested.

    ryanofsky commented at 5:01 pm on June 8, 2020:

    In commit “walletdb: Move log consolidation into BerkeleyEnvironment::Close” (2c663e453104b2f49849d8bc80727e98abda8055)

    re: #18971 (review)

    I don’t think there is a behavior change here, although the it is difficult to work out exactly when the database and environment are actually being flushed and closed.

    Thanks I missed that Flush(true) was wasn’t called on unload.

    I think it’s good if the commit message at least says what the change intends without getting into details, so reviewers have something to check and so we know if changes are going in a consistent direction.

    In this case would suggest adding something like: “With this change, logs are now consolidated whenever the last database in an environment is closed (e.g. a in unloadwallet RPC call), instead of only during node shutdown and wallet encryption.” to commit message for “walletdb: Move log consolidation into BerkeleyEnvironment::Close” (2c663e453104b2f49849d8bc80727e98abda8055).


    achow101 commented at 6:57 pm on June 9, 2020:
    Done
  41. in src/wallet/db.cpp:735 in 1dbc24d27d outdated
    744-            // pointers, or else separate the database and environment shutdowns so
    745-            // environments can be shut down after databases.
    746-            g_fileids.erase(m_file_path);
    747-        }
    748+        env->Flush();
    749+        // TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the
    


    ryanofsky commented at 7:36 pm on May 29, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (1dbc24d27d4b54b45c6b49ba41b4f64d0093dd71)

    Todo was actually already implemented and should be dropped. It was added in 2d796faf62095e83f74337c26e7e1a8c3957cf3c #14320 (review) and implemented in f1f4bb7345b90853ec5037478173601035593d26 #11911


    achow101 commented at 7:16 pm on June 1, 2020:
    Removed it
  42. in src/wallet/db.cpp:663 in 9cfb631f80 outdated
    675 
    676-                env->mapFileUseCount.erase(mi++);
    677-                LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
    678-                ret = true;
    679-            }
    680+            m_refcount = 0;
    


    ryanofsky commented at 7:53 pm on May 29, 2020:

    In commit “walletdb: track database file use as m_refcount within BerkeleyDatabase” (9cfb631f808aa6ccb10ab31d4b8325bd940a8916)

    What’s this doing? It seems like either this should already be 0 or it would be unsafe to overwrite if not already 0


    achow101 commented at 7:16 pm on June 1, 2020:
    Removed
  43. in src/wallet/db.cpp:534 in 9cfb631f80 outdated
    528@@ -531,11 +529,10 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    529     while (true) {
    530         {
    531             LOCK(cs_db);
    532-            if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
    533+            if (database.m_refcount == 0) {
    534                 // Flush log data to the dat file
    535                 env->CloseDb(strFile);
    536                 env->CheckpointLSN(strFile);
    537-                env->mapFileUseCount.erase(strFile);
    


    ryanofsky commented at 8:01 pm on May 29, 2020:

    In commit “walletdb: track database file use as m_refcount within BerkeleyDatabase” (9cfb631f808aa6ccb10ab31d4b8325bd940a8916)

    Note: seems this commit drops distinction between use count being 0 and use count not existing (no map entry). I think 0 use count meant the database wasn’t in use but wasn’t flushed yet, and no use count meant it was flushed. I think the last piece of code which used this distinction was the log_archive code removed in the previous commit 1dbc24d27d4b54b45c6b49ba41b4f64d0093dd71

  44. ryanofsky commented at 8:09 pm on May 29, 2020: member
    Updated #18971#pullrequestreview-419340191
  45. in src/wallet/wallet.cpp:3719 in 4decfab19f outdated
    3689     } catch (const fs::filesystem_error& e) {
    3690         error_string = Untranslated(strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e)));
    3691         return false;
    3692     }
    3693-
    3694-    return WalletBatch::VerifyDatabaseFile(wallet_path, error_string);
    


    Empact commented at 9:49 pm on May 31, 2020:
    nit: assert as unreachable?

    achow101 commented at 7:17 pm on June 1, 2020:
    Done
  46. in src/wallet/wallettool.cpp:145 in f06619ecd4 outdated
    141@@ -141,10 +142,14 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
    142             return false;
    143         }
    144         bilingual_str error;
    145-        if (!WalletBatch::VerifyEnvironment(path, error)) {
    146+        std::vector<bilingual_str> warnings;
    


    Empact commented at 0:16 am on June 1, 2020:
    nit: this is unused

    achow101 commented at 7:17 pm on June 1, 2020:
    Removed
  47. achow101 force-pushed on Jun 1, 2020
  48. achow101 force-pushed on Jun 2, 2020
  49. DrahtBot added the label Needs rebase on Jun 2, 2020
  50. achow101 force-pushed on Jun 2, 2020
  51. DrahtBot removed the label Needs rebase on Jun 2, 2020
  52. in src/wallet/db.cpp:368 in 3cdb7fd9d1 outdated
    364+    }
    365+}
    366+
    367+void BerkeleyDatabase::Open(const char* pszMode)
    368+{
    369+    m_read_only = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
    


    Sjors commented at 7:03 pm on June 2, 2020:
    In 3cdb7fd9d1e92f07c04c325c9b585ff736162198: it’s a bit odd that you’re introducing m_read_only here because it’s not read anywhere yet. 64a5a29d692378df443ae599b81c1f4547ec3d03 seems a better place to introduce it.

    achow101 commented at 3:08 pm on June 9, 2020:
    I’d like to keep it here instead of removing and re-introducing this line.
  53. in src/wallet/db.cpp:398 in 3cdb7fd9d1 outdated
    396 
    397             ret = pdb_temp->open(nullptr,                             // Txn pointer
    398-                            fMockDb ? nullptr : strFilename.c_str(),  // Filename
    399-                            fMockDb ? strFilename.c_str() : "main",   // Logical db name
    400+                            fMockDb ? nullptr : strFile.c_str(),  // Filename
    401+                            fMockDb ? strFile.c_str() : "main",   // Logical db name
    


    Sjors commented at 7:37 pm on June 2, 2020:
    3cdb7fd9d1e92f07c04c325c9b585ff736162198 nit: rename breaks comment indent (it’s still broken in bdb.cpp after the move)

    achow101 commented at 6:56 pm on June 9, 2020:
    Fixed
  54. in src/wallet/db.cpp:358 in 3cdb7fd9d1 outdated
    354     fFlushOnClose = fFlushOnCloseIn;
    355     env = database.env.get();
    356-    if (database.IsDummy()) {
    357+    pdb = database.m_db.get();
    358+    strFile = database.strFile;
    359+    if (strchr(pszMode, 'c') != nullptr && !Exists(std::string("version"))) {
    


    Sjors commented at 7:41 pm on June 2, 2020:
    3cdb7fd9d1e92f07c04c325c9b585ff736162198 nit: the fCreate helper variable makes this more readable. It’s introduced in a later commit, but might as well do it here.

    achow101 commented at 6:56 pm on June 9, 2020:
    Done
  55. Sjors commented at 7:48 pm on June 2, 2020: member
    Reviewed the first couple of commits up to 3cdb7fd9d1e92f07c04c325c9b585ff736162198, will resume later.
  56. in src/wallet/db.cpp:31 in b6a6e0292c outdated
    32+std::unordered_map<std::string, WalletDatabaseFileId> g_fileids GUARDED_BY(cs_db);
    33 
    34-    int ret = db.get_mpf()->get_fileid(fileid.value);
    35-    if (ret != 0) {
    36-        throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
    37+static void AddUniqueFileId(Db& db, const std::string& path, const std::string& filename)
    


    ryanofsky commented at 2:15 pm on June 8, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (b6a6e0292c226f67b9ee177f9732a936aa7a0d7e)

    Minor: it would be better to drop filename argument and just print path value instead in all the error messages below currently using filename. When this code was originally written, there was only one environment so it made sense to print just the filename without the environment path. But now that -wallet values are usually environment paths, not filenames in a shared environment, filename isn’t descriptive or unique anymore.


    achow101 commented at 6:56 pm on June 9, 2020:
    Done
  57. in src/wallet/db.cpp:36 in b6a6e0292c outdated
    37+static void AddUniqueFileId(Db& db, const std::string& path, const std::string& filename)
    38+{
    39+    LOCK(cs_db);
    40+    // Check that the BDB file id has not already been loaded in any BDB environment
    41+    // to avoid BDB data consistency bugs that happen when different data
    42+    // files in the same environment have the same fileid. All BDB environments are
    


    ryanofsky commented at 2:52 pm on June 8, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (b6a6e0292c226f67b9ee177f9732a936aa7a0d7e)

    Minor: It would be good to restore the new paragraph break before “All BDB environments” and replace “All BDB environments are checked to prevent…” with “In addition to disallowing opening databases with the same fileid in the same environment, also disallow opening databases with the same fileid in other environments to prevent…”

    The first sentence here is talking about a necessary fix (from #11476) for a BDB corruption issue. The second sentence and rest of the paragraph is talking about a different, clumsy workaround (from #11687#event-1366258575) to try to prevent bitcoin from reopening an already opened wallet, which it would be better to remove and replace with some type of exclusive lock


    achow101 commented at 6:56 pm on June 9, 2020:
    Done
  58. in src/wallet/db.cpp:448 in 9603e233c9 outdated
    443@@ -445,7 +444,6 @@ void BerkeleyDatabase::Open(const char* pszMode)
    444             m_db.reset(pdb_temp.release());
    445 
    446         }
    447-        ++env->mapFileUseCount[strFile];
    


    ryanofsky commented at 4:49 pm on June 8, 2020:

    In commit “walletdb: track database file use as m_refcount within BerkeleyDatabase” (9603e233c9c4049a267282615b5e4f31600656e6)

    Note: Line is moved outside this function to the only caller, BerkeleyBatch::BerkeleyBatch

  59. in src/wallet/wallet.cpp:436 in f3b553f36e outdated
    429@@ -430,7 +430,12 @@ bool CWallet::HasWalletSpend(const uint256& txid) const
    430 
    431 void CWallet::Flush(bool shutdown)
    432 {
    433-    database->Flush(shutdown);
    434+    if (database) {
    435+        database->Close();
    436+        if (shutdown) {
    437+            database.reset();
    


    ryanofsky commented at 7:14 pm on June 8, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (f3b553f36e83c44a4edb51710ae673d6b9b3e573)

    I don’t think it makes sense to add this new database.reset() here. The only two places calling this function with shutdown=true are immediately deleting the whole CWallet[1][2], so having this condition and branch just seems like adding complexity for no reason. If there is a reason why we want a CWallet instance with a null database pointer, saying what that reason is in a comment on the database.reset() call here would be helpful.

    [1] https://github.com/bitcoin/bitcoin/blob/f3b553f36e83c44a4edb51710ae673d6b9b3e573/src/wallet/wallet.cpp#L116-L117 [2] https://github.com/bitcoin/bitcoin/blob/f3b553f36e83c44a4edb51710ae673d6b9b3e573/src/init.cpp#L284-L296


    achow101 commented at 6:57 pm on June 9, 2020:
    Removed.
  60. in src/wallet/db.cpp:746 in b6a6e0292c outdated
    741@@ -740,7 +742,8 @@ void BerkeleyDatabase::Flush(bool shutdown)
    742             // environment, should replace raw database `env` pointers with shared or weak
    743             // pointers, or else separate the database and environment shutdowns so
    744             // environments can be shut down after databases.
    745-            env->m_fileids.erase(strFile);
    746+            LOCK(cs_db);
    747+            g_fileids.erase(m_file_path);
    


    ryanofsky commented at 8:20 pm on June 8, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (b6a6e0292c226f67b9ee177f9732a936aa7a0d7e)

    This isn’t actually the right place to be erasing the fileid entry. A Fileid entry is created when a database is opened (when BerkeleyDatabase::m_db is set in BerkeleyDatabase::Open) and it should be erased when the database is closed (when BerkeleyDatabase::m_db is deleted in BerkeleyEnvironment::CloseDb and BerkeleyEnvironment::CloseDb).

    So I think this whole else block should be dropped and g_fileids.erase calls just added the two places where m_db.reset() is called.

    It looks like this code mostly worked before because the fileids.erase here was usually paired with a CloseDb call inside the env->Flush(shutdown) line above calling m_db.reset(). But with bad timing, a g_fileids entry could be erased without a database close, resulting in missing “duplicates fileid” errors, and a database could be closed without deleting a g_fileids entry resulting in spurious “duplicates fileid” errors. Better not to carry this strangeness forward, and simply pair all m_db and g_fileids updates together.


    achow101 commented at 6:59 pm on June 9, 2020:
    This can’t be moved into CloseDb. However I have moved it into BerkeleyEnvironment::Close.
  61. in src/wallet/db.cpp:393 in f3b553f36e outdated
    388+{
    389+    Close();
    390+    if (env) {
    391+        LOCK(cs_db);
    392+        size_t erased = env->m_databases.erase(strFile);
    393+        assert(erased == 1);
    


    ryanofsky commented at 9:31 pm on June 8, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (f3b553f36e83c44a4edb51710ae673d6b9b3e573)

    Could you add an assert(!m_db); here as well? The Close() call above should be properly closing the database but this depends on a big strand of spaghetti that an assert might prevent getting chewed up in the future.


    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  62. in src/wallet/db.cpp:372 in 9603e233c9 outdated
    368@@ -371,6 +369,7 @@ void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
    369 BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_database(database)
    370 {
    371     database.Open(pszMode);
    372+    database.Acquire();
    


    ryanofsky commented at 9:47 pm on June 8, 2020:

    In commit “walletdb: track database file use as m_refcount within BerkeleyDatabase” (9603e233c9c4049a267282615b5e4f31600656e6)

    I think you need to call Acquire() here before Open() not after. Now that the reference count is no longer protected by the cs_db mutex there is a race condition where rewrite/flush/backup functions could start running between the Open and Acquire calls here and and start rewriting/flushing/backing up while this batch is active.


    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  63. in src/wallet/db.cpp:743 in 9603e233c9 outdated
    739@@ -755,9 +740,11 @@ std::string BerkeleyDatabaseVersion()
    740 
    741 void BerkeleyDatabase::Release()
    742 {
    743-    {
    744-        LOCK(cs_db);
    745-        --env->mapFileUseCount[strFile];
    746-    }
    747+    m_refcount--;
    


    ryanofsky commented at 10:07 pm on June 8, 2020:

    In commit “walletdb: track database file use as m_refcount within BerkeleyDatabase” (9603e233c9c4049a267282615b5e4f31600656e6)

    Even though m_refcount is atomic you still have to lock cs_db while updating it, otherwise the m_db_in_use.notify_all() call may result in a lost wakeup and infinite hang if the m_db_in_use.wait() lambda happens to run at the same time m_refcount is decremented and return false (see https://stackoverflow.com/a/41875321 or https://github.com/isocpp/CppCoreGuidelines/issues/554).


    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  64. in src/wallet/db.cpp:553 in 2711caa2a2 outdated
    549@@ -550,17 +550,17 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    550                         fSuccess = false;
    551                     }
    552 
    553-                    Dbc* pcursor = db.GetCursor();
    554-                    if (pcursor)
    555+                    if (db.CreateCursor())
    


    ryanofsky commented at 10:37 pm on June 8, 2020:

    In commit “walletdb: Handle cursor internally” (2711caa2a2d4f04b848c25b614b353a1a08351b0)

    Behavior isn’t changing in this commit, but error handling here is broken, right? This should be returning failure if creating a cursor doesn’t succeed, not just pretending it succeeded and not reading anything.


    achow101 commented at 6:13 pm on June 9, 2020:
    Yes, I think error handling is broken here. I think the intent was to ignore an error and just keep trying until it succeeds. But there’s nothing to actually have the outer while loop keep going.
  65. in src/wallet/db.h:340 in 2711caa2a2 outdated
    339         if (!pdb)
    340-            return nullptr;
    341-        Dbc* pcursor = nullptr;
    342-        int ret = pdb->cursor(nullptr, &pcursor, 0);
    343+            return false;
    344+        m_cursor = nullptr;
    


    ryanofsky commented at 10:39 pm on June 8, 2020:

    In commit “walletdb: Handle cursor internally” (2711caa2a2d4f04b848c25b614b353a1a08351b0)

    I think this needs to either return false if m_cursor is not null or assert that it is null, otherwise calling this at the wrong time will silently leak memory


    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  66. ryanofsky commented at 10:54 pm on June 8, 2020: member
    Updated #18971#pullrequestreview-419340191
  67. in src/wallet/db.cpp:538 in 75f3532a70 outdated
    533@@ -536,7 +534,8 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    534                 LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
    535                 std::string strFileRes = strFile + ".rewrite";
    536                 { // surround usage of db with extra {}
    537-                    BerkeleyBatch db(database, "r");
    538+                    Open("r");
    539+                    Acquire();
    


    ryanofsky commented at 10:33 am on June 9, 2020:

    In commit “walletdb: Move Rewrite into BerkeleyDatabase” (75f3532a70abf4331aba212f979a2e8be02d191c)

    I think Acquire and Release calls here should be dropped because they are misleading and don’t do anything and add extra potential for leaks and bugs. Acquire/Release are used for shared access to a database, but this is doing exclusive access, with cs_db locked the whole time. It might be good to have a comment though.

    0// Not calling Acquire/Release here because this is an exclusive database
    1// operation done with cs_db locked the entire time.
    2Open("r"); 
    

    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  68. in src/wallet/db.cpp:540 in 75f3532a70 outdated
    533@@ -536,7 +534,8 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    534                 LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
    535                 std::string strFileRes = strFile + ".rewrite";
    536                 { // surround usage of db with extra {}
    537-                    BerkeleyBatch db(database, "r");
    


    ryanofsky commented at 10:34 am on June 9, 2020:

    In commit “walletdb: Move Rewrite into BerkeleyDatabase” (75f3532a70abf4331aba212f979a2e8be02d191c)

    Should remove comment previous line referring to db now outdated


    achow101 commented at 6:59 pm on June 9, 2020:
    Done
  69. in src/wallet/db.cpp:710 in e1ebc69a5e outdated
    705@@ -706,6 +706,10 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) const
    706 
    707 void BerkeleyDatabase::Close()
    708 {
    709+    if (m_active_txn) {
    710+        m_active_txn->abort();
    


    ryanofsky commented at 10:54 am on June 9, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (e1ebc69a5e6628cadf480dfd6d787a6c9f631b86)

    This seems bad. If there’s an ongoing transaction and the database is closed, this should raise or return some error, not silently throw away data


    ryanofsky commented at 7:44 pm on June 12, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (e1ebc69)

    re: #18971 (review)

    I still don’t think it is safe to silently discard data when a database is closed. Suggest assert(m_active_txn) or adding other error handling, or moving active transaction back to the batch object for the batch destructor to scope properly

  70. in src/wallet/db.cpp:751 in e1ebc69a5e outdated
    746@@ -739,6 +747,10 @@ std::string BerkeleyDatabaseVersion()
    747 
    748 void BerkeleyDatabase::Release()
    749 {
    750+    if (m_active_txn) {
    751+       m_active_txn->abort();
    


    ryanofsky commented at 11:15 am on June 9, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (e1ebc69a5e6628cadf480dfd6d787a6c9f631b86)

    This might do more damage than the abort call in Close(). I don’t think there’s any point to having a shared reference count if one user of the database can silently abort a transaction created by a different user of the database.

    I’m skeptical of the database having these new Dbc* m_cursor and DbTxn* m_active_txn members. I suspect you’re putting these in the Database class because you don’t want to have more than one class with virtual methods, but it seems to me these really belong in the batch object, not the database object. It’s not coherent to have a shared reference count but only allow a single cursor and a single transaction at a time, and have different batches abort each others transactions and step on each others cursors.

    I’m kind of surprised this code works at all. I suspect it does because wallet only makes limited use of transactions and cursors (since it loads most of the database into memory on startup) and our python tests don’t call RPC methods in parallel, so the database is usually accessed from a single thread.


    ryanofsky commented at 2:48 pm on June 9, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (e1ebc69)

    re: #18971 (review)

    It’s not coherent to have a shared reference count but only allow a single cursor and a single transaction at a time, and have different batches abort each others transactions and step on each others cursors.

    I should be more specific about how I think this could be resolved. I think there are two ways:

    1. Add a bunch of asserts to ensure the new shared cursors and transactions aren’t misused. assert(!m_active_txn) in TxnBegin and Close and Release, assert(m_active_txn) in TxnCommit, TxnAbort, Read, Write, etc. assert(!m_cursor) in CreateCursor, etc.

    2. Don’t add shared cursors and transactions. Keep the methods where they are currently in BerkeleyBatch instead of moving them to WalletDatabase. Just make them them virtual overrides and have BerkeleyBatch inherit from a DatabaseBatch class. Give WalletDatabase a virtual MakeBatch method returning unique_ptr<DatabaseBatch> and have WalletBatch call it during construction and use the pointer as needed. You have two classes with virtual methods instead of one class with virtual methods, but less code moves around and the classes have clear responsibilities.

    Another variants of the second approach are also possible like giving WalletBatch itself virtual methods, but the basic idea’s the same.


    achow101 commented at 6:28 pm on June 9, 2020:
    Transactions are only used during encryption. Cursors are only used by loading, rewriting, and zapwallettx.

    achow101 commented at 8:45 pm on June 9, 2020:

    I’ve decided to go with option 1 and added asserts to CreateCursor and TxnBegin.

    I don’t think it makes much sense to support multiple transactions and cursors because of our extremely limited usage of them. Transactions are only used when encrypting a wallet. Cursors are used in scenarios where there is only a single thread accessing the database already. These are in loading (both load and zapwallettx) and rewrite, which is encryption. So there won’t be concurrent cursors nor concurrent transactions. Even with multiple parallel RPCs, we use locking during encryption to enforce single access, and during loading, the wallet isn’t setup yet to even have multiple accesses.

    Additionally, in SQLite, there aren’t such a thing as multiple transactions, so having a separate `Batch/ class would make that more difficult.


    ryanofsky commented at 7:48 pm on June 12, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (0e96b5b77c33275d287f5c33e77f87ab3f4ac868)

    re: #18971 (review)

    I don’t think it is safe to silently discard data when a reference count is decremented. Suggest assert(m_active_txn) or adding other error handling, or moving active transaction back to the batch object for the batch destructor to scope properly


    ryanofsky commented at 10:26 pm on June 12, 2020:

    re: #18971 (review)

    Additionally, in SQLite, there aren’t such a thing as multiple transactions, so having a separate `Batch/ class would make that more difficult.

    RIGHT. :sweat_smile: Finally I understand why you are doing this. I thought you had just gone insane wanting to cram the database and batch classes together and make all these extra changes. But I remember using sqlite in python it doesn’t support more than one transaction per connection.

    Even so, I don’t think this is the best choice or even the easier choice, because it is pushing a problem from the database layer up to the application and RPC layers. Now if there are two RPC calls happening in different threads that both want to open a transaction or a cursor, they have to coordinate with each other to see who is allowed to go first, or risk crashing the process.

    Also, the C++ code is more complicated than it needs to be because instead of cursors and transactions being owned by batches and getting properly cleaned up when batches are cleaned up, now they can outlive batches, and error handling and cleanup that should be triggered when the batch goes out of scope now gets lost or delayed until a later point, making debugging more difficult. Also the nice layer separation where Batch objects are responsible for data access, and Database objects are responsible for management is lost, and more code has to move, and everything is more complicated.

    If you like the current organization, then please ignore my suggestion, but here is what I would do:

    • For BDB, put ownership of transaction and cursor objects back in the batch objects where they are happy and belong.

    • For Sqlite, just have Mutex m_cursor_mutex, m_transaction_mutex members in the Database instance. Lock m_cursor when a batch object needs a cursor, and unlock when it’s done. Similarly for m_transaction. That way if two RPC calls happen, then there aren’t errors or aborts, one call just waits for the other.


    ryanofsky commented at 9:46 am on June 13, 2020:

    re: #18971 (review)

    Now if there are two RPC calls happening in different threads that both want to open a transaction or a cursor, they have to coordinate with each other to see who is allowed to go first, or risk crashing the process.

    I forgot about cs_wallet when writing this. I think with cs_wallet, two batch objects for the same database will probably not even exist at the same time, so asserts only allowing one cursor or transaction should never trigger, and there should be no need for the m_transaction_mutex suggestion. Something like bool m_transaction_started and assert(!m_transaction_started) for sqlite would be fine. And there wouldn’t be a need for m_cursor_mutex in any case since there’s no problem with multiple cursors, just multiple transactions.

    I still think it is better to keep different batch and connection objects for all the other reasons (limiting lifetime of cursor and txn objects, reporting errors earlier, smaller diff, smaller classes, separation of data access methods from database management methods), but with cs_wallet the multiple thread RPC request thing should be not be a real issue.


    achow101 commented at 6:02 pm on June 13, 2020:

    There does happen to be a few times where there are multiple BerkeleyBatch objects are active, although I don’t know where this is happening. You can check this by adding some print lines at the place where mapFileUseCount is being updated.

    I will probably rework this PR to keep BerkeleyBatch and make the move commits at the beginning so this isn’t so difficult to update.

  71. in src/wallet/db.h:130 in aac3483e9e outdated
    123@@ -124,6 +124,11 @@ class BerkeleyDatabase
    124     Dbc* m_cursor = nullptr;
    125     DbTxn* m_active_txn = nullptr;
    126 
    127+    bool DBRead(CDataStream& key, CDataStream& value) const;
    128+    bool DBWrite(CDataStream& key, CDataStream& value, bool overwrite=true) const;
    129+    bool DBErase(CDataStream& key) const;
    130+    bool DBExists(CDataStream& key) const;
    


    ryanofsky commented at 11:40 am on June 9, 2020:

    In commit “walletdb: refactor Read, Write, Erase, and Exists into non-template func” (aac3483e9e0423b3414cfaf84f37041a200d773c)

    Minor: DBRead does need something like a CDataStream& value argument to write into but all the other CDataStream& arguments are just byte strings and could be Span<char> typed instead. Actually even DBRead could use Span if written as

    0bool DBRead(Span<const char> key, std::function<void(Span<char>)> value);
    

    But feel free to ignore this since I think it mostly just comes from me liking Span more than CDataStream.

    Other minor suggestions:

    • Would name these ReadKey WriteKey EraseKey HasKey because DB` prefix is redundant (every method here is a db method) and these are specifically accessing keys in the database, not really the database itself.

    • Would drop const method keyword if/when these become virtual methods. I think virtual const methods should usually be avoided, because const exposes an implementation detail of subclasses to the parent class, and can infectiously force other subclasses to do dumb things like have mutable members or add pointless indirection to work around the inherited const.


    achow101 commented at 7:00 pm on June 9, 2020:
    Renamed and removed const
  72. in src/wallet/db.h:265 in aac3483e9e outdated
    269         ssValue.reserve(10000);
    270         ssValue << value;
    271-        SafeDbt datValue(ssValue.data(), ssValue.size());
    272 
    273         // Write
    274-        int ret = m_db->put(m_active_txn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE));
    


    ryanofsky commented at 11:55 am on June 9, 2020:

    In commit “walletdb: refactor Read, Write, Erase, and Exists into non-template func” (aac3483e9e0423b3414cfaf84f37041a200d773c)

    I like this commit! Nice to keep the template messiness and bdb messiness separate.

  73. in src/wallet/walletdb.h:215 in 64a5a29d69 outdated
    215-        m_batch(database, pszMode, _fFlushOnClose),
    216+        m_flush_on_close(_fFlushOnClose),
    217         m_database(database)
    218     {
    219+        database.Open(pszMode);
    220+        database.Acquire();
    


    ryanofsky commented at 12:07 pm on June 9, 2020:

    In commit “walletdb: Remove BerkeleyBatch and have WalletBatch use WalletDatabase” (64a5a29d692378df443ae599b81c1f4547ec3d03)

    Believe this should also call acquire before open not after per #18971 (review)


    achow101 commented at 7:00 pm on June 9, 2020:
    Done
  74. in src/wallet/db.h:294 in 4c6c9080d4 outdated
    290+    }
    291+
    292+    ~BerkeleyDatabase();
    293+
    294+    /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
    295+    std::unique_ptr<Db> m_db;
    


    ryanofsky commented at 12:27 pm on June 9, 2020:

    In commit “walletdb: Make WalletDatabase abstract class” (4c6c9080d48ec5810e7d36348f7c730a586e9fb5)

    Minor: Any particular reason this is a public member? (Only asking because I think it would be good for comprehension to declare std::shared_ptr<BerkeleyEnvironment> and std::unique_ptr<Db> members next to each other)


    achow101 commented at 6:55 pm on June 9, 2020:
    BerkeleyEnvironment needs this.
  75. in src/wallet/db.h:26 in b4c2765fd1 outdated
    24 #pragma GCC diagnostic ignored "-Wsuggest-override"
    25 #endif
    26-#include <db_cxx.h>
    27 #if defined(__GNUC__) && !defined(__clang__)
    28 #pragma GCC diagnostic pop
    29 #endif
    


    ryanofsky commented at 12:34 pm on June 9, 2020:

    In commit “Move BDB specific things into bdb.{cpp/h}” (b4c2765fd19e5dd8c823febd65ad5e62a58c8260)

    I think you can drop all this pragma stuff if you’re dropping the db_cxx include


    achow101 commented at 7:00 pm on June 9, 2020:
    Removed
  76. ryanofsky approved
  77. ryanofsky commented at 1:44 pm on June 9, 2020: member

    Code review almost-ACK b4c2765fd19e5dd8c823febd65ad5e62a58c8260 (just various individual comments should be addressed).

    This is a nice code improvement that enables new features (#19077, #19137, #19102)

    My biggest comment in terms of scope is about sharing cursor and transaction objects. Sharing these seems fragile if not buggy (https://github.com/bitcoin/bitcoin/pull/18971#discussion_r437330343). It seems like it would also shrink down the PR to just make the Batch cursor & transaction & read/write/erase methods virtual instead of trying to cram them into the already complicated Database class. Database class would just need a virtual unique_ptr<Batch> Database::MakeBatch() method returning the right Batch subclass for the database subclass.

    I’m also surprised how the moveonly commits in this PR come after the change commits, rather than before. I’d be curious if you have a good workflow for updates because it seems like every update to one of the early change commits would require repeatedly making the same updates in the later move commits.

    Anyway these are all fairly minor comments. The changes here are great and definitely much better than everything that was here before.

  78. achow101 force-pushed on Jun 9, 2020
  79. achow101 commented at 7:21 pm on June 9, 2020: member

    My biggest comment in terms of scope is about sharing cursor and transaction objects. Sharing these seems fragile if not buggy (#18971 (comment)). It seems like it would also shrink down the PR to just make the Batch cursor & transaction & read/write/erase methods virtual instead of trying to cram them into the already complicated Database class. Database class would just need a virtual unique_ptr<Batch> Database::MakeBatch() method returning the right Batch subclass for the database subclass.

    I’ll look into it. However I’m wary of having parallel inheritances like this. It also doesn’t seem like it would be useful because we barely use transactions and cursors. Transactions are only used during encryption and cursors are only used for initial loading, rewrite, and zapwallettx.

    I’d be curious if you have a good workflow for updates because it seems like every update to one of the early change commits would require repeatedly making the same updates in the later move commits.

    No and it’s very painful.

  80. achow101 force-pushed on Jun 9, 2020
  81. in src/wallet/db.cpp:634 in 1b354344eb outdated
    642@@ -631,16 +643,9 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
    643                 if (!fMockDb)
    644                     dbenv->lsn_reset(strFile.c_str(), 0);
    645                 LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
    646-                mapFileUseCount.erase(mi++);
    647-            } else
    648-                mi++;
    


    ryanofsky commented at 3:34 pm on June 12, 2020:

    In commit “walletdb: Change BerkeleyDatabase::Flush to Close” (1b354344ebfc68506484f688f042b0588e83f1c4)

    If these lines are removed in this commit, I think this is an infinite loop. Not a serious problem since it’s fixed in a later commit, but wanted to make a note

  82. in src/wallet/db.cpp:149 in 43afe48e58 outdated
    144@@ -126,6 +145,8 @@ void BerkeleyEnvironment::Close()
    145         if (database.m_db) {
    146             database.m_db->close(0);
    147             database.m_db.reset();
    148+            LOCK(cs_db);
    149+            g_fileids.erase(database.m_file_path);
    


    ryanofsky commented at 7:29 pm on June 12, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (43afe48e5848a263240f6e128269d00bf932da61)

    re: #18971 (review)

    This can’t be moved into CloseDb. However I have moved it into BerkeleyEnvironment::Close.

    Oh you’re right, it can’t go there because flushes also close the database. But now if two databases are opened in the same environment, and one is closed and reopened, I think there will be an inappropriate “duplicates fileids” error on reopening. Calling g_fileids.erase in ~BerkeleyDatabase seems like a way to fix it. Following change there seems to work:

    0@@ -350,6 +350,8 @@ BerkeleyDatabase::~BerkeleyDatabase()
    1         LOCK(cs_db);
    2         size_t erased = env->m_databases.erase(strFile);
    3         assert(erased == 1);
    4+        g_fileids.erase(m_file_path);
    5+        assert(erased == 1);
    6         assert(!m_db);
    7     }
    8 }
    

    achow101 commented at 7:07 pm on June 15, 2020:
    Do you have a specific scenario where this could happen? This change seems to be causing a test failure.

    ryanofsky commented at 5:30 pm on June 18, 2020:

    re: #18971 (review)

    Do you have a specific scenario where this could happen? This change seems to be causing a test failure.

    Marking resolved, followed up in a new thread: #18971 (review)

  83. in src/wallet/db.cpp:584 in cb343b8628 outdated
    583@@ -584,8 +584,7 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    584                                 fSuccess = false;
    585                         }
    586                     if (fSuccess) {
    587-                        db.Close();
    588-                        env->CloseDb(strFile);
    


    ryanofsky commented at 7:35 pm on June 12, 2020:

    In commit “walletdb: Move Rewrite into BerkeleyDatabase” (cb343b8628587f7ef4949741f666b4ee06f8774d)

    Note: It’s safe to drop this env->CloseDb because m_refcount is will still be 0 here, so BerkeleyDatabase::Close call below will call BerkeleyEnvironment::Flush internally which will call BerkeleyEnvironment::CloseDb

  84. in src/wallet/db.cpp:725 in 0e96b5b77c outdated
    720     }
    721 }
    722 
    723 void BerkeleyDatabase::Flush()
    724 {
    725+    if (m_active_txn) {
    


    ryanofsky commented at 7:56 pm on June 12, 2020:

    In commit “walletdb: Move Txn* functions into WalletDatabase” (0e96b5b77c33275d287f5c33e77f87ab3f4ac868)

    What is this doing? Could you add a code comment to explain it? Maybe log something or return an error? This seems like a change in behavior, and I wouldn’t expect there to be a problem checkpointing data just because a transaction’s active. Maybe remove this if there isn’t a particular reason to think it is necessary.

    Tangent: I think it was confusing before that BerkeleyBatch::Flush did a completely different thing than BerkeleyDatabase::Flush and BerkeleyEnvironment::Flush and CWallet::Flush. The batch flush meant “write memory to disk” and the other flushes meant “maybe close databases if they arent in use, otherwise do nothing”. Things are even more confusing now with the Batch and Database objects combined. Ideally I think we would stop saying flush and rename the methods to Checkpoint() and CloseIfUnused() respectively.

  85. ryanofsky approved
  86. ryanofsky commented at 10:46 pm on June 12, 2020: member

    Code review ACK 47dc8d6aff04e4f4c3ed0b4eb28fca0596d020e6. Looks very good. I suggested changes but they aren’t too important and could all be followups.

    I’d be curious if you have a good workflow for updates because it seems like every update to one of the early change commits would require repeatedly making the same updates in the later move commits.

    No and it’s very painful.

    Sorry for that. My experience is it’s less painful with a one or two big move commits up front (which can also be merged earlier) and the refactoring changes after the moves. But just having the moves separate at all has really helped me review this PR, so I definitely appreciate the effort!

  87. achow101 force-pushed on Jun 15, 2020
  88. achow101 commented at 10:35 pm on June 15, 2020: member

    I’ve redone the refactor to keep BerkeleyBatch and introduce DatabaseBatch as an abstract class. Database transactions, cursors, and modifications occur via BerkeleyBatch.

    This change also results in changes to the Flush and Close logic. I’ve decided to keep the shutdown argument in BerkeleyEnvironment::Flush. So BerkeleyDatabase::Flush calls that with shutdown == false and BerkeleyDatabase::Close calls it with shutdown == true. This makes the changes there far fewer and should be easier to review. The commit to move the log consolidation has been removed.

    The commits have also been reordered so that moves occur first so this is less painful to modify later.

  89. achow101 force-pushed on Jun 15, 2020
  90. MarcoFalke commented at 11:33 pm on June 15, 2020: member
    Can the move-only stuff be split up in its own pr? That seems like something a lot more people could review than the bdb-refactors.
  91. achow101 force-pushed on Jun 15, 2020
  92. achow101 force-pushed on Jun 15, 2020
  93. achow101 force-pushed on Jun 16, 2020
  94. achow101 commented at 0:58 am on June 16, 2020: member

    Updating this list in the OP now.

    I’ve started breaking this down into separate PRs. Some of the simpler stuff is moved up to the front so they can be merged first.

    • Mostly simple moveonly things: #19290 (merged)
      • walletdb: Make SpliWalletFilePath non-static
      • walletdb: Add IsBDBWalletLoaded to look for BDB wallets specifically
      • walletdb: move IsWalletLoaded to walletdb.cpp
      • walletdb: moveonly: Move BerkeleyBatch Cursor and Txn funcs to cpp
      • walletdb: Move BDB specific things into bdb.{cpp/h}
    • Refactor Read, Write, Erase, Exists: #19292 (merged)
      • walletdb: refactor Read, Write, Erase, and Exists into non-template func
    • Handle cursor internally: #19308
      • walletdb: Handle cursor internally
    • Make Create, CreateMock, and CreateDummy standalone: #19310 (merged)
      • Add Create*WalletDatabase functions
      • scripted-diff: Replace WalletDatabase::Create* with CreateWalletDatabase
      • Remove WalletDatabase::Create, CreateMock, and CreateDummy

    I will continue to break this down as things get merged.

  95. meshcollider referenced this in commit 62d863f915 on Jun 17, 2020
  96. achow101 force-pushed on Jun 17, 2020
  97. in src/wallet/bdb.cpp:125 in c89ec975fe outdated
    121@@ -103,6 +122,8 @@ void BerkeleyEnvironment::Close()
    122         if (database.m_db) {
    123             database.m_db->close(0);
    124             database.m_db.reset();
    125+            LOCK(cs_db);
    126+            g_fileids.erase(database.m_file_path);
    


    ryanofsky commented at 3:54 pm on June 17, 2020:

    In commit “walletdb: Use a global g_fileids instead of m_fileids for each env” (d9cbaa21c351e266a7fc4a52774e85a8e5a3d08d)

    re: #18971 (review)

    Do you have a specific scenario where this could happen? This change seems to be causing a test failure.

    The scenario is two wallets A and B are loaded in the same environment, then A is unloaded and B is unloaded. When A is unloaded m_database.erase is called so the m_databases loop on line 118 above that runs when B is unloaded doesn’t include A so A’s fileid is never erased here. Next time A is loaded there will be a spurious duplicate fileid error.

    IIRC I tested the previous fix on 47dc8d6aff04e4f4c3ed0b4eb28fca0596d020e6 and all tests passed. With the following change on current f370c1c40ea1f152900052ab1a14c6c192350256 all tests seem to pass:

     0--- a/src/wallet/bdb.cpp
     1+++ b/src/wallet/bdb.cpp
     2@@ -361,6 +361,17 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
     3     }
     4 }
     5 
     6+BerkeleyDatabase::~BerkeleyDatabase() {
     7+    if (env) {
     8+        env->CloseDb(strFile);
     9+        assert(!m_db);
    10+        size_t erased = env->m_databases.erase(strFile);
    11+        assert(erased == 1);
    12+        g_fileids.erase(m_file_path);
    13+        assert(erased == 1);
    14+    }
    15+}
    16+
    17 void BerkeleyDatabase::Open(const char* pszMode)
    18 {
    19     if (IsDummy()){
    20diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
    21index a638fbf99f9..1db5851e5f6 100644
    22--- a/src/wallet/bdb.h
    23+++ b/src/wallet/bdb.h
    24@@ -112,12 +112,7 @@ public:
    25         assert(inserted.second);
    26     }
    27 
    28-    ~BerkeleyDatabase() override {
    29-        if (env) {
    30-            size_t erased = env->m_databases.erase(strFile);
    31-            assert(erased == 1);
    32-        }
    33-    }
    34+    ~BerkeleyDatabase() override;
    35 
    36     /** Open the database if it is not already opened. */
    37     void Open(const char* mode) override;
    

    achow101 commented at 5:49 pm on June 17, 2020:

    Ah, I see how that could theoretically happen. I did a bit of testing and wasn’t able to trigger a spurious duplicate fileid error. I believe that’s because closing database A also closed database B since nothing was using it. So to get this error, B needs to be in use.

    The reason this was failing tests was because I corrected a mistake in the patch. You have g_fileids.erase(m_file_path); which should actually be erased = g_fileids.erase(m_file_path);. Otherwise the following assert(erased == 1); doesn’t do anything. But because BerkeleyEnvironment::Close removes the fileid before the destructor is called, this assertion will fail.

    So instead, I’ve dropped that assertion but still kept the g_fileids.erase(m_file_path);.

  98. achow101 force-pushed on Jun 17, 2020
  99. in src/wallet/bdb.cpp:546 in 84bc5c1705 outdated
    527@@ -528,17 +528,17 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
    528                         fSuccess = false;
    529                     }
    530 
    531-                    Dbc* pcursor = db.GetCursor();
    532-                    if (pcursor)
    533+                    if (db.CreateCursor())
    


    ryanofsky commented at 5:01 pm on June 17, 2020:

    In commit “walletdb: Handle cursor internally” (84bc5c1705691f4ecdfb8bcc90eb1bc4970281dc)

    As noted #18971 (review), the error handling for failing to get a cursor here is broken. It had been broken before this commit, but now this commit is introducing a new way for getting a cursor to fail (the case where m_cursor is not null), so this might be making the problem worse. A simple way to fix this while keeping status quo would be to assert(!m_cursor) in CreateCursor instead of returning false. Otherwise it probably would be good to do improve the error handling here and at least log or return failure.


    achow101 commented at 5:59 pm on June 17, 2020:
    I’ve changed CreateCursor to assert.
  100. in src/wallet/bdb.h:215 in 84bc5c1705 outdated
    216@@ -217,6 +217,7 @@ class BerkeleyBatch
    217     Db* pdb;
    218     std::string strFile;
    219     DbTxn* activeTxn;
    220+    Dbc* m_cursor;
    


    ryanofsky commented at 5:04 pm on June 17, 2020:

    In commit “walletdb: Handle cursor internally” (84bc5c1705691f4ecdfb8bcc90eb1bc4970281dc)

    Could the commit message for this say something about the motivation for this change? It seems good, but I’m also not sure I understand why it’s happening.


    achow101 commented at 5:59 pm on June 17, 2020:

    Done.

    This is happening because Dbc is BDB specific so a generic DatabaseBatch class can’t return them.

  101. in src/wallet/walletdb.cpp:890 in 84bc5c1705 outdated
    885@@ -880,8 +886,8 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
    886                 ssValue >> vWtx.back();
    887             }
    888         }
    889-        pcursor->close();
    890     } catch (...) {
    891+        m_batch.CloseCursor();
    


    ryanofsky commented at 5:07 pm on June 17, 2020:

    In commit “walletdb: Handle cursor internally” (84bc5c1705691f4ecdfb8bcc90eb1bc4970281dc)

    Is it intentional to close the cursor in the catch block and stop closing the cursor in the try block? It looks like it could be an accidental change


    achow101 commented at 5:52 pm on June 17, 2020:

    Yes, this was intentional. I believe previously we could have had a memory leak as the cursor could possible never get closed.

    Before the while loop is exited, we always close the cursor. So having this at the end of the try block isn’t useful. However it is also possible that the while loop is exited by an exception, in which case the cursor would never get closed as the catch block doesn’t close it, and the cursor wasn’t being closed afterwards. Moving the CloseCursor into the catch block handles this case.

  102. in src/wallet/bdb.h:309 in 84bc5c1705 outdated
    303@@ -303,8 +304,9 @@ class BerkeleyBatch
    304         return HasKey(ssKey);
    305     }
    306 
    307-    Dbc* GetCursor();
    308-    int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue);
    309+    bool CreateCursor();
    310+    bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete);
    311+    void CloseCursor();
    


    ryanofsky commented at 5:10 pm on June 17, 2020:

    In commit “walletdb: Handle cursor internally” (84bc5c1705691f4ecdfb8bcc90eb1bc4970281dc)

    Would it make sense to call CloseCursor in the BerkeleyBatch destructor? It seems like it could guard against memory leaks, and maybe avoid the need to add CloseCursor calls some of the places where they are added.


    achow101 commented at 6:00 pm on June 17, 2020:
    Done (I put it in Close which is called by the desctructor).
  103. ryanofsky commented at 5:20 pm on June 17, 2020: member

    Start rereviewing (will update list below with progress).

    • cc05c351c0e4a62eed81996b5370e64b28b985fe walletdb: Handle cursor internally (1/15)
    • 11f7966982364911f4ee88df787961b110d24a38 Add Create*WalletDatabase functions (2/15)
    • 2dd5707793a448b6425490e17847d4b12cb53020 scripted-diff: Replace WalletDatabase::Create* with CreateWalletDatabase (3/15)
    • 37c656b671ba1e17df5f8cfb2ae7776555e61313 Remove WalletDatabase::Create, CreateMock, and CreateDummy (4/15)
    • 622b5cbddfab6496f4334271a8aedeaff3a6999d walletdb: Combine VerifyDatabaseFile and VerifyEnvironment (5/15)
    • 73888f0bf833d28d951a3cf597c772c7a3d87228 walletdb: Move PeriodicFlush into WalletDatabase (6/15)
    • 8c903f535019b4820cd2259eb1d77033d32b066a walletdb: Move Db->open to BerkeleyDatabase::Open (7/15)
    • b0e0ecca36728c79e985acfbafc6737d5d87570f walletdb: Use a global g_fileids instead of m_fileids for each env (8/15)
    • 54ee8905d23213d6165fe45601627d2aadee1bb7 walletdb: Move BerkeleyDatabase::Flush(true) to Close() (9/15)
    • c5b005f34ccc0d0a40486da37394a3556e1fa14b walletdb: Add BerkeleyDatabase::RemoveRef (10/15)
    • baca4d0769b63578a8e4a94beb01f8f750c1fb6f walletdb: track database file use as m_refcount within BerkeleyDatabase (11/15)
    • 029b13023dd3b695bcbf65a8fa4e89640b86ddd9 walletdb: Move Rewrite into BerkeleyDatabase (12/15)
    • 58fc08efd4641cdeec74e9af1dd71a12fd09efbc walletdb: Remove BerkeleyBatch friend class from BerkeleyDatabase (13/15)
    • b11e2ad21ed264307e746fb198c5ddeb8f23c954 walletdb: Add MakeBatch function to BerkeleyDatabase and use it (14/15)
    • d850984306f8b605ae5a35dd19018b734f2bbd55 walletdb: Introduce WalletDatabase and DatabaseBatch abstract classes (15/15)
  104. achow101 force-pushed on Jun 17, 2020
  105. achow101 force-pushed on Jun 17, 2020
  106. achow101 force-pushed on Jun 17, 2020
  107. laanwj removed this from the "Blockers" column in a project

  108. DrahtBot added the label Needs rebase on Jun 18, 2020
  109. achow101 force-pushed on Jun 18, 2020
  110. DrahtBot removed the label Needs rebase on Jun 18, 2020
  111. MarcoFalke referenced this in commit dbd7a91fdf on Jun 18, 2020
  112. walletdb: Handle cursor internally
    Instead of returning a Dbc (BDB cursor object) and having the caller
    deal with the cursor, make BerkeleyBatch handle the cursor internally.
    
    This prepares BerkeleyBatch to work with other database systems as Dbc
    objects are BDB specific.
    8f9353cec3
  113. achow101 force-pushed on Jun 18, 2020
  114. walletdb: Combine VerifyDatabaseFile and VerifyEnvironment
    Combine these two functions into a single Verify function that is a
    member of WalletDatabase. Additionally, these are no longer static.
    31d39e7655
  115. walletdb: Move PeriodicFlush into WalletDatabase
    Make PeriodicFlush a non-static member of WalletDatabase instead of
    WalletBatch.
    0bbfd0652f
  116. walletdb: Move Rewrite into BerkeleyDatabase
    Make Rewrite actually a member of BerkeleyDatabase instead of a static
    function in BerkeleyBatch
    a927f29823
  117. walletdb: Refactor DatabaseBatch abstract class from BerkeleyBatch fad6faf245
  118. achow101 force-pushed on Jun 18, 2020
  119. Sjors commented at 9:41 am on June 19, 2020: member

    I get similar errors as Travis on macOS (./configure --enable-debug --with-incompatible-bdb --enable-werror):

    0./wallet/bdb.h:167:17: error: non-static data member 'm_file_path' of 'BerkeleyDatabase' shadows member inherited from type 'WalletDatabase' [-Werror,-Wshadow-field]
    11687    std::string m_file_path;
    21688                ^
    31689./wallet/db.h:166:17: note: declared here
    41690    std::string m_file_path;
    51691                ^
    

    And:

    0./wallet/walletdb.h:208:17: warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
    1        m_batch(std::move(database.MakeBatch(pszMode, _fFlushOnClose))),
    2                ^
    
  120. walletdb: Add MakeBatch function to BerkeleyDatabase and use it
    Instead of having WalletBatch construct the BerkeleyBatch, have
    BerkeleyDatabase do it and return a std::unique_ptr<BerkeleyBatch>
    1e62e6ebaa
  121. achow101 force-pushed on Jun 19, 2020
  122. achow101 commented at 3:17 pm on June 19, 2020: member

    I get similar errors as Travis on macOS

    Should be fixed now.

  123. meshcollider added this to the "PRs" column in a project

  124. walletdb: Move BerkeleyDatabase::Flush(true) to Close()
    Instead of having Flush optionally shutdown the database and
    environment, add a Close() function that does that.
    34238dcfa6
  125. walletdb: Introduce AddRef and RemoveRef functions
    Refactor mapFileUseCount increment and decrement to separate functions
    AddRef and RemoveRef
    6b922dd31d
  126. walletdb: Add BerkeleyDatabase::Open dummy function
    Adds an Open function for the class abstraction that does nothing for
    now.
    8c01416142
  127. walletdb: Introduce WalletDatabase abstract class
    Make WalletDatabase actually an abstract class and not just a typedef
    for BerkeleyDatabase. Have BerkeleyDatabase inherit this class.
    ec2b5ab71f
  128. walletdb: track database file use as m_refcount within BerkeleyDatabase
    Instead of having BerkeleyEnvironment track the file use count, make
    BerkeleyDatabase do it itself.
    69c0e5cd6f
  129. walletdb: Move Db->open to BerkeleyDatabase::Open
    Instead of opening the Db handle in BerkeleyBatch, make BerkeleyDatabase
    do that.
    3fa3b09dac
  130. walletdb: Use a global g_fileids instead of m_fileids for each env
    File ids were basically global anyways, it doesn't make sense to have
    them be environment specific if we're going to loop through all the
    environments anyways.
    bc4985fc11
  131. walletdb: Remove BerkeleyBatch friend class from BerkeleyDatabase a2a30a569e
  132. achow101 force-pushed on Jun 20, 2020
  133. achow101 commented at 2:33 am on June 20, 2020: member

    This PR is now superseded by the PRs listed in the OP. Even though the final one, #19335, is the same diff and commits as this one, I wanted to have it be separate without the history from this PR as it is conceptually unrelated to this refactor. The refactoring into the abstract classes occurs in #19334.

    All of the PRs that were dependent on this one will be rebased onto #19334

  134. achow101 closed this on Jun 20, 2020

  135. laanwj referenced this in commit 26291745ae on Jul 1, 2020
  136. MarcoFalke referenced this in commit 171f4a516b on Jul 5, 2020
  137. MarcoFalke referenced this in commit a924f61cae on Jul 14, 2020
  138. sidhujag referenced this in commit fa48d2e308 on Jul 14, 2020
  139. meshcollider moved this from the "PRs" to the "Design" column in a project

  140. meshcollider referenced this in commit 9d4b3d86b6 on Jul 23, 2020
  141. sidhujag referenced this in commit 855dce6786 on Jul 24, 2020
  142. laanwj referenced this in commit 8db23349fe on Jul 29, 2020
  143. sidhujag referenced this in commit fbe47717c0 on Jul 31, 2020
  144. DrahtBot locked this on Aug 30, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-03 10:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me