-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Introduce SockMan ("lite"): low-level socket handling for HTTP #32747
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. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32747. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. 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. LLM Linter (✨ experimental)Possible typos and grammar issues:
drahtbot_id_5_m |
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
55a329a to
2d79cd7
Compare
w0xlt
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.
Concept ACK.
Are the functions in this PR to replace Connam functions like CConnman::BindListenPort, CConnman::InitBinds, etc...?
In this PR they are just protocol-agnostic copies of those ConnMan functions. Just the socket stuff without the Bitcoin stuff. We could still proceed with the reactor in #30988 after this is merged and actually replace the socket stuff in ConnMan with SockMan but this PR is minimized with the focus being the socket needs of the HTTP server in #32061 |
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.
Approach ACK 5f79411
src/test/sockman_tests.cpp
Outdated
| TestSockMan sockman; | ||
|
|
||
| // This address won't actually get used because we stubbed CreateSock() | ||
| const std::optional<CService> addr_bind{Lookup("0.0.0.0", 0, 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.
9299d5d SockMan: introduce class and implement binding to listening socket
nit, ensure Lookup() succeeded before continuing because below addr_bind.value() is used unconditionally:
BOOST_REQUIRE(addr.has_value());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.
👍
src/common/sockman.cpp
Outdated
| /** Get the bind address for a socket as CService. */ | ||
| static CService GetBindAddress(const Sock& sock) | ||
| { | ||
| CService addr_bind; | ||
| struct sockaddr_storage sockaddr_bind; | ||
| socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); | ||
| if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { | ||
| addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind, sockaddr_bind_len); | ||
| } else { | ||
| LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); | ||
| } | ||
| return addr_bind; | ||
| } |
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.
GetBindAddress() is the same as in net.cpp. It is nice to have this PR remove 0 lines, but I think it is better to make an exception and move the function from net.cpp to netbase.{h,cpp} and use that from both net.cpp and common/sockman.cpp.
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 yes thanks, done by inserting a move-only commit
| SocketHandlerConnected(io_readiness); | ||
|
|
||
| // Accept new connections from listening sockets. | ||
| SocketHandlerListening(io_readiness.events_per_sock); |
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.
674a5ff SockMan: handle connected sockets: read data from socket
In the commit message: s/conencts/connects/
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.
👍
src/test/sockman_tests.cpp
Outdated
| // Received data is written here by the SockMan I/O thread | ||
| // and tested by the main thread. | ||
| Mutex m_received_mutex; | ||
| std::vector<uint8_t> m_received; |
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.
674a5ff SockMan: handle connected sockets: read data from socket
m_received would better be per-client:
std::unordered_map<Id, std::vector<uint8_t>> m_received;and then adjust EventGotData() to plug the data in the client's slot:
m_received[id].assign(data.begin(), data.end());and GetReceivedData() to get the data for the client:
GetReceivedData(Id id)
{
return m_received[id];
}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 catch, taken. I was hoping to get away with only ever using one client in this test but this makes more sense for coverage anyway ;-)
| return m_connected.erase(id) > 0; | ||
| } | ||
|
|
||
| ssize_t SockMan::SendBytes(Id id, |
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.
3f796b6 SockMan: handle connected sockets: write data to socket
In the commit message: s/recevied/received/
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.
👍
pinheadmz
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.
Rebase to address review by @vasild THANKS!
src/test/sockman_tests.cpp
Outdated
| TestSockMan sockman; | ||
|
|
||
| // This address won't actually get used because we stubbed CreateSock() | ||
| const std::optional<CService> addr_bind{Lookup("0.0.0.0", 0, 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.
👍
src/common/sockman.cpp
Outdated
| /** Get the bind address for a socket as CService. */ | ||
| static CService GetBindAddress(const Sock& sock) | ||
| { | ||
| CService addr_bind; | ||
| struct sockaddr_storage sockaddr_bind; | ||
| socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); | ||
| if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { | ||
| addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind, sockaddr_bind_len); | ||
| } else { | ||
| LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); | ||
| } | ||
| return addr_bind; | ||
| } |
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 yes thanks, done by inserting a move-only commit
src/test/sockman_tests.cpp
Outdated
| // Received data is written here by the SockMan I/O thread | ||
| // and tested by the main thread. | ||
| Mutex m_received_mutex; | ||
| std::vector<uint8_t> m_received; |
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 catch, taken. I was hoping to get away with only ever using one client in this test but this makes more sense for coverage anyway ;-)
| SocketHandlerConnected(io_readiness); | ||
|
|
||
| // Accept new connections from listening sockets. | ||
| SocketHandlerListening(io_readiness.events_per_sock); |
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.
👍
| return m_connected.erase(id) > 0; | ||
| } | ||
|
|
||
| ssize_t SockMan::SendBytes(Id id, |
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.
👍
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.
ACK 3e7abce
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.
Began reviewing, then realized that I was reviewing the same code twice with the two "modernize" commits following "original" commits.
Propose to either (1) make clear in the commit message where the code is being pulled from, and that it will be re-styled afterward, or (2) squash the modernization commits into the previous ones. Thereby easing life a bit for your reviewers :)
| #include <pubkey.h> | ||
| #include <stdexcept> | ||
| #include <test/util/random.h> | ||
| #include <test/util/net.h> |
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.
535daaf nit, sort
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.
fixed, thanks
src/test/sockman_tests.cpp
Outdated
| @@ -0,0 +1,152 @@ | |||
| // Copyright (c) 2021-2022 The Bitcoin Core developers | |||
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.
535daaf (edit, or is this due to the code already existing, if yes, please mention this in the commit message)
| // Copyright (c) 2021-2022 The Bitcoin Core developers | |
| // Copyright (c) 2025-present The Bitcoin Core developers |
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.
Thanks, removed the years entirely which seems to be the style for new files.
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.
ACK e007e1b57d5d42c2a8d932d5b91eec8a3ca76e14
l0rinc
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.
I'm not immersed in this to give a meaningful ack yet, but please see a few of my comments.
My main observation is that we're introducing tests and new code at the same time and I find it hard to follow what the relation between the two is - how do I tell if they're doing what they're supposed to.
Given that this is meant to replace libevent, I wonder if we could add either a wrapper to libevent and cover it with tests as a first step (could be done in a separate standalone PR as well) and slowly replace the methods we rely on from libevent with our own implementation while the tests keep passing and as a last step delete libevent abstraction completely. That would reassure me personally that the new code isn't just dead weight we're adding to the code, but that we're slowly strangling out a dependency with small focused changes covered with tests where I can debug both the old or new behavior and make absolutely sure they behave in the same way.
I would also prefer new code to be fully covered with tests, especially since none of the error cases and ipv6 part seem to be.
src/common/sockman.cpp
Outdated
| return false; | ||
| } | ||
|
|
||
| int one{1}; |
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: I know this was the original name as well, but variables (by their name) should reflect the purpose of their usage, not their value. ipv6_only{1} or tcp_nodelay{1} might make the context more obvious (if that's indeed what they're goal is.
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 call, this value is setting a boolean option to true so I made the variable name more explicit, in the "modernize" commit.
src/common/sockman.cpp
Outdated
| err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort())); | ||
| 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.
if error message is tied to a false return value, can we return std::optional<bilingual_str> instead of having both a return value and an output parameter?
std::optional<bilingual_str> BindAndStartListening(const CService& to)
{
sockaddr_storage storage;
socklen_t len{sizeof(storage)};
if (!to.GetSockAddr(reinterpret_cast<sockaddr*>(&storage), &len)) {
return Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort()));
}
...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 call, will use util::Result here and remove the error string as an "out" parameter.
src/netbase.cpp
Outdated
| CService addr_bind; | ||
| struct sockaddr_storage sockaddr_bind; | ||
| socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); | ||
| if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { |
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.
whenreinterpret_cast<sockaddr*>(&storage) as in SockMan::BindAndStartListening and when c-style cast?
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 idea, i'll insert a new "modernize" commit after the move-only GetBindAddress commit
src/common/sockman.cpp
Outdated
| // and enable it by default or not. Try to enable it, if possible. | ||
| if (to.IsIPv6()) { | ||
| #ifdef IPV6_V6ONLY | ||
| if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) == SOCKET_ERROR) { |
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.
are we testing this code in any way?
And can we enable compile and be type-checking and make it if constexpr instead of using macros?
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 don't think we could reliably test these paths in ci because such socket operations depend so much on the platform.
We could create additional child classes of DynSock for the unit tests, so we have mock socket variants that fail each specific option, but I'm not sure we'd get enough value from those tests to be worth the work. Could be a follow up.
src/common/sockman.cpp
Outdated
| sockaddr_storage storage; | ||
| socklen_t len{sizeof(storage)}; | ||
| if (!to.GetSockAddr(reinterpret_cast<sockaddr*>(&storage), &len)) { | ||
| err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort())); |
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.
seems to me we're not testing any of these unhappy paths - could we do that or is it too much work?
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'll add coverage for as many as I can. But I think all I can do from this level is try to bind to an address with an invalid family (i.e. "NET_ONION").
src/common/sockman.cpp
Outdated
|
|
||
| // According to the internet TCP_NODELAY is not carried into accepted sockets | ||
| // on all platforms. Set it again here just to be sure. | ||
| const int on{1}; |
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's the difference between this an the one (without const) above? Can we unify their naming so that they represent the meaning of what they're storing instead of the value?
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 call ill set a static constexpr for all the times we do this in the module, overriding https://github.com/bitcoin/bitcoin/pull/32747/files#r2420892943
| // on all platforms. Set it again here just to be sure. | ||
| const int on{1}; | ||
| if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { | ||
| LogDebug(BCLog::NET, "connection from %s: unable to set TCP_NODELAY, continuing anyway\n", |
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: most logs don't need a trailing newline anymore
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.
that's nice, thanks
src/common/sockman.cpp
Outdated
| std::unique_ptr<Sock> SockMan::AcceptConnection(const Sock& listen_sock, CService& addr) | ||
| { | ||
| // Make sure we only operate on our own listening sockets | ||
| Assume(std::ranges::find_if(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; }) != m_listen.end()); |
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.
We don't actually need the value here, just that it exists, right?
| Assume(std::ranges::find_if(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; }) != m_listen.end()); | |
| Assume(std::ranges::any_of(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; })); |
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
| // Create socket for listening for incoming connections | ||
| sockaddr_storage storage; | ||
| socklen_t len{sizeof(storage)}; | ||
| if (!to.GetSockAddr(reinterpret_cast<sockaddr*>(&storage), &len)) { |
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 understand that we need different sizes for ipv4 and ipv6, hence these ugly polimorphism-mimicking structures, but I'm wondering if there's a way to hide this ugliness in the C++ code with either decicated structures or something lke std::variant<sockaddr_in, sockaddr_in6>
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.
This is a reasonable note but I think it's out of scope for this PR, since GetSockAddr() is used so many other places in the code. Wrapping sockaddr_storage for C++ style could also be applied to Accept(), Connect(), Bind() and probably a few others. I'll open a new PR for that cleanup, similar to #33378
| } | ||
|
|
||
| int flags{MSG_NOSIGNAL | MSG_DONTWAIT}; | ||
| #ifdef MSG_MORE |
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.
can we have some explanation here, it's not self-explanatory. The source location had:
// We rely on the 'more' value returned by GetBytesToSend to correctly predict whether more // bytes are still to be sent, to correctly set the MSG_MORE flag. As a sanity check, // verify that the previously returned 'more' was correct.
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.
sure, added.
e007e1b to
03ce73a
Compare
pinheadmz
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.
Rebase to 03ce73a820b485fbdbb854d9d3947ff1e6d87880 address conflicts with master, as well as silent conflict from #33378. Address feedback from @l0rinc THANKS -- mostly improving "modernization" of the code borrowed from Connman.
There is a bigger question about the approach in this PR, which adds code that is dead for now but required for #32061. I am going to explore other ways to introduce this code more incrementally, or maybe we can start off by writing SockMan as a libevent wrapper, and then replace libevent with our own code once the libevent behavior is asserted by tests.
src/common/sockman.cpp
Outdated
| return false; | ||
| } | ||
|
|
||
| int one{1}; |
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 call, this value is setting a boolean option to true so I made the variable name more explicit, in the "modernize" commit.
| // Create socket for listening for incoming connections | ||
| sockaddr_storage storage; | ||
| socklen_t len{sizeof(storage)}; | ||
| if (!to.GetSockAddr(reinterpret_cast<sockaddr*>(&storage), &len)) { |
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.
This is a reasonable note but I think it's out of scope for this PR, since GetSockAddr() is used so many other places in the code. Wrapping sockaddr_storage for C++ style could also be applied to Accept(), Connect(), Bind() and probably a few others. I'll open a new PR for that cleanup, similar to #33378
src/common/sockman.cpp
Outdated
| err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort())); | ||
| 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.
Good call, will use util::Result here and remove the error string as an "out" parameter.
src/common/sockman.cpp
Outdated
| sockaddr_storage storage; | ||
| socklen_t len{sizeof(storage)}; | ||
| if (!to.GetSockAddr(reinterpret_cast<sockaddr*>(&storage), &len)) { | ||
| err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort())); |
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'll add coverage for as many as I can. But I think all I can do from this level is try to bind to an address with an invalid family (i.e. "NET_ONION").
src/common/sockman.cpp
Outdated
| // and enable it by default or not. Try to enable it, if possible. | ||
| if (to.IsIPv6()) { | ||
| #ifdef IPV6_V6ONLY | ||
| if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) == SOCKET_ERROR) { |
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 don't think we could reliably test these paths in ci because such socket operations depend so much on the platform.
We could create additional child classes of DynSock for the unit tests, so we have mock socket variants that fail each specific option, but I'm not sure we'd get enough value from those tests to be worth the work. Could be a follow up.
| // on all platforms. Set it again here just to be sure. | ||
| const int on{1}; | ||
| if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { | ||
| LogDebug(BCLog::NET, "connection from %s: unable to set TCP_NODELAY, continuing anyway\n", |
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.
that's nice, thanks
src/common/sockman.cpp
Outdated
| std::unique_ptr<Sock> SockMan::AcceptConnection(const Sock& listen_sock, CService& addr) | ||
| { | ||
| // Make sure we only operate on our own listening sockets | ||
| Assume(std::ranges::find_if(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; }) != m_listen.end()); |
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
src/common/sockman.cpp
Outdated
|
|
||
| // According to the internet TCP_NODELAY is not carried into accepted sockets | ||
| // on all platforms. Set it again here just to be sure. | ||
| const int on{1}; |
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 call ill set a static constexpr for all the times we do this in the module, overriding https://github.com/bitcoin/bitcoin/pull/32747/files#r2420892943
| } | ||
|
|
||
| int flags{MSG_NOSIGNAL | MSG_DONTWAIT}; | ||
| #ifdef MSG_MORE |
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.
sure, added.
src/netbase.cpp
Outdated
| CService addr_bind; | ||
| struct sockaddr_storage sockaddr_bind; | ||
| socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); | ||
| if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { |
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 idea, i'll insert a new "modernize" commit after the move-only GetBindAddress commit
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.
ACK 03ce73a820b485fbdbb854d9d3947ff1e6d87880
|
My $.02 and repeating what I've said elsewhere... I still just don't think that this is the correct level of abstraction. IMO socket-based protocols are different enough to warrant their own logic. Either that, or the abstraction would need to provide a bazillion options and flags, ala libevent/libev/libuv/libkj/etc. For ex: presumably in order to remain generic,
I'd rather see this and a custom HTTP server collapsed together into a tight implementation. If another another socket-based protocol is implemented in the future, that would be a good time to abstract away the commonalities. But the fact that CConnman isn't a good a good candidate for abstraction is a hint to me that the next one won't be either. |
|
@theuni I hear you, maybe I should re write the PR description: The purpose is not to establish a generic socket manager but to just advance the bigger goal of replacing libevent with #32061. In that context, this PR is just the first 11 commits of the HTTP server replacement, broken off for more focused unit testing and review (which has all been great so far). So with that in mind, what do you recommend? Is it higher level organization like removing the abstract class stuff and moving all the code into I'm waiting for approval on this socket chunk before rebasing #32061 but I could also ditch this initial PR and just focus all the work on the bigger picture. |
Introduce a new low-level socket managing class `SockMan`. BindListenPort() is copied from CConnMan in net.cpp and will be modernized in the next commit. Unit-test it with a new class `SocketTestingSetup` which mocks `CreateSock()` and will enable mock client I/O in future commits. `SockMan` and `SocketTestingSetup` are designed to be generic and reusbale for higher-level network protocol implementation and testing. Co-authored-by: Vasil Dimov <[email protected]>
It was copied verbatim from `CConnman::BindListenPort()` in the previous commit. Modernize its variables and style and log the error messages from the caller. Also categorize the informative messages to the "net" category because they are quite specific to the networking layer. Co-authored-by: Vasil Dimov <[email protected]>
AcceptConnection() is mostly copied from CConmann in net.cpp and will be modernized in the following commit. Co-authored-by: Vasil Dimov <[email protected]>
Co-authored-by: Vasil Dimov <[email protected]>
Replace the C-style casting with C++ reinterpret_cast
Socket handling methods are copied from CConnMan: `CConnman::GenerateWaitSockets()` goes to `SockMan::GenerateWaitSockets()`. `CConnman::ThreadSocketHandler()` and `CConnman::SocketHandler()` are combined into `SockMan::ThreadSocketHandler()`. `CConnman::SocketHandlerListening()` goes to `SockMan::SocketHandlerListening()`. Co-authored-by: Vasil Dimov <[email protected]>
`CConnman::SocketHandlerConnected()` copied to `SockMan::SocketHandlerConnected()`. Testing this requires adding a new feature to the SocketTestingSetup, inserting a "request" payload into the mock client that connects to us. Co-authored-by: Vasil Dimov <[email protected]>
Sockets-touching bits from `CConnman::SocketSendData()` copied to `SockMan::SendBytes()`. Testing this requires adding a new feature to the SocketTestingSetup, returning the DynSock I/O pipes from the mock socket so the received data can be checked. Co-authored-by: Vasil Dimov <[email protected]>
Copy from some parts of `CConnman::SocketHandlerConnected()` and `CConnman::ThreadSocketHandler()` to: `EventIOLoopCompletedForOne(id)` and `EventIOLoopCompletedForAll()`. Co-authored-by: Vasil Dimov <[email protected]>
03ce73a to
40c9700
Compare
|
rebased on master to fix conflict but also converted to draft. The plan is now to integrate this I/O loop directly with the new http server. So I will rebase #32061 on this branch to include the feedback the socket stuff has received, and then combine this sockman with the http server and remove the abstraction. |
|
Closing - these commits are all merged into #32061 including all the great review feedback <3 |
Introduces a new low-level socket manager
SockManas an abstract class with virtual functions for implementing higher-level networking protocols like HTTP. This is the next step in #31194This is a minimal, alternative version of #30988 ("Split CConnman") without any changes to working code (P2P is not affected). It adds a stripped-down version of the
SockManintroduced in that pull request that implements only what is needed for the HTTP server implemented in #32061 (i.e. no I2P stuff and for now, no outbound connection stuff). Exclusions from the originalSockManpull request can be checked with:The commit order has been flipped quite a bit because the original PR incrementally pulls logic out of
netwheras this PR builds a new system from the bottom-up. Otherwise I tried to keep all theSockMancode in order so reviewers of the original PR would be familiar with it.It also adds unit tests by introducing a
SocketTestingSetupwhich mocks network sockets by returning a queue ofDynSockfromCreateSock(). HTTP server tests in #32061 will be rebased on this framework as well.