Skip to content

Fix error handling when creating clients (mp::ConnectStream) - #298

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

Fix error handling when creating clients (mp::ConnectStream)#298
xyzconstant wants to merge 4 commits into
bitcoin-core:masterfrom
xyzconstant:add-coverage-for-connect-stream

Conversation

@xyzconstant

@xyzconstant xyzconstant commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Avoid use-after-free if the socket is disconnected before ConnectStream connects (#308), and avoid leaks and hangs if client construct() calls throw (#309). Also add tests to cover these and other client connection errors, as suggested by @ryanofsky.

The following cases are tested:

  1. Connecting to a socket serving a valid init interface
  2. Passing a disconnected socket (ConnectStream throws during the construct() call)
  3. Passing a disconnected socket to an interface without construct() (the failure is deferred to the first IPC request)
  4. Passing a disconnected socket and making no calls (the disconnect is still handled and the connection cleaned up)
  5. Passing a live socket that disconnects after some data is received
  6. Passing a socket from a listening socket (accept()) that disconnects after some data arrives

Additionally, a new FooInit test interface is added, and the UnixListener class introduced in #269 is extracted to a shared file so the new connect_tests.cpp file can use it.

Note: Clients that own their connection now delete it on unexpected disconnects, so calls after a server disconnect fail with "called after disconnect" instead of "interrupted by disconnect" (one test.cpp assertion updated accordingly).

@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

Reviewers, this pull request conflicts with the following ones:

  • #304 (proxy: fix BuildList to use non-const iteration for interface types by ryanofsky)
  • #231 (Add windows support by ryanofsky)

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.

@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
@xyzconstant xyzconstant changed the title Add test coverage for ConnectStream Fix error handling when creating clients Jul 15, 2026
@xyzconstant xyzconstant changed the title Fix error handling when creating clients Fix error handling when creating clients (mp::ConnectStream) Jul 15, 2026
@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch 4 times, most recently from 9b93947 to 82f7ef2 Compare July 15, 2026 17:50
@xyzconstant

xyzconstant commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

re: #298 (comment)

Thanks for the review @ryanofsky!

Addressed your feedback and force-pushed, I hope the commit history is cleaner now.

Opened issues (#308 and #309) and updated the PR title and description as well.

Also, please note that I've added 2 new tests (I just noticed I pushed them right after your latest review, so you might have missed them), and I think they're valuable for showcasing the difference with an Init interface without construct(). They're included in the latest commit along with the single "disconnected socket" case.

@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch from 82f7ef2 to 9078674 Compare July 15, 2026 18:48
@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch from 9078674 to a2e0007 Compare July 21, 2026 06:17
@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch 2 times, most recently from e4530f8 to f3355b5 Compare July 21, 2026 20:24
@xyzconstant

Copy link
Copy Markdown
Contributor Author

Rebased on master which now includes #274. Adapted the tests to the stream API (MakeStream) and TestSetup now holds an EventLoopRef because it needs it to keep loop() running.

@DrahtBot DrahtBot mentioned this pull request Jul 21, 2026

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review f3355b5. I need to take more time to understand the tests but the bugfix looks right and useful for improving the stability of the C++ IPC client if it connects to a server that disconnects right away.

I left a minor suggestion below. Also would note that this conflicts with #231 and while I think conflicts mostly just come from moved code, it could be useful to try merging the two PRs and making sure the new tests do not introduce any unix-isms.

Comment thread test/mp/test/foo.capnp
passDataPointers @22 (arg :List(Data)) -> (result :List(Data));
}

interface FooInit $Proxy.wrap("mp::test::FooInit") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In commit "Add test coverage for ConnectStream" (31c1ac2)

Am curious why this new FooInit interface is needed and existing Foo interface isn't used.

It also seems like a potentially complicating factor that could make the tests harder to debug & understand for this to have a construct method. I wonder if it could be dropped or at least the Thread map parameters could be dropped since it doesn't look like anything in these tests requires threadmaps

EDIT: Oh, I see in next commit it looks like there are new tests that rely on the construct call failing. I think it would be to only use the FooInit type for the tests which actually need the construct method, and use FooInterface for other tests. Also would be good to drop ThreadMap parameters as I believe they should not be needed.

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.

Nice suggestion!

Addressed it at 65eab9d by removing the ThreadMap parameters.

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.

Regarding the FooInit/FooInterface split, the tests already follow this. Only the 4 tests that require the construct() call use FooInit.

Also, I've replaced the initThreadMap call with a simple add(1, 2) in the "ConnectStream defers disconnect failure to the first IPC request for interfaces without construct()" test case.

ryanofsky added a commit that referenced this pull request Aug 3, 2026
…ctions`

496fb84 test: cover immediate client disconnects for `ListenConnections` (xyzconstant)
140d9ba test: allow custom log handler in `ListenSetup` (xyzconstant)

Pull request description:

  Following on testing the reversed direction *ryanofsky* [suggested](#298 (comment)) in #298, this PR adds a test to cover immediate client disconnects on the server side.

  Additionally, this test adds `DefaultLogHandler` and a `log_handler` parameter to `ListenSetup`, allowing individual tests to observe logs by passing a custom log handler. The new test takes advantage of this by catching and skipping `Uncaught exception in daemonized task.` logs.

  NOTE: an issue surfaced on the macOS job, the `accept()` call in `ListenConnections` [fails](https://github.com/bitcoin-core/libmultiprocess/actions/runs/29545518652/job/87776862017?pr=310) for the closed connection, this is a Cap'n Proto bug as reported by *ViniciusCestarii* in #310 (review) and its fix is [available](capnproto/capnproto@7df5bd0#diff-ec577ad66535f58f6d7396ea51d3e56c0065308aa8fb02751cd6a8cfaa67252fR1358-R1372) in the v2 branch.

ACKs for top commit:
  ryanofsky:
    Code review ACK 496fb84. Since last review just tightened the checks to only allow the error on macos

Tree-SHA512: 6347b433d03968541be58b893746f707ce905d0fe35ddfaa858439d73cc0850a9d5077a472ff6bad91c8de67fb3ef43e2b303dcf73c6e9d6ef555fce40327f96
@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch 3 times, most recently from c435ef7 to 8fb3b5b Compare August 4, 2026 23:25
A later commit will consume the `UnixListener` class in another test file,
so move it to a shared one.
This commit introduces a new test file `connect_tests.cpp`
for testing the `ConnectStream` function. It also adds a `FooInit`
test interface declaring a `construct()` method, which
`ConnectStream` calls implicitly when present.
Two bugs that have always been present:

1. `ConnectStream` registered the `onDisconnect` handler that deletes the
`Connection` before the `ProxyClient` object owning it was created, so an
early disconnect could delete the `Connection` while the client constructor
was reading it (bitcoin-core#308).

2. A `construct()` method failing during client construction caused a
`Connection` leak. Normally, cleanup happens in the destructor but a
constructor that throws leaves no object behind, so it never runs, resulting
in an event loop ref preventing `EventLoop::loop()` from exiting (bitcoin-core#309).

Fix the first by making `ProxyClientBase` responsible for the connection
when `destroy_connection` is true, registering the delete-on-disconnect
handler at the end of its constructor. Fix the second by running the cleanup
functions before rethrowing.

Fixes bitcoin-core#308
Fixes bitcoin-core#309
@xyzconstant

Copy link
Copy Markdown
Contributor Author

Rebased with master and fixed surfaced IWYU issues.

Additionally, addressed @ryanofsky's feedback by removing the ThreadMap parameter from the (new) FooInit's construct method. Thanks for the review!

@xyzconstant
xyzconstant force-pushed the add-coverage-for-connect-stream branch from 8fb3b5b to bb47369 Compare August 5, 2026 00:02
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