New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wallet: Fix possible memory leak in CreateWalletFromFile. #12647
Conversation
This looks fine to me. It looks like the existing leak scenarios are for fatal errors, but more RAII pointer types is good in my opinion. It's kind of weird that you're putting this in a utACK 9378d89 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine moving down the registration.
src/wallet/wallet.cpp
Outdated
@@ -3929,7 +3929,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& | |||
|
|||
int64_t nStart = GetTimeMillis(); | |||
bool fFirstRun = true; | |||
CWallet *walletInstance = new CWallet(name, CWalletDBWrapper::Create(path)); | |||
std::unique_ptr<CWallet> walletInstance(new CWallet(name, CWalletDBWrapper::Create(path))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use MakeUnique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? This is exactly what MakeUnique does, except that this constructs one unique_ptr, whereas std::unique_ptr<CWallet> walletInstance = MakeUnique<...>(...)
would construct two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you say it would construct two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MakeUnique
constructs one (the right-hand side), then the left hand side is created using the move constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the compiler will optimize the copy initialization. Anyway, there are several advantages and we are using MakeUnique so that we can replace with std::make_unique when we switch to c++14.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're right about the compiler optimization. Didn't realize that. http://en.cppreference.com/w/cpp/language/copy_elision
src/wallet/wallet.cpp
Outdated
|
||
CWallet* wallet_ptr = walletInstance.release(); | ||
RegisterValidationInterface(wallet_ptr); | ||
return wallet_ptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Release on return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason? I did it this way so that it would be released before RegisterValidationInterface
creates a global reference. Basically, if RegisterValidationInterface failed for some reason, a memory leak is probably better than a segfault. I don't feel too strongly though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegisterValidationInterface failed for some reason
We would want to shutdown no? Anyway leave as it is then.
@eklitzke Thanks for the review. I think it is OK to use a |
@@ -4025,9 +4025,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& | |||
pindexRescan = FindForkInGlobalIndex(chainActive, locator); | |||
} | |||
|
|||
walletInstance->m_last_block_processed = chainActive.Tip(); | |||
RegisterValidationInterface(walletInstance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you want to move the m_last_block_processed line above, because rescan code below will access it.
I'm also not sure it's a great idea to be moving the RegisterValidationInterface
call. It may work for now, but create a race condition if we allow loading wallets after node startup (see #10740).
I think I'd suggest a more limited change than what's implemented here:
- Creating the wallet above with:
auto wallet_deleter = MakeUnique<CWallet>(...);
CWallet* walletInstance = wallet_deleter.get();
- And changing this line to:
RegisterValidationInterface(wallet_deleter.release());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where m_last_block_processed
is accessed in the rescan code. It seems to me it's just used to implement BlockUntilSyncedToCurrentChain
.
The RegisterValidationInterface call is tricky. I agree moving it down only works if the wallets are all loaded before the Connman starts, as is the case right now, but it would break if wallets are loaded/unloaded via RPC. Once way to handle it could be to call UnregisterValidationInterface
in the CWallet
destructor (even without calling RegisterValidationInterface
in the constructor). In that case, we could leave the register call where it is. What do you think of that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where m_last_block_processed is accessed in the rescan code. It seems to me it's just used to implement BlockUntilSyncedToCurrentChain.
You're right. I misremembered and it should be fine to move this, if that's desirable.
Once way to handle it could be to call UnregisterValidationInterface in the CWallet destructor (even without calling RegisterValidationInterface in the constructor). In that case, we could leave the register call where it is. What do you think of that?
Seems good. Would fix the immediate problem and also help with dynamic loading.
This should be fine because it's an atomic bool. |
src/wallet/wallet.h
Outdated
@@ -789,6 +789,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface | |||
|
|||
~CWallet() | |||
{ | |||
// Even if RegisterValidationInstance was never called on this instance, | |||
// the unregister call is safe to make and is a no-op. | |||
UnregisterValidationInterface(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this doesn't work because UnregisterValidationInterface tries to call virtual methods, which isn't something you can do in c++ in the middle of object destruction. I ran into this problem in #10973
bitcoin/src/interface/chain.cpp
Lines 163 to 169 in 0bdf0e3
~HandlerImpl() override | |
{ | |
// Don't call UnregisterValidationInterface here because it would try to | |
// access virtual methods on this object which can't be accessed during | |
// destruction. Also UnregisterAllValidationInterfaces is already called | |
// at this point, so unregistering this object would be redundant. | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so then should I revert to the version where the Register call was moved to the bottom of the method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so then should I revert to the version where the Register call was moved to the bottom of the method?
IIRC the problem with moving the registration is that it creates potential for race conditions if the code is called at any time other than startup, which is something that's been attempted in prs like #10740 (and I also want to support in #10102).
I think I'd recommend just fixing the memory leak with the minimal suggestion from #12647 (comment). But if you prefer moving the registration, that also seems fine with a comment mentioning the potential for race conditions.
I think maybe the best way to clean up error handling and leaks here going forward would be to move initialization logic out of the static CWallet::CreateWalletFromFile
method into a non-static bool CWallet::Initialize(...) method
. This way errors could just be handled by returning false
from Initialize
and CreateWalletFromFile
would only be responsible for constructing the wallet, calling Initialize, and deleting/unregistering the wallet object in the case of errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there actually a potential for race conditions? The function locks cs_main before the rescan logic and before the RegisterValidationInterface
call even before it was moved.
Needs rebase |
@MarcoFalke Rebased |
1fd6823
to
5799e57
Compare
If there was an error loading the wallet after creating the instance, the instance would leak. This uses RAII to ensure the instance is cleaned up in that case.
No one seems to care about this (myself included), so I'll just close in favor of #13063. |
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin/bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Summary: ``` Fix proposed by ryanofsky in bitcoin/bitcoin#12647 (comment) ``` This cleans the `wallet_hd.py` with asan. Partial backport of core PR10740 (commit 59b87a2) bitcoin/bitcoin@59b87a2 Progress towards T459 Depends on D3618 Test Plan: make check With ASAN enabled: ./test/functional/test_runner.py wallet_hd Reviewers: #bitcoin_abc, deadalnix, markblundeberg Reviewed By: #bitcoin_abc, markblundeberg Differential Revision: https://reviews.bitcoinabc.org/D3629
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
Fix proposed by ryanofsky in bitcoin#12647 (comment)
If there was an error loading the wallet after creating the instance, the instance would leak. This uses RAII to ensure the instance is cleaned up in that case. Issue found during review of #11687.