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
Move cs_vSend into SocketSendData and resolve RecordBytesSent lock inconsistency #19673
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. |
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.
Concept ACK.
It seems to me that there are a few additional members of CNode
that should be guarded by the cs_vSend
lock:
mapSendBytesPerMsgCmd
nSendSize
nSendOffset
I don't think we want any of those to be accessed/updated while another thread is locking cs_vSend
and updating vSendMsg
and nSendBytes
. If you agree, what do you think about updating them in this PR?
59df5d1
to
e85fb2d
Compare
|
utACK e85fb2d |
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.
Concept ACK.
The first commit message could be prefixed with refactor:
, no?
This commit brings PushMessage in line with the style guide, with a few simple renames, some changes to if statements, and the removal of an unnecessary "== true". Easter Egg - serializedHeader has the weird "in between" naming convention: no szHungarianNotation but still camelCase.
These variables are already guarded by cs_vSend, they are just missing the annotations.
SocketSendData requires cs_vSend to be locked, but it places this responsibility with the caller. This is unnecessary, unclear, and has caused us to accidentally add RecordBytesSent under the lock. This commit fixes both problems. Justification is needed in the case of PushMessage. It is ok that we let go of cs_vSend for a moment before locking it again and calling SocketSendData. In the case where optimistic_send == false, this lock is let go of for a long time between adding messages to vSendMsg and the call to SocketSendData (in SocketHandler). As corrected in the comment change, optimistic_send represents whether vSendMsg *was* empty, not *is* empty (as, of course, we've just added to it). If SocketHandler grabs cs_vSend in the middle then SocketSendData will be empty when we call it in PushMessage. As suggested by the "if (bytes_sent)" check, this is fine.
e85fb2d
to
9d47d6d
Compare
|
Can't replicate CI failure. |
{ | ||
LOCK(pnode->cs_vSend); | ||
bool optimisticSend(pnode->vSendMsg.empty()); | ||
optimistic_send = pnode->vSendMsg.empty(); | ||
|
||
//log total amount of bytes per message type |
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.
If you retouch the branch, you may as well touch every line in this function:
//log total amount of bytes per message type | |
// Log total amount of bytes per message type |
size_t nSendSize{0}; // total size of all vSendMsg entries | ||
size_t nSendOffset{0}; // offset inside the first vSendMsg already sent | ||
size_t nSendSize GUARDED_BY(cs_vSend){0}; // total size of all vSendMsg entries | ||
size_t nSendOffset GUARDED_BY(cs_vSend){0}; // offset inside the first vSendMsg already sent | ||
uint64_t nSendBytes GUARDED_BY(cs_vSend){0}; | ||
std::deque<std::vector<unsigned char>> vSendMsg GUARDED_BY(cs_vSend); | ||
RecursiveMutex cs_vSend; |
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 could use a comment. Something like:
//* Guards the send buffer and statistics about data sent on this connection */
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.
The GUARDED_BY
annotation seems quite self-documented; especially, when the mutex and all of the data members protected by it are nicely grouped :)
utACK 9d47d6d Justification for the change in commit Move cs_vSend lock into SocketSendData looks good to me. I'd personally remove the easter egg from the cleanup: Clean PushMessage commit log, but it's fine either way. |
Thanks for the review @jnewbery! I'll leave your suggestions for now, and I'll look at adding them if there are non-stylistic changes needed for this PR. My usage of list initialization comes from this stackoverflow. The C++ Core Guidelines talk about this as well, ES.23. Edit: Another article about list initialization for those interested (link) |
Thanks for the links. I retract my vote for = initialization! |
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.
ACK 9d47d6d, I have reviewed the code and it looks OK, I agree it can be merged.
@@ -759,8 +759,8 @@ class CNode | |||
// socket | |||
std::atomic<ServiceFlags> nServices{NODE_NONE}; | |||
SOCKET hSocket GUARDED_BY(cs_hSocket); | |||
size_t nSendSize{0}; // total size of all vSendMsg entries | |||
size_t nSendOffset{0}; // offset inside the first vSendMsg already sent | |||
size_t nSendSize GUARDED_BY(cs_vSend){0}; // total size of all vSendMsg entries |
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.
nit, clang-format suggests:
size_t nSendSize GUARDED_BY(cs_vSend){0}; // total size of all vSendMsg entries | |
size_t nSendSize GUARDED_BY(cs_vSend){0}; // total size of all vSendMsg entries |
@@ -747,8 +747,9 @@ void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vec | |||
CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr}; | |||
} | |||
|
|||
size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_vSend) | |||
size_t CConnman::SocketSendData(CNode *pnode) const |
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.
nit, clang-format suggests:
size_t CConnman::SocketSendData(CNode *pnode) const | |
size_t CConnman::SocketSendData(CNode* pnode) const |
if (sendSet) | ||
{ |
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.
9d47d6d, nit:
if (sendSet) {
Thanks for the ack hebasto! I now have an ack from you and jnewbery. Both of you gave style suggestions (which I agree with). Since the suggestions are both further improvements to existing code (as opposed to style problems I'm adding myself, with the exception of the spaces before the comment) I'm going to leave them until a functional change is needed. |
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.
What is the goal of this change? If it is to avoid a lock when RecordBytesSent
, you can simply release the lock before calling RecordBytesSent
. I don't think this justifies the other behaviour changes.
if (bytes_sent) RecordBytesSent(bytes_sent); | ||
// If write queue was empty, attempt "optimistic write" | ||
if (optimistic_send) { | ||
const size_t bytes_sent{SocketSendData(pnode)}; |
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 is this change needed? Previously we'd optimistically write only one message, now we might write more than one message when other messages have been pushed by other threads.
@MarcoFalke would you prefer an approach like https://github.com/jnewbery/bitcoin/tree/pr19673.1 ? |
Yes, if the goal is to avoid a lock when RecordBytesSent, then simply releasing the lock before calling RecordBytesSent seems a good solution. |
@MarcoFalke - I've opened #20816 as an alternative. |
I was too preoccupied with whether or not I could, I didn't stop to think whether or not I should :) Closing this in favor of #20816. |
378aedc [net] Add cs_vSend lock annotations (John Newbery) 6732545 [net] Move RecordBytesSent() call out of cs_vSend lock (John Newbery) Pull request description: RecordBytesSent() does not require cs_vSend to be locked, so reduce the scope of cs_vSend. Also correctly annotate the CNode data members that are guarded by cs_vSend. This is a simpler alternative to #19673. ACKs for top commit: jnewbery: ok, reverting to commit 378aedc which has two ACKs already. Any style issues can be fixed up in future PRs. troygiorshev: ACK 378aedc theStack: re-ACK 378aedc MarcoFalke: review ACK 378aedc 🔌 Tree-SHA512: e9cd6c472b7e1479120c1bf2d1c640cf6d18c7d589a5f9b7dfc4875e5790adaab403a7a1b945a47e79e7249a614b8583270e4549f89b22e8a9edb2e4818b0d07
… lock 378aedc [net] Add cs_vSend lock annotations (John Newbery) 6732545 [net] Move RecordBytesSent() call out of cs_vSend lock (John Newbery) Pull request description: RecordBytesSent() does not require cs_vSend to be locked, so reduce the scope of cs_vSend. Also correctly annotate the CNode data members that are guarded by cs_vSend. This is a simpler alternative to bitcoin#19673. ACKs for top commit: jnewbery: ok, reverting to commit 378aedc which has two ACKs already. Any style issues can be fixed up in future PRs. troygiorshev: ACK 378aedc theStack: re-ACK 378aedc MarcoFalke: review ACK 378aedc 🔌 Tree-SHA512: e9cd6c472b7e1479120c1bf2d1c640cf6d18c7d589a5f9b7dfc4875e5790adaab403a7a1b945a47e79e7249a614b8583270e4549f89b22e8a9edb2e4818b0d07
As it stands,
RecordBytesSent
is called twice, once under thecs_vSend
lock, and once not. This PR moves both calls out of the lock.In doing this, I've made a change to SocketSendData. Before, it required the caller to lock
cs_vSend
, now it lockscs_vSend
itself. The only case where this makes a difference is when performing an optimistic write (where MessageHandler is the thread that calls SocketSendData, not SocketHandler). As justified in the commit message, I don't think this will cause any problems, and subjectively I think this is an improvement to how we're using the locks. I'm more than happy to be told that I'm wrong :)Additionally this PR brings PushMessage into line with the style guide, and does verious other cleanups.
This PR was originally related to #18784 but it now stands on its own.