This is a piece of #21878, chopped off to ease review.
Change CNode to use a pointer to Sock instead of a bare SOCKET.
This will help mocking / testing / fuzzing more code.
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
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.
Concept and light code-review ACK ba8cc5b4e5c38e163d01b38f8a73da7c10ce0999
The first commit seems unrelated to the other two, I guess this will be useful in a later chopped off PR?
ba8cc5b4e5...846e9952cf: use FuzzedSock in ConsumeNode()
Invalidates (light) ACK from @theStack
The first commit seems unrelated to the other two...
Oh, good catch! The idea is to use FuzzedSock in ConsumeNode() which creates a CNode object. Both are defined in src/test/fuzz/util.h, but for this FuzzedSock must appear before ConsumeNode(). It was like this in a previous incarnation of #21878 but in the latest push, for some reason, I changed it to use nullptr 🤦
Light re-ACK 78cda19bb859be69a8bdb006c210b8d47d56acc9
(as per git range-diff ba8cc5b4...78cda19b)
Concept ACK. CI failures are unrelated.
605 | @@ -607,7 +606,16 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) 606 | in_addr ipv4AddrPeer; 607 | ipv4AddrPeer.s_addr = 0xa0b0c001; 608 | CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); 609 | - std::unique_ptr<CNode> pnode = std::make_unique<CNode>(0, NODE_NETWORK, INVALID_SOCKET, addr, /*nKeyedNetGroupIn=*/0, /*nLocalHostNonceIn=*/0, CAddress{}, /*pszDest=*/std::string{}, ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false); 610 | + std::unique_ptr<CNode> pnode = std::make_unique<CNode>(0, 611 | + NODE_NETWORK, 612 | + nullptr,
c702f77a in net_tests and denialofservice_tests named args might be nice for these nullptr params
/*sock=*/nullptr,
Done (all over the place)
296 | @@ -259,7 +297,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N 297 | { 298 | const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegral<NodeId>()); 299 | const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS); 300 | - const SOCKET socket = INVALID_SOCKET; 301 | + auto sock = std::make_shared<FuzzedSock>(fuzzed_data_provider);
c702f77a1f5e5988a0c7cee7ef3f7cfc63a9ea1b feel free to ignore: not sure if there is a reason to not keep it const (and use braced initialization)
const auto sock{std::make_shared<FuzzedSock>(fuzzed_data_provider)};
Yes, it can be const, this is the std::shared_ptr itself, not the contained FuzzedSock.
Braced initialization is pointless with auto since there is nothing to check against. I find braced initialization less readable than = so I left it as it is.
ACK 0ec56c651ee87d10e7c9b730dcdfffe26b95d88c
ACK d22a47754208f18d5bd674c28458bb82bbd1b9b9
ACK d22a477
Concept ACK. Just a question: using a shared pointer instead of a unique pointer muddles the concept of ownership a bit (e.g. it makes it harder to reason about the lifecycle). Do we need one?
using a shared pointer instead of a unique pointer muddles the concept of ownership a bit ...
Oh well, this is a bit subtle, maybe some comments are warranted 🤔. It is needed to resolve #21744. That issue contains some details on "why shared_ptr".
d22a477542...1477f3aa73: rebase due to conflicts, elaborate the comment for CNode::m_sock
Invalidates ACKs from @jonatack, @stratospher
Previously invalidated ACK from @theStack
@laanwj, added this comment in the hopes to make it more clear:
`shared_ptr` (instead of `unique_ptr`) is used to avoid premature close of
the underlying file descriptor by one thread while another thread is
poll(2)-ing it for activity.
[@see](/bitcoin-bitcoin/contributor/see/) [#21744](/bitcoin-bitcoin/21744/) for details.
ACK 1477f3aa73c4bec6e04682be63071592a032d506
Light re-ACK 1477f3aa73c4bec6e04682be63071592a032d506
(as per git range-diff 78cda19b...1477f3aa)
ACK 1477f3a.
tACK 1477f3a
Great explanation of why use shared_ptr instead of std::unique_ptr for m_sock
This way it can be used in `ConsumeNode()`.
1477f3aa73...af7fc2864e: rebase due to conflicts
Invalidates ACKs from @jonatack, @theStack, @stratospher, @w0xlt
504 | @@ -505,7 +505,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo 505 | if (!addr_bind.IsValid()) { 506 | addr_bind = GetBindAddress(sock->Get()); 507 | } 508 | - CNode* pnode = new CNode(id, nLocalServices, sock->Release(), addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type, /* inbound_onion */ false); 509 | + CNode* pnode = new CNode(id, nLocalServices, std::move(sock), addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type, /* inbound_onion */ false);
nit if you retouch
CNode* pnode = new CNode(id, nLocalServices, std::move(sock), addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type, /*inbound_onion=*/false);
Done (in a separate commit to avoid combining style changes and logical changes in one commit).
I find the line wrapping style changes more difficult to read than before and the trivial named argument fixup could be done while touching the line in net: use Sock in CNode but ok.
1196 | @@ -1197,7 +1197,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, 1197 | } 1198 | 1199 | const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end(); 1200 | - CNode* pnode = new CNode(id, nodeServices, sock->Release(), addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", ConnectionType::INBOUND, inbound_onion); 1201 | + CNode* pnode = new CNode(id, nodeServices, std::move(sock), addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", ConnectionType::INBOUND, inbound_onion);
nit if you retouch this line
CNode* pnode = new CNode(id, nodeServices, std::move(sock), addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, /*addrNameIn=*/"", ConnectionType::INBOUND, inbound_onion);
Done (in a separate commit to avoid combining style changes and logical changes in one commit).
I find the line wrapping style changes more difficult to read than before and the trivial named argument fixup could be done while touching the line in net: use Sock in CNode but ok.
ACK af7fc2864e6fb9323fbefc9a9080ba23057143a9 :socks:
803 | + LOCK(node.m_sock_mutex); 804 | + if (!node.m_sock) { 805 | break; 806 | - nBytes = send(node.hSocket, reinterpret_cast<const char*>(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); 807 | + } 808 | + nBytes = node.m_sock->Send(reinterpret_cast<const char*>(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
Probably a silly question: LOCK(node.m_sock_mutex); applies even to node.m_sock->Send(...) operation but then here one can return contained sockets out of the function.
I understand that m_sock_mutex guards only m_sock member read & write operations but not necessarily the contained socket. Is that correct or not?
That is a good question.
Before this PR the mutex protects the value of the socket variable - that is, to make sure it does not change from e.g. 7 (it is a file descriptor) to INVALID_SOCKET in the middle of constructs like:
if (socket != INVALID_SOCKET) {
... do something with the socket, assuming it is valid ...
}
After this PR, it is the same with the exception that the variable is not a bare integer anymore, but a shared_ptr and an empty such pointer is used to designate what was designated before by INVALID_SOCKET.
2996 | @@ -2994,10 +2997,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const 2997 | m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer()); 2998 | } 2999 | 3000 | -CNode::~CNode() 3001 | -{ 3002 | - CloseSocket(hSocket); 3003 | -} 3004 | +CNode::~CNode() {}
pls move to header and use = default (or document specific reason why it must be in cpp)
I Just removed the destructor, it is not necessary now.
LGTM, one request
Change `CNode` to use a pointer to `Sock` instead of a bare `SOCKET`.
This will help mocking / testing / fuzzing more code.
-BEGIN VERIFY SCRIPT-
sed -i -e 's/cs_hSocket/m_sock_mutex/g' $(git grep -l cs_hSocket)
-END VERIFY SCRIPT-
af7fc2864e...ef5014d256: address suggestions
Invalidates ACK from @jonatack
Previously invalidated ACKs from @theStack, @stratospher, @w0xlt
re-ACK ef5014d256638735b292672c774446db4003f03b changes since last review are the removal of an unneeded dtor and the addition of a style commit
reACK ef5014d
2996 | @@ -2994,11 +2997,6 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const 2997 | m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer()); 2998 | } 2999 | 3000 | -CNode::~CNode()
Apologies if this is a silly question but just wanted to clarify - removing this destructor, it's implicit that the shared_ptr for Sock will be reset and the Sock's destructor will responsible for closing the socket?
See https://en.cppreference.com/w/cpp/language/destructor "Implicitly-declared destructor".
The default destructor for CNode will call all the destructors of all of it's members. The destructor for shared_ptr will reduce the reference counter, and then if that reference counter is zero, the destructor for Sock will be called. If the RC is not zero, then the Sock will continue to exist on the heap (presumably being used by another thread), until the owner of that shared_ptr calls the destructor.
utACK ef5014d256638735b292672c774446db4003f03b, I have reviewed the code, and believe it makes sense to merge
Since my last ACK was only "light" and happened weeks ago, so I decided to do a full review of this PR again instead of looking at the range-diff. Changes LGTM!
Cod-review ACK ef5014d256638735b292672c774446db4003f03b