rpc: Make unloadwallet wait for complete wallet unload #14941

pull promag wants to merge 2 commits into bitcoin:master from promag:2018-12-sync-unloadwallet changing 6 files +61 −12
  1. promag commented at 11:31 pm on December 12, 2018: member

    Currently the unloadwallet RPC is asynchronous, it only signals the intent to unload the wallet and then returns the response to the client. The actual unload can happen later and the client has no way to be notified of that.

    This PR makes the unloadwallet RPC synchronous, meaning that it blocks until the wallet is fully unloaded.

    Replaces #14919, fixes #14917.

  2. meshcollider added the label Wallet on Dec 12, 2018
  3. meshcollider added the label RPC/REST/ZMQ on Dec 12, 2018
  4. DrahtBot commented at 2:03 am on December 13, 2018: 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:

    • #14144 (Refactoring: Clarify code using encrypted_batch in CWallet by domob1812)
    • #13926 ([Tools] bitcoin-wallet - a tool for creating and managing wallets offline by jnewbery)
    • #10973 (Refactor: separate wallet from node 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. promag commented at 9:07 am on December 13, 2018: member
  6. laanwj commented at 10:39 am on December 13, 2018: member

    Concept ACK - I think this can be useful outside of the tests, as well.

    Also I’d like to discuss the wait default value, currently it avoid changing behavior. If wait=true is considered the correct behavior then this could be backport to 0.17.2?

    Strictly spoken this is a feature, not a bugfix, though if it’s optional and improves testing I think a point can be made to backport it anyhow.

  7. ryanofsky commented at 2:11 pm on December 13, 2018: member

    Code change looks ok, but this shouldn’t be an option. Making wait=false the default makes unloadwallet pointlessly difficult to use, and inconsistent with other methods, including closely related methods like createwallet and loadwallet.

    If you think there is reason to support wait=false, I think you need to say what the use-case is, and why it wouldn’t be better served by having the client make a fully asynchronous JSON-RPC request, instead of using this half-asynchronous/half-synchronous wait option that doesn’t (I guess?) block on acquiring cs_wallet, but does block on the network connection and on acquiring cs_wallets, and on whatever the UI currently does in NotifyUnload (or will do in the future).

    Also, if you want to keep the async=false option, it would be good to have a test to ensure that it doesn’t acquire cs_wallet and cs_main. This would be easy to write as a c++ unit test which acquires the locks in one thread and calls unloadwallet in another.

  8. promag force-pushed on Dec 13, 2018
  9. promag commented at 4:09 pm on December 13, 2018: member

    Changed to synchronous unload. Locally added some sleeps to see if there were any connection timeout, but didn’t manage to cause one.

    doesn’t (I guess?) block on acquiring cs_wallet

    no it doesn’t, it waits until the weak pointer is expired.

  10. in src/wallet/wallet.cpp:102 in 36d41ff908 outdated
    102-    wallet->WalletLogPrintf("Releasing wallet\n");
    103-    wallet->BlockUntilSyncedToCurrentChain();
    104-    wallet->Flush();
    105-    delete wallet;
    106+    {
    107+        std::unique_lock<std::mutex> lk(g_wallet_release_mutex);
    


    ryanofsky commented at 5:59 pm on December 13, 2018:
    Might be good to use Mutex and WAIT_LOCK from sync.h instead of mutex and unique_lock for thread annotations & debugging.

    promag commented at 2:28 am on December 14, 2018:
    Will change.
  11. promag force-pushed on Dec 13, 2018
  12. in src/wallet/wallet.cpp:85 in c85d15d9d3 outdated
    80@@ -81,13 +81,33 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
    81     return nullptr;
    82 }
    83 
    84+static std::mutex g_wallet_release_mutex;
    85+static std::condition_variable g_wallet_release_cond;
    


    ryanofsky commented at 6:02 pm on December 13, 2018:
    It seems more common in bitcoin code to abbreviate condition variables as cv instead of cond and name locks lock instead of lk.

    promag commented at 2:28 am on December 14, 2018:
    Will change.
  13. promag renamed this:
    rpc: Add option to unloadwallet to wait for complete wallet unload
    rpc: Make unloadwallet wait for complete wallet unload
    on Dec 13, 2018
  14. promag force-pushed on Dec 13, 2018
  15. in src/wallet/wallet.cpp:110 in ffa87990d1 outdated
    109+        wallet->BlockUntilSyncedToCurrentChain();
    110+        wallet->Flush();
    111+        delete wallet;
    112+    }
    113+
    114+    g_wallet_release_cond.notify_all();
    


    ryanofsky commented at 6:10 pm on December 13, 2018:
    I think we need to confirm that !weak_wallet.expired() in the other thread is true at the point of this call (and guaranteed by the c++ standard to be true). Otherwise there could be a lost-wakeup bug here because this would be signalling the condition variable before the condition is true.

    promag commented at 6:38 pm on December 13, 2018:
    Good catch, I’ll investigate.

    promag commented at 12:50 pm on December 14, 2018:
    This no longer applies, @ryanofsky concern was valid.
  16. in src/wallet/wallet.cpp:93 in ffa87990d1 outdated
    88+{
    89+    std::weak_ptr<CWallet> weak_wallet(wallet);
    90+    wallet.reset();
    91+
    92+    std::unique_lock<std::mutex> lk(g_wallet_release_mutex);
    93+    while (!weak_wallet.expired()) {
    


    ryanofsky commented at 7:33 pm on December 13, 2018:

    It looks like this might not be sufficient if we want to guarantee that the wallet is really destroyed. From https://en.cppreference.com/w/cpp/memory/weak_ptr/expired:

    The destructor for the managed object may not yet have been called, but this object’s destruction is imminent (or may have already happened).

    A more reliable but cumbersome approach might be to add wallet members:

    0std::promise<void> m_unload_promise;
    1std:shared_future<void> m_unload_future = m_unload_promise.get_future();
    

    and call m_unloaded_promise.set_value() in the destructor, and then future.wait() here.


    promag commented at 2:29 am on December 14, 2018:
    Please see current approach.
  17. promag force-pushed on Dec 14, 2018
  18. in src/wallet/wallet.cpp:121 in 353659d0dd outdated
    120+            g_wallet_release_cv.wait(lock);
    121+        }
    122+    }
    123+    // Now it's safe to close and delete the wallet.
    124+    pwallet->WalletLogPrintf("Releasing wallet\n");
    125+    pwallet->BlockUntilSyncedToCurrentChain();
    


    promag commented at 2:40 am on December 14, 2018:
    Not sure about this, I think it should be called before UnregisterValidationInterface otherwise SyncWithValidationInterfaceQueue does nothing than waiting..

    ryanofsky commented at 8:24 pm on December 18, 2018:
    This does seem unnecessary. But I’d also expect this code to calling UnregisterValidationInterface before flushing. I.e. stop requesting new notifications, then save state, instead of saving state first and then hoping new notifications don’t arrive after the flush…
  19. promag force-pushed on Dec 18, 2018
  20. promag force-pushed on Dec 18, 2018
  21. promag force-pushed on Dec 18, 2018
  22. in src/wallet/wallet.h:59 in fec3c58bd0 outdated
    53@@ -54,6 +54,11 @@ void StopWallets();
    54 //! Close all wallets.
    55 void UnloadWallets();
    56 
    57+//! Unloads and deletes the wallet.
    58+//  Blocks current thread until the wallet is destroyed.
    59+//  The caller must std::move the pointer otherwise it deadlocks
    


    ryanofsky commented at 7:50 pm on December 18, 2018:
    You could make the argument type std::shared_ptr<CWallet>&& to make the compiler require an rvalue.
  23. in src/init.cpp:265 in fec3c58bd0 outdated
    261@@ -262,10 +262,10 @@ void Shutdown(InitInterfaces& interfaces)
    262         LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what());
    263     }
    264 #endif
    265+    interfaces.chain_clients.clear();
    


    ryanofsky commented at 7:54 pm on December 18, 2018:
    This seems sensible. I have the same change in aa67dc7681337a3de79641981820b4b08ba35f03 from #10973.
  24. in src/wallet/wallet.cpp:95 in fec3c58bd0 outdated
    94-    wallet->Flush();
    95-    delete wallet;
    96+    {
    97+        std::unique_lock<std::mutex> lock(g_wallet_release_mutex);
    98+        // Assert that there was an explict UnloadWallet call.
    99+        assert(g_unloading_wallet_set.erase(wallet) == 1);
    


    ryanofsky commented at 8:07 pm on December 18, 2018:

    Would suggest:

    0size_t erased = g_unloading_wallet_set.erase(wallet) == 1
    1assert(erased == 1);
    

    Even though we don’t support building bitcoin with NDEBUG, I think most people just expect asserts to contain error checking, not code that is important to run.

  25. promag force-pushed on Dec 18, 2018
  26. ryanofsky commented at 8:57 pm on December 18, 2018: member

    This change seems like it should work, but multiwallet test is failing. There’s some problem with combine_logs on travis, but locally I see:

    0AssertionError: [node 0] Expected message "BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" does not partially match stderr:
    1"bitcoind: wallet/wallet.cpp:95: void ReleaseWallet(CWallet*): Assertion `g_unloading_wallet_set.erase(wallet) == 1' failed."
    
  27. promag commented at 9:01 pm on December 18, 2018: member
    @ryanofsky looks like you started reviewing before my latest push?
  28. promag force-pushed on Dec 18, 2018
  29. in src/wallet/wallet.cpp:107 in c8d60d10c6 outdated
    104+    {
    105+        LOCK(g_wallet_release_mutex);
    106+        unloading = g_unloading_wallet_set.erase(wallet) == 1;
    107+    }
    108+    if (unloading) {
    109+        // Delegate wallet deletion for UnloadWallet caller.
    


    promag commented at 10:04 pm on December 18, 2018:
    Delegate wallet deletion to UnloadWallet caller
  30. in src/wallet/wallet.cpp:110 in c8d60d10c6 outdated
    107+    }
    108+    if (unloading) {
    109+        // Delegate wallet deletion for UnloadWallet caller.
    110+        g_wallet_release_cv.notify_all();
    111+    } else {
    112+        // Delegate wallet deletion for UnloadWallet caller.
    


    promag commented at 10:04 pm on December 18, 2018:
    Could say it’s not necessary to call NotifyUnload because at this point there are no shared pointers to this wallet.
  31. promag commented at 10:07 pm on December 18, 2018: member
    Self review, 2 comments must be fixed.
  32. promag force-pushed on Dec 19, 2018
  33. promag force-pushed on Dec 19, 2018
  34. promag force-pushed on Dec 19, 2018
  35. promag force-pushed on Dec 19, 2018
  36. laanwj added this to the milestone 0.17.2 on Dec 19, 2018
  37. promag referenced this in commit 702b74698c on Dec 19, 2018
  38. promag commented at 4:18 pm on January 2, 2019: member
    @ryanofsky would appreciate another review! 😄ty
  39. fanquake added this to the "Blockers" column in a project

  40. gmaxwell commented at 7:41 pm on January 3, 2019: contributor
    Concept ACK.
  41. in src/wallet/wallet.cpp:105 in 7aed2bb1f0 outdated
    100 }
    101 
    102+// Custom deleter for shared_ptr<CWallet>.
    103+static void ReleaseWallet(CWallet* wallet)
    104+{
    105+    bool unloading{false};
    


    ryanofsky commented at 9:07 pm on January 3, 2019:

    Is there a reason this needs to delegate deletion? It seems if you removed the DeleteWallet call from UnloadWallet, you could simplify this function to just:

    0DeleteWallet(wallet);
    1LOCK(g_wallet_release_mutex);
    2g_unloading_wallet_set.erase(wallet);
    3g_wallet_release_cv.notify_all();
    

    promag commented at 9:56 pm on January 3, 2019:
    No strong reason, just thought the unload burden/time should be left to the unloadwallet caller. The way you suggest both the unloadwallet caller and the last wallet client wait for the complete unload.

    ryanofsky commented at 10:24 pm on January 3, 2019:

    just thought the unload burden/time should be left to the unloadwallet caller.

    Interesting. That seems like a legitimate optimization for the last RPC using the wallet. Personally, I think I’d still choose the simplification over the optimization, but if you want to keep the optimization it’d be good to have a comment saying why it’s better to delete on the unload thread than the release thread.

  42. in src/wallet/wallet.h:62 in 7aed2bb1f0 outdated
    53@@ -54,6 +54,10 @@ void StopWallets();
    54 //! Close all wallets.
    55 void UnloadWallets();
    56 
    57+//! Unloads and deletes the wallet.
    58+//  Blocks current thread until the wallet is deleted.
    59+void UnloadWallet(std::shared_ptr<CWallet>&& wallet);
    


    ryanofsky commented at 9:16 pm on January 3, 2019:
    Comment could say that it’s not necessary to call this function unless you need to block waiting the unload.

    promag commented at 9:57 pm on January 3, 2019:
    Will improve comment.
  43. in src/wallet/wallet.cpp:122 in 7aed2bb1f0 outdated
    126+        assert(g_unloading_wallet_set.insert(pwallet).second);
    127+    }
    128+    // The wallet can be in use so it's not possible to explicitly unload here.
    129+    // Notify the unload intent so that all remaining shared pointers are
    130+    // released.
    131+    pwallet->NotifyUnload();
    


    ryanofsky commented at 9:24 pm on January 3, 2019:
    Could we assert(NotifyUnload.empty()) in the wallet destructor to verify delete isn’t happening too early and clients are fully detached?

    promag commented at 9:57 pm on January 3, 2019:
    Will try.
  44. ryanofsky approved
  45. ryanofsky commented at 9:38 pm on January 3, 2019: member
    utACK 7aed2bb1f06ec505507d28f623b4973ae314c3f. This seems right but I suggested a simplification. It would also be good to update the description and add a release note.
  46. promag force-pushed on Jan 4, 2019
  47. promag commented at 1:42 am on January 4, 2019: member
    Updated after @ryanofsky review, added release notes and updated description.
  48. gmaxwell commented at 5:34 am on January 4, 2019: contributor
    Concept ACK.
  49. ryanofsky approved
  50. ryanofsky commented at 7:18 pm on January 4, 2019: member
    utACK c2ae9d9c295ca75f6653892b5f5e1ad6f5c1a9e7. Changes since last review were adding documentation and assert. Also fixing unrelated (I think) encrypted_batch potential memory leak in CWallet destructor.
  51. laanwj commented at 3:17 pm on January 8, 2019: member
    utACK c2ae9d9c295ca75f6653892b5f5e1ad6f5c1a9e7
  52. laanwj referenced this in commit 91dbba8857 on Jan 10, 2019
  53. jnewbery commented at 9:38 pm on January 10, 2019: member
    Lightly tested ACK c2ae9d9c295ca75f6653892b5f5e1ad6f5c1a9e7, although I have a preference for @ryanofsky’s proposed simplification here: #14941 (review)
  54. promag commented at 4:35 pm on January 14, 2019: member
    Pushed the simplification, also split code from assertions.
  55. in src/wallet/wallet.cpp:101 in e6cfc2b566 outdated
     98+    UnregisterValidationInterface(wallet);
     99     delete wallet;
    100 }
    101 
    102+// Custom deleter for shared_ptr<CWallet>.
    103+// If the release is due to a UnloadWallet call then deletion is delegated to
    


    jnewbery commented at 4:39 pm on January 14, 2019:

    This comment needs to be updated with the latest change.

    Also nit: s/a UnloadWallet/an UnloadWallet/

  56. jnewbery commented at 4:40 pm on January 14, 2019: member
    Looks good. Comment needs updating, and I’d squash the new commit ‘fixup: Russ simplification’ into ‘rpc: Make unloadwallet wait for complete wallet unload’
  57. in src/wallet/wallet.cpp:105 in e6cfc2b566 outdated
    113-    } else {
    114-        // UnloadWallet was not called, just delete the wallet since there's no
    115-        // more shared pointers to the wallet.
    116-        DeleteWallet(wallet);
    117-    }
    118+    DeleteWallet(wallet);
    


    ryanofsky commented at 7:38 pm on January 14, 2019:
    Would suggest inlining DeleteWallet here. This function is only called from this one place, and I can’t think of any other place where it would be safe to call.

    promag commented at 7:48 pm on January 14, 2019:
    Good point.
  58. ryanofsky approved
  59. ryanofsky commented at 7:40 pm on January 14, 2019: member
    utACK e6cfc2b566b5a036d884754363b95ac94808907d. Only change since last review is new commit.
  60. promag force-pushed on Jan 14, 2019
  61. promag force-pushed on Jan 14, 2019
  62. promag commented at 10:02 pm on January 14, 2019: member
    Latest changes in 4a50dce. Squashed.
  63. promag force-pushed on Jan 14, 2019
  64. promag force-pushed on Jan 14, 2019
  65. rpc: Make unloadwallet wait for complete wallet unload c37851de57
  66. doc: Add release notes for unloadwallet change to synchronous call 645e905c32
  67. promag force-pushed on Jan 15, 2019
  68. laanwj merged this on Jan 15, 2019
  69. laanwj closed this on Jan 15, 2019

  70. laanwj referenced this in commit 1b6fc30530 on Jan 15, 2019
  71. promag referenced this in commit 781bf79370 on Jan 15, 2019
  72. jnewbery commented at 4:06 pm on January 15, 2019: member
    utACK 645e905c327411555073fa7964b36f652998059f
  73. jnewbery removed this from the "Blockers" column in a project

  74. promag referenced this in commit 0cd9ad208c on Jan 16, 2019
  75. promag deleted the branch on Jan 20, 2019
  76. laanwj referenced this in commit 30db5cc641 on Jan 31, 2019
  77. sidhujag referenced this in commit 63a518ac3e on Feb 14, 2019
  78. Rishabh42 referenced this in commit 5941f2b526 on Mar 22, 2019
  79. uhliksk referenced this in commit 4c608b5c41 on Apr 21, 2019
  80. Rishabh42 referenced this in commit ce3547fae6 on Apr 22, 2019
  81. uhliksk referenced this in commit 0edd154201 on May 1, 2019
  82. ptschip referenced this in commit 05f446d4fa on Oct 22, 2019
  83. jonspock referenced this in commit 97a82afca6 on Feb 26, 2020
  84. jonspock referenced this in commit 312c6f7521 on Mar 2, 2020
  85. jonspock referenced this in commit 66a67a6d07 on Mar 7, 2020
  86. jonspock referenced this in commit 70dc7dcd59 on Mar 14, 2020
  87. jonspock referenced this in commit 80b5961d58 on Mar 21, 2020
  88. jonspock referenced this in commit 929469ec8d on Mar 24, 2020
  89. jonspock referenced this in commit ff4cd7b695 on Apr 6, 2020
  90. jonspock referenced this in commit cecc2ee830 on Apr 8, 2020
  91. jonspock referenced this in commit af657f8b9d on Apr 8, 2020
  92. jonspock referenced this in commit 2f5a3d5808 on Apr 8, 2020
  93. jonspock referenced this in commit 8b3115236b on Apr 8, 2020
  94. jonspock referenced this in commit acc496fd90 on Apr 9, 2020
  95. jonspock referenced this in commit d32e999cfd on Apr 17, 2020
  96. jonspock referenced this in commit 8e369dab9d on May 23, 2020
  97. jonspock referenced this in commit 3141d841a5 on May 25, 2020
  98. jonspock referenced this in commit 909bbe2913 on Jul 9, 2020
  99. jonspock referenced this in commit 6c780e0f50 on Jul 10, 2020
  100. jonspock referenced this in commit d2f2a2e30c on Jul 17, 2020
  101. jonspock referenced this in commit a91fc9a5bc on Jul 17, 2020
  102. jonspock referenced this in commit 3425e36d74 on Jul 20, 2020
  103. jonspock referenced this in commit 00e014cfd3 on Jul 29, 2020
  104. jonspock referenced this in commit 4fd2966da9 on Jul 31, 2020
  105. jonspock referenced this in commit 90a7ae9b5e on Aug 5, 2020
  106. jonspock referenced this in commit 543ab4ea28 on Aug 6, 2020
  107. jonspock referenced this in commit 66733a8d17 on Aug 7, 2020
  108. proteanx referenced this in commit 8e3c54c649 on Aug 8, 2020
  109. PastaPastaPasta referenced this in commit 39308f017a on Sep 8, 2021
  110. vijaydasmp referenced this in commit 70a1f2c79b on Sep 13, 2021
  111. MarcoFalke locked this on Dec 16, 2021

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-12-18 18:12 UTC

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