-
Notifications
You must be signed in to change notification settings - Fork 37.7k
net: refactor the connection process. moving towards async connections. #10285
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
No functional change, just preparation for the following commits.
ConnectNode is only called by OpenNetworkConnection, and only in one place. Combine them for preparation of splitting them into functions that are friendlier for callbacks.
The next commits will move the connection logic out of netbase into net.
This is the next step towards asynchronous connections. The current code confuses connection steps and tends to obfuscate what's going on. Here, I've tried to create individual stages. The logic has all moved into a single function for now: - Global checks: Should any connection be attempted? - Input checks: is there something valid to connect to? - Resolving: If the address string may be valid, attempt to resolve it (if allowed by the user's dns settings and proxy config) - Proxy config - Socket creation - Connect to the destination - If proxied, attempt the remote connection - Create the node with the resulting connection With this done, resolves and connections can trigger callbacks so that a connection attempt requires no return value. At that point, they can be made asynchronous via libevent.
Vague Concept ACK. |
utACK, going to test 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.
Review turned up a few bugs that should be fixed, though most of them not your fault :/.
src/net.cpp
Outdated
if (!fNetworkActive) { | ||
return false; | ||
} | ||
if (IsLocal(addrConnect)) { |
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.
Shouldnt this only be if !pszDest?
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.
yes, thanks. Will fix.
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.
Note: Fixed in 1d94e2e.
|
||
addrman.Attempt(addrConnect, fCountFailure); | ||
if (!strDest.empty() && addrDest.IsValid()) { |
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: Move this into the previous block, and maybe move the addrDest.IsValid() check into it as well. Then you can always require addrDest.IsValid() for non-named addresses as well, which seems reasonable.
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.
Generally the point here is to have top-level ifs like if (strDest.empty()) and then put the logic inside of that, instead of repeated ifs for similar branches which are just confusing.
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 avoided coalescing on purpose. The current code does a bunch of steps at once, depending on what the input is. That's what I'm trying to undo here.
The previous block handles resolving, and will be moved out to a new function as a next step. By keeping the connection flow separated like this, it's trivial to factor out that way.
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.
Ahh, OK, didnt realize that the next step would be to pull it out.
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.
By keeping the connection flow separated like this, it's trivial to factor out that way.
Probably there is something going over my head, but I don't understand the reasoning.
Currently this does:
if (condition) {
// bunch of code
} else {
// other code
}
if (equivalent condition) {
// code that you want to factor out
}
if (equivalent condition) {
// more code that you want to factor out
}
I don't see why it would be any harder to refactor later with:
if (condition) {
// bunch of code
// code that you want to factor out
// more code that you want to factor out
} else {
// other code
}
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.
While it may make sense to lump a few actions together if they meet the same conditions now because a connection is just one long synchronous operation, my intention is to clarify which conditions really need to be met at which stage, so that we can break that operation up into smaller asynchronous ones.
Some of these tests (like whether or not we resolved successfully) will just move in an obvious way to callbacks, so I guess it makes sense to combine them here. Others though, will need to be re-checked on separate threads.
I'll clarify this in the code by setting some variables rather than repeating conditions, so that it's more clear what the intent is.
I'll rebase while I'm at it.
src/netbase.cpp
Outdated
@@ -445,6 +439,19 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe | |||
if (!SetSocketNonBlocking(hSocket, true)) | |||
return error("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); |
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: needs a string change here for __func__.
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.
Oh, also think we need a CloseSocket here.
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.
Will fix func.
SetSocketNonBlocking should take care of closing the socket, but an explicit one here wouldn't hurt either.
CloseSocket is really annoying. I'll RAII it directly after this PR.
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.
Good, CloseSocket were half the issues here :(
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.
Isn't seems strange for SetSocketNonBlocking
to try to destroy the socket if it doesn't succeed. Maybe consider removing CloseSocket
calls from SetSocketNonBlocking
after adding the RAII here.
src/net.cpp
Outdated
addrman.Attempt(addrDest, fCountFailure); | ||
|
||
if (!connected) { | ||
return false; |
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.
Not strictly your fault, but I think you're exposing a bug in Socks5 here - Socks5 fails to CloseSocket() prior to returning false in one place (Proxy username or password too long), so I think it'd be better to just CloseSocket here instead of further down to make it harder to miss such things.
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.
Agreed.
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.
+1 for the SocketCloser
struct sockaddr_storage sockaddr; | ||
socklen_t len = sizeof(sockaddr); | ||
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { | ||
LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); |
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.
Need a CloseSocket here (but, really, lets just move all the CloseSockets up to OpenNetworkConnection post-CreateSocket instead of them being sprinkled all over everywhere?).
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.
Ugh. I'll see how much work it would be to RAII it as part of this pull.
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.
Note: fixed in 34133bd.
- Only check IsLocal if pszDest is unset - Fix function name string in CreateSocket
addrman.Attempt(addrConnect, fCountFailure); | ||
// Create socket and make sure that it is closed in the event of an error | ||
SOCKET hSocket = INVALID_SOCKET; | ||
CSocketCloser sockCloser(hSocket); |
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.
It seems strange to create the CSocketCloser and then give it a reference to the socket - why not just have the CSocketCloser hold the SOCKET variable (publicly) and then set it to INVALID_SOCKET upon close/release (and return it from release) to make it feel more like a unique_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.
It seems strange to create the CSocketCloser and then give it a reference to the socket
It is strange, just like CloseSocket taking a reference is strange. But I'd think as long as one of these is taking a reference, the other probably should as well for consistency.
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 gave it a reference because several calls further down the chain set the value to -1. For example, if ConnectSocketDirectly() fails. If storing a value, CSocketCloser would attempt to close it again at destruction if we weren't careful about resetting it, which would defeat the point of using RAII.
The alternative would be to audit for CloseSocket()s (or make a full-blown RAII CSocket with Close() as a member), which would make for a much larger diff.
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.
utACK 1d94e2e.
Would be nice to squash review fixes into place.
I don't think this PR is a pure improvement because OpenNetworkConnection
is now a big monolith, but there are nice changes here and presumably there will be more cleanup in the future.
src/net.cpp
Outdated
@@ -340,7 +340,7 @@ bool CConnman::CheckIncomingNonce(uint64_t nonce) | |||
return true; | |||
} | |||
|
|||
CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure) | |||
CNode* CConnman::ConnectNode(const CAddress& addrConnect, const char *pszDest, bool fCountFailure) |
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.
In commit "net: pass a reference into ConnectNode and copy it there" (8245107)
Corresponding change to net.h is missing so this commit doesn't compile.
src/net.cpp
Outdated
if (!fNetworkActive) { | ||
return false; | ||
} | ||
if (IsLocal(addrConnect)) { |
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.
Note: Fixed in 1d94e2e.
src/netbase.cpp
Outdated
@@ -445,6 +439,19 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe | |||
if (!SetSocketNonBlocking(hSocket, true)) | |||
return error("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); |
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.
Isn't seems strange for SetSocketNonBlocking
to try to destroy the socket if it doesn't succeed. Maybe consider removing CloseSocket
calls from SetSocketNonBlocking
after adding the RAII here.
struct sockaddr_storage sockaddr; | ||
socklen_t len = sizeof(sockaddr); | ||
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { | ||
LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); |
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.
Note: fixed in 34133bd.
return true; | ||
} | ||
|
||
bool ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocket, int nTimeout) |
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.
In commit "net: split socket creation out of connection"
This commit drops static
, but the function isn't exposed publicly in a header until the next commit.
addrman.Attempt(addrConnect, fCountFailure); | ||
// Create socket and make sure that it is closed in the event of an error | ||
SOCKET hSocket = INVALID_SOCKET; | ||
CSocketCloser sockCloser(hSocket); |
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.
It seems strange to create the CSocketCloser and then give it a reference to the socket
It is strange, just like CloseSocket taking a reference is strange. But I'd think as long as one of these is taking a reference, the other probably should as well for consistency.
|
||
addrman.Attempt(addrConnect, fCountFailure); | ||
if (!strDest.empty() && addrDest.IsValid()) { |
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.
By keeping the connection flow separated like this, it's trivial to factor out that way.
Probably there is something going over my head, but I don't understand the reasoning.
Currently this does:
if (condition) {
// bunch of code
} else {
// other code
}
if (equivalent condition) {
// code that you want to factor out
}
if (equivalent condition) {
// more code that you want to factor out
}
I don't see why it would be any harder to refactor later with:
if (condition) {
// bunch of code
// code that you want to factor out
// more code that you want to factor out
} else {
// other code
}
Closing this for now. I'm working on getting this fully refactored with libevent instead. Breaking this out and serializing the logic was very helpful as an intermediary step, but I now agree that it doesn't make much sense to review/merge those changes alone. |
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of #10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3 ConnectSocket -> ConnectSocketDirectly Signed-off-by: Pasta <pasta@dashboost.org>
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3 ConnectSocket -> ConnectSocketDirectly Signed-off-by: Pasta <pasta@dashboost.org>
b887676 net: remove now-unused functions (Cory Fields) 45fd754 net: remove now-superfluous numeric resolve (Cory Fields) 2416dd7 net: separate resolving and conecting (Cory Fields) Pull request description: This is a greatly simplified version of bitcoin#10285, which only aims to address async resolving. It essentially breaks up two wrapper functions for things only used in one place (ConnectSocketDirectly/ConnectThroughProxy) in favor of calling them directly. This allows us to fully handle resolves before attempting a connection, as is necessary for async connections. As a bonus, I believe the logic is now much easier to follow than before. Tree-SHA512: f03f618107379edf3efe2a9f3e3677e8f075017ab140a0b4fdc3b8263e6beff148d55256263ab10bc2125ef089ca68e0d8e865beeae176f1eca544e769c976d3 ConnectSocket -> ConnectSocketDirectly Signed-off-by: Pasta <pasta@dashboost.org>
This untangles our current connection logic and moves toward async connections/resolves. Before merging, I'd like to construct a matrix of different connection configs, and make sure that each one has test coverage. I'm PRing now to get a feel for whether this is reviewable as-is or not.
In short, switch from "here's what we know, connect please!" to "walk through the individual connection steps in-order".
This moves the entire process into one function, but the next step will be to break out the individual steps (input checking, resolving, connection, node creation).
See the individual commit messages for more detail.