Skip to content

Add test coverage for ConnectStream#298

Open
xyzconstant wants to merge 3 commits into
bitcoin-core:masterfrom
xyzconstant:add-coverage-for-connect-stream
Open

Add test coverage for ConnectStream#298
xyzconstant wants to merge 3 commits into
bitcoin-core:masterfrom
xyzconstant:add-coverage-for-connect-stream

Conversation

@xyzconstant

@xyzconstant xyzconstant commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

ref: #183

This PR adds test coverage for the client method ConnectStream, as suggested by @ryanofsky.

The following cases are tested:

  1. Passing a disconnected socket
  2. Passing a live socket that disconnects after some data is received
  3. Passing a UNIX domain socket that disconnects after some data arrives

Additionally, the UnixListener class introduced in #269 to set up listening tests is extracted to a shared file so the new connect_tests.cpp file can consume it.

Use-after-free race found in the NetBSD 9.4 job

The NetBSD 9.4 job was crashing in the new test connect_tests.cpp:94 with:

assertion "m_loop" failed: file ".../include/mp/proxy.h", line 59,
function "mp::EventLoop& mp::EventLoopRef::operator*() const"
*** Received signal #6: Abort trap

Asked Claude to try to reproduce it locally and came up with placing (in ConnectStream)

std::this_thread::sleep_for(std::chrono::milliseconds(100));

between the loop.sync(...) block and the client construction to widen the race window so that the event loop thread wins the race (as it consistently did in the failing job), thereby exposing the issue.

The sequence of events was as follows: passing a disconnected socket fd causes the RPC system to hit EOF on the first read. The event loop resumes right after the loop.sync(...) block, resolves m_network.onDisconnect(), whose handler (registered inside the same sync block) runs delete connection_ptr. Connection::~Connection resets its EventLoopRef m_loop member, and the memory is freed.

The caller thread then wakes up and constructs the ProxyClient<InitInterface>, whose ProxyContext constructor reads the freed Connection:

ProxyContext::ProxyContext(Connection* connection) : connection(connection), loop{*connection->m_loop} {}

The fix is included in this PR as the first commit. It moves the onDisconnect handler registration to a second loop.sync(...) call just after the proxy client has been constructed.

@DrahtBot

DrahtBot commented Jun 24, 2026

Copy link
Copy Markdown

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

Reviews

See the guideline and AI policy for information on the review process.
A summary of reviews will appear here.

Conflicts

No conflicts as of last run.

@xyzconstant xyzconstant changed the title WIP: Add test coverage for ConnectStream WIP: Add test coverage for ConnectStream Jun 24, 2026
@ryanofsky

Copy link
Copy Markdown
Collaborator

Thanks for following up to #183 with these tests. They do seem potentially useful. Here is feedback I'd have:

  • The first 3 tests seems like they are mostly in good shape. You would just want to wrap these in try/catch to and assert expected exceptions are thrown. For the second test it would seem fine to accept both "called after disconnect" and "interrupted by disconnect" exceptions since we already have existing tests testing for each of these errors more specifically.

  • The first 3 tests do overlap a lot with disconnect tests already in test.cpp, and it's a little questionable if having all 3 tests adds much value. The first test could be is nice because it directly tests connecting to a non-capnp server that disconnects ignoring whatever is sent. But the second and third tests just sending the same disconnect at later points in time and needing MSG_PEEK complexity would not seem to add as much value.

  • For the 4th test having the client hang as long as server hangs is probably expected behavior. It would be good to make sure that client can still disconnect or cancel the calls if the the server hangs. Or that client calls are able to time out correctly.

  • It could make sense to rebase this on #269 or rebase if that could help with the "Add the case with an actual mkdtemp/socket/bind/listen setup" follow up comment.

  • Maybe (not sure) it could be interesting to have tests working in opposite direction with server providing dummy interface and clients connecting and disconnecting suddenly. Maybe it could also be useful to have tests sending garbage bytes and making sure clients and servers handle them cleanly. It might even be useful to use fuzzing for this though it might not be a good use of fuzzing resources since it would mostly only be fuzz testing capnproto code and only a little bit of libmultiprocess exception/cleanup handling code.

Overall the tests here seem reasonable to add. It seems good to have at least 1-2 tests verifying disconnects are processed when a capnproto client connects to a non-capnproto server.

(Relatedly, there are also other disconnect tests that could be added at different points during capnproto connections, which I started to write in #201 (comment) and https://github.com/ryanofsky/libmultiprocess/commits/pr/distest.2 but was never able to really finish due to complexity of trying to set up and cover all of the relevant cases. Just mentioning this for completeness, though. There's probably not an obviously place to follow up with this at the moment.)

@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch 3 times, most recently from d3390db to cdada78 Compare July 8, 2026 03:14
@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch from cdada78 to 4f05dbe Compare July 8, 2026 15:38
@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch 4 times, most recently from 595e258 to bc85c9a Compare July 8, 2026 18:14
@xyzconstant xyzconstant changed the title WIP: Add test coverage for ConnectStream Add test coverage for ConnectStream Jul 8, 2026
@xyzconstant xyzconstant marked this pull request as ready for review July 8, 2026 18:51
@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch 2 times, most recently from 02b300c to 7c24708 Compare July 8, 2026 21:46
@xyzconstant

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback @ryanofsky!

I've just rebased to master now that #269 has been merged and added a new commit to move UnixListener to its own dedicated file.

Also, I've made changes to the tests (please check the description), keeping the first 2 tests (Notice that I've dropped the MSG_PEEK setup as you suggested.) now catching errors in a try/catch block, plus a Unix domain socket that disconnects after some data arrives and a successful case connecting to a valid libmultiprocess server.

It would be good to make sure that client can still disconnect or cancel the calls if the the server hangs. Or that client calls are able to time out correctly.

This is my current focus. I'm looking more into it, but I think this work might need its own branch so we keep test coverage scoped to this PR.

Maybe (not sure) it could be interesting to have tests working in opposite direction with server providing dummy interface and clients connecting and disconnecting suddenly. Maybe it could also be useful to have tests sending garbage bytes and making sure clients and servers handle them cleanly. It might even be useful to use fuzzing for this though it might not be a good use of fuzzing resources since it would mostly only be fuzz testing capnproto code and only a little bit of libmultiprocess exception/cleanup handling code.

Good, these ideas are great and definitely worth exploring. Happy to tackle them after this.

@xyzconstant

Copy link
Copy Markdown
Contributor Author

This PR is now ready for review. I've updated the description as well!

@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch 8 times, most recently from 02f2822 to bd3c83d Compare July 9, 2026 05:29
Previously, the `onDisconnect` handler responsible for deleting the
`Connection` was registered before the `ProxyClient<InitInterface>`
instance was constructed. If the peer was already disconnected, the
event loop could run the handler and delete the `Connection` before
the client constructor registered its cleanup callbacks, resulting
in the constructor accessing freed memory.

To resolve this, register the handler in a second `.sync()` call after
the client is constructed.
Next commit will consume the `UnixListener` class in another test file,
so move it to a shared one.
@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch from aa095c1 to a648c51 Compare July 10, 2026 05:29
@xyzconstant

Copy link
Copy Markdown
Contributor Author

The netbsd 9.4 job exposed a use-after-free race condition in ConnectStream on early disconnects, included a new commit in this PR that fixes it.

Comment thread test/mp/test/connect_tests.cpp Outdated
});
loop = loop_promise.get_future().get();

// Initalize and store sockets

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LLM Linter (✨ experimental)

Possible typos and grammar issues:

Initalize -> Initialize [misspelling in the socket setup comment; intended meaning is clear but the word is misspelled]

2026-07-10 05:29:42

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

This commit introduces a new test file `connect_tests.cpp`
for testing the `ConnectStream` client method.
@xyzconstant xyzconstant force-pushed the add-coverage-for-connect-stream branch from a648c51 to aee480b Compare July 10, 2026 14:50
@xyzconstant

Copy link
Copy Markdown
Contributor Author

The netbsd 9.4 job exposed a use-after-free race condition in ConnectStream on early disconnects, included a new commit in this PR that fixes it.

Updated the description expanding more on this issue.

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.

4 participants