Skip to content

Conversation

@pinheadmz
Copy link
Member

Introduces a new low-level socket manager SockMan as an abstract class with virtual functions for implementing higher-level networking protocols like HTTP. This is the next step in #31194

This 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 SockMan introduced 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 original SockMan pull request can be checked with:

git diff vasild/sockman \
src/common/sockman.h \
src/common/sockman.cpp

The commit order has been flipped quite a bit because the original PR incrementally pulls logic out of net wheras this PR builds a new system from the bottom-up. Otherwise I tried to keep all the SockMan code in order so reviewers of the original PR would be familiar with it.

It also adds unit tests by introducing a SocketTestingSetup which mocks network sockets by returning a queue of DynSock from CreateSock(). HTTP server tests in #32061 will be rebased on this framework as well.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 13, 2025

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

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32747.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK w0xlt, hodlinator
Stale ACK vasild

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32015 (net: replace manual reference counting of CNode with shared_ptr by vasild)
  • #30988 (Split CConnman by vasild)

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:

  • an unique -> a unique [“unique” begins with a consonant sound; “a unique” is grammatically correct]
  • datasent -> data sent [two words; “data sent” is the intended phrase]

drahtbot_id_5_m

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task MSan, depends: https://github.com/bitcoin/bitcoin/runs/44070031695
LLM reason (✨ experimental): MemorySanitizer detected use of uninitialized memory during sockman_tests, causing CI failure.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@Sjors
Copy link
Member

Sjors commented Jun 16, 2025

I was able to switch Sjors#50 to use this instead of #30988 without have to change anything. So it makes sense to me to focus on this PR first, as it's smaller. Thoughts @vasild?

Copy link
Contributor

@w0xlt w0xlt 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.
Are the functions in this PR to replace Connam functions like CConnman::BindListenPort, CConnman::InitBinds, etc...?

@pinheadmz
Copy link
Member Author

replace Connman functions

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

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

Approach ACK 5f79411

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)};
Copy link
Contributor

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());

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Comment on lines 17 to 29
/** 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;
}
Copy link
Contributor

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.

Copy link
Member Author

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);
Copy link
Contributor

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/

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

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

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];
}

Copy link
Member Author

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,
Copy link
Contributor

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/

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Copy link
Member Author

@pinheadmz pinheadmz left a 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!

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)};
Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Comment on lines 17 to 29
/** 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;
}
Copy link
Member Author

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

// 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;
Copy link
Member Author

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);
Copy link
Member Author

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,
Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

ACK 3e7abce

@DrahtBot DrahtBot requested a review from w0xlt June 25, 2025 15:57
@pinheadmz pinheadmz requested review from Sjors and removed request for w0xlt July 2, 2025 18:08
@pinheadmz pinheadmz requested a review from hodlinator July 15, 2025 18:05
Copy link
Member

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

Choose a reason for hiding this comment

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

535daaf nit, sort

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed, thanks

@@ -0,0 +1,152 @@
// Copyright (c) 2021-2022 The Bitcoin Core developers
Copy link
Member

@jonatack jonatack Jul 23, 2025

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)

Suggested change
// Copyright (c) 2021-2022 The Bitcoin Core developers
// Copyright (c) 2025-present The Bitcoin Core developers

Copy link
Member Author

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.

@DrahtBot DrahtBot requested a review from w0xlt July 23, 2025 22:42
Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

ACK e007e1b57d5d42c2a8d932d5b91eec8a3ca76e14

@DrahtBot DrahtBot requested a review from hodlinator September 29, 2025 13:37
Copy link
Contributor

@l0rinc l0rinc left a 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.

return false;
}

int one{1};
Copy link
Contributor

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.

Copy link
Member Author

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.

Comment on lines 23 to 24
err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort()));
return false;
Copy link
Contributor

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()));
    }
...

Copy link
Member Author

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)) {
Copy link
Contributor

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?

Copy link
Member Author

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

// 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) {
Copy link
Contributor

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?

Copy link
Member Author

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.

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()));
Copy link
Contributor

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?

Copy link
Member Author

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").


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

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?

Copy link
Member Author

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",
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

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

that's nice, thanks

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());
Copy link
Contributor

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?

Suggested change
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; }));

Copy link
Member Author

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)) {
Copy link
Contributor

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>

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

sure, added.

Copy link
Member Author

@pinheadmz pinheadmz left a 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.

return false;
}

int one{1};
Copy link
Member Author

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)) {
Copy link
Member Author

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

Comment on lines 23 to 24
err_msg = Untranslated(strprintf("Bind address family for %s not supported", to.ToStringAddrPort()));
return false;
Copy link
Member Author

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.

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()));
Copy link
Member Author

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").

// 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) {
Copy link
Member Author

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",
Copy link
Member Author

Choose a reason for hiding this comment

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

that's nice, thanks

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());
Copy link
Member Author

Choose a reason for hiding this comment

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

yes thanks


// 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};
Copy link
Member Author

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
Copy link
Member Author

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)) {
Copy link
Member Author

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

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

ACK 03ce73a820b485fbdbb854d9d3947ff1e6d87880

@theuni
Copy link
Member

theuni commented Oct 17, 2025

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, SendBytes may "succeed" at sending only one byte of many. Meaning that implementations will have to maintain their own send buffers, and presumably a thread that does a write/sleep/write/sleep dance. Imo that alone kinda defeats the purpose of this abstraction.

IOReadiness and Sock already implement a good chunk of the low-level handling. Sure, a custom event-loop per-protocol isn't exactly trivial, but it's a better result imo than a master-of-none abstraction.

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.

@pinheadmz
Copy link
Member Author

@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 HTTPServer directly? Or are there actual lower-level implementation details here you think are ineffective for HTTP?

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.

pinheadmz and others added 11 commits November 18, 2025 11:15
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]>
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]>
@pinheadmz pinheadmz marked this pull request as draft November 18, 2025 16:17
@pinheadmz
Copy link
Member Author

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.

@pinheadmz
Copy link
Member Author

Closing - these commits are all merged into #32061 including all the great review feedback <3

@pinheadmz pinheadmz closed this Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants