Skip to content
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

Closed

Conversation

troygiorshev
Copy link
Contributor

@troygiorshev troygiorshev commented Aug 6, 2020

As it stands, RecordBytesSent is called twice, once under the cs_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 locks cs_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.

@laanwj laanwj added the P2P label Aug 6, 2020
@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 6, 2020

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

Conflicts

Reviewers, 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.

Copy link
Contributor

@jnewbery jnewbery left a 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?

src/net.cpp Outdated Show resolved Hide resolved
@troygiorshev
Copy link
Contributor Author

git range-diff master 59df5d1 e85fb2d

  • Added missing lock annotations
  • Cleaned PushMessage more thoroughly

@jnewbery
Copy link
Contributor

utACK e85fb2d

@DrahtBot DrahtBot mentioned this pull request Sep 4, 2020
3 tasks
Copy link
Member

@hebasto hebasto left a 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?

src/net.cpp Outdated Show resolved Hide resolved
src/net.cpp Outdated Show resolved Hide resolved
src/net.h Show resolved Hide resolved
src/net.cpp Outdated Show resolved Hide resolved
src/net.cpp Outdated Show resolved Hide resolved
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.
@troygiorshev
Copy link
Contributor Author

git range-diff master e85fb2d 9d47d6d

  • Clean commit improved
  • Improved various const initializations
  • Renamed the cleanup commit

@troygiorshev
Copy link
Contributor Author

Seeing that #18784 is now closed, I've reworded the description of this PR. This PR didn't end up being related to the bug #18784 was trying to fix, so this PR now stands on its own.

@troygiorshev
Copy link
Contributor Author

Can't replicate CI failure. mempool_updatefromblock.py passes in 172 seconds on my machine. (It was the longest test by a factor of 2x). Didn't investigate further.

{
LOCK(pnode->cs_vSend);
bool optimisticSend(pnode->vSendMsg.empty());
optimistic_send = pnode->vSendMsg.empty();

//log total amount of bytes per message type
Copy link
Contributor

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:

Suggested change
//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;
Copy link
Contributor

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 */

Copy link
Member

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 :)

@jnewbery
Copy link
Contributor

jnewbery commented Nov 4, 2020

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.

@troygiorshev
Copy link
Contributor Author

troygiorshev commented Nov 4, 2020

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)

@jnewbery
Copy link
Contributor

jnewbery commented Nov 4, 2020

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!

Copy link
Member

@hebasto hebasto left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

92897d4

nit, clang-format suggests:

Suggested change
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9d47d6d

nit, clang-format suggests:

Suggested change
size_t CConnman::SocketSendData(CNode *pnode) const
size_t CConnman::SocketSendData(CNode* pnode) const

Comment on lines 1447 to 1448
if (sendSet)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9d47d6d, nit:

        if (sendSet) {

@troygiorshev
Copy link
Contributor Author

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.

Copy link
Member

@maflcko maflcko left a 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)};
Copy link
Member

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.

@jnewbery
Copy link
Contributor

@MarcoFalke would you prefer an approach like https://github.com/jnewbery/bitcoin/tree/pr19673.1 ?

@maflcko
Copy link
Member

maflcko commented Dec 28, 2020

Yes, if the goal is to avoid a lock when RecordBytesSent, then simply releasing the lock before calling RecordBytesSent seems a good solution.

@jnewbery
Copy link
Contributor

@MarcoFalke - I've opened #20816 as an alternative.

@troygiorshev
Copy link
Contributor Author

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.

maflcko pushed a commit that referenced this pull request Jan 6, 2021
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
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 6, 2021
… 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
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants