-
Notifications
You must be signed in to change notification settings - Fork 38.7k
refactor: Release cs_main before MaybeSendFeefilter #22053
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
Conversation
|
|
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
|
Code review ACK faca41443120c254f45d7a4dd72d5d80efacaf59 |
lsilva01
left a comment
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.
Tested on mainnet ACK faca414
FEEFILTER messages sent normally.
|
Approach ACK Suggest adding a |
vasild
left a comment
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.
My concerns expressed in #21527 (comment) are valid for this PR too.
In addition, this PR introduces the mutex without any GUARDED_BY(the new mutex). So it is unclear what is being protected by it. There is this vague comment: "anything only accessed by ProcessMessage(s) or SendMessages" which better be replaced by GUARDED_BY() directives on the variables protected by this mutex.
We have the following call path:
CConnman::ThreadMessageHandler() (single thread) ->
PeerManagerImpl::SendMessages(CNode pto) (protected by CNode::cs_sendProcessing) ->
PeerManagerImpl::MaybeSendFeefilter(CNode pto)
And PeerManagerImpl::MaybeSendFeefilter(CNode pto) accesses the following variables directly or indirectly:
bool PeerManagerImpl::m_ignore_incoming_txs
std::unique_ptr<TxRelay> CNode::m_tx_relay
CNode::GetCommonVersion()
std::atomic<int> CNode::m_greatest_common_version
CNode::HasPermission()
NetPermissionFlags CNode::m_permissionFlags
CTxMemPool& PeerManagerImpl::m_mempool
ChainstateManager& PeerManagerImpl::m_chainman
ChainstateManager::ActiveChainstate()
CChainState* ChainstateManager::m_active_chainstate GUARDED_BY(cs_main)
CChainState::IsInitialBlockDownload()
std::atomic<bool> CChainState::m_cached_finished_ibd // ok
CChain CChainState::m_chain // implicit GUARDED_BY(cs_main)
FeeFilterRounder g_filter_rounder
FeeFilterRounder::round()
FeeFilterRounder::feeset
CAmount CNode::TxRelay::lastSentFeeFilter
std::chrono::microseconds CNode::TxRelay::m_next_send_feefilter
(global) CFeeRate minRelayTxFee
CFeeRate::GetFeePerK()
CFeeRate::GetFee()
CAmount CFeeRate::nSatoshisPerK
PeerManagerImpl::m_connman
CConnman::PushMessage()
const CAddress CNode::addr
std::unique_ptr<TransportSerializer> CNode::m_serializer
TransportSerializer::prepareForTransport()
I think that the above variables should be analyzed with respect to access from other threads before changing MaybeSendFeefilter() to be executed without holding cs_main:
- If a variable is not accessed by other threads than
ThreadMessageHandler(), then it is irrelevant (I understand that in this case you may want to addGUARDED_BY(the newly added mutex)) - If a variable can be accessed by other threads, then it should be clear which mutex is protecting it, have
GUARDED_BY()and the mutex held when callingMaybeSendFeefilter().
src/net_processing.cpp
Outdated
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.
How guarding with a mutex guarantees "it's only accessed by a single thread"? Is the intention to say "it's only accessed by a single thread at a given point in time"? If yes, then this applies to any mutex and the comment better be removed to avoid confusion. If it means that the mutex guarantees "it's only accessed by the single thread ThreadMessageHandler()", then it is not true because any other thread can grab the mutex and access the relevant variables.
faca414 to
fa78edf
Compare
Thanks, added a
Thanks, I've replied there. Though, I think this pull makes sense on its own because it (minimally) increases the performance of blockchain-related RPCs that actually need the cs_main lock. As of now, the cs_main lock is both confusing and (minimally) harmful.
Thanks, added a comment
This is
This is initialized in the constructor, so there can't possibly be any race.
This is atomic, so there can't possibly be any race. Also, there can't be a logical race introduced in this pr, because it is not guarded by cs_main in other threads.
This is initialized before the Peer is initialized in net_processing. MaybeSendFeefilter isn't called for objects without a Peer, so there is no race introduced by this patch.
A reference as member variable is like a
Same
The lock is taken by this function. Currently we don't allow loading the active chainstate after init, so this reference can be treated like a "const pointer". If there is a patch in the future that allows re-seating the active chainstate, a lot more places need to be fixed up. My point is that re-seating shouldn't be done here, but this is a discussion for the future.
The appropriate locks are taken by this function where needed. This function doesn't care about the return value anyway, as long as it is a bool that goes from true->false.
Added comment in this pull
Same
This is only read and written by this function. Happy to add the annotation here as well, if reviewers think it is worth it?
Same
This is set in init, and treated as
A reference as member variable is like a
This function takes locks where needed and is thread-safe. |
|
Thanks for the review. Addressed all feedback. |
Since message handling is single threaded, this can simplify guarding any variables only accessed during message handling.
fa78edf to
fa37534
Compare
fa37534 to
fab56fb
Compare
|
Rebased and clarified the goals (as initially pointed out in OP) in the source code as well. The new comment also explains the history and future, so that developers don't have to use |
|
Still trivial to re-ACK as there are no code changes: |
|
After re-reading carefully the comments and the diff, I still think this is flawed because it introduces a mutex that protects "anything only accessed by" What about this:
I don't think so, a member reference vs const pointer#include <vector>
class A
{
public:
A(std::vector<int>& arg) : m1{arg}, m2{nullptr} {};
std::vector<int>& m1;
std::vector<int>* const m2;
};
int main(int, char**)
{
std::vector<int> v1;
std::vector<int> v2;
A a(v1);
// ok
a.m1 = v2;
// error: cannot assign to non-static data member 'm2' with const-qualified type 'std::vector<int> *const'
a.m2 = nullptr;
return 0;
} |
TIL |
Yes, but this is not the only use of Mutexes. In this context the mutex is not strictly needed to guard from UB. It is possible to remove the existing cs_main annotation in the function and compile without compiler warnings and run the program without UB. The goal of the mutex here is to allow only one caller to execute the function.
The assumption that I touch here is documented in the source code and has a compiler annotation, both before the changes here and after. Breaking it would require removing the compiler annotation and runtime check, as well as the comment. |
|
I mean that "anything only accessed by ...list of functions..." is "something vague which is hard to check and very easy to change or break in the future". |
|
It is trivial to check with In any case, this pull isn't changing anything about that. Previously there was a I think the discussion in this pull request should focus on the goal and how the code changes help to achieve the goal. The goal is to reduce the use of cs_main to mean "global lock for everything" and work toward cs_main to mean "validation lock for validation stuff only". |
|
Then just moving the call to Another attempt to explain: right now there are e.g. 25 variables accessed directly or indirectly by /** Message handling mutex.
* Message processing is single-threaded, so anything only accessed
* by ProcessMessage(s) or SendMessages can be guarded by this mutex,
* which guarantees it's only accessed by a single thread.at least somebody else finds the comment confusing too: #21527 (comment) |
It would require also removing the |
+1, should be ok, I will review carefully if you go that way. |
|
Closing for now. Reviewers, please ACK/NACK #24427 instead. |
There is no need for any lock to be held, because net processing is single threaded. So holding the validation lock cs_main for sending a feefilter is confusing and might even degrade blockchain-related RPC performance minimally.
MaybeSendFeefilterwill break when called in multiple threads, becauseg_filter_rounder.roundisn't thread safe (among others). Thus, annotating the function withm_mutex_message_handlingseems a nice belt-and-suspenders compiler check for now. If there are more threads in the future, a special feefilter mutex can be introduced.