Skip to content

Add nonunix platform support#274

Merged
ryanofsky merged 17 commits into
bitcoin-core:masterfrom
ryanofsky:pr/wins
Jul 17, 2026
Merged

Add nonunix platform support#274
ryanofsky merged 17 commits into
bitcoin-core:masterfrom
ryanofsky:pr/wins

Conversation

@ryanofsky

Copy link
Copy Markdown
Collaborator

This PR implements API changes and fixes needed to allow libmultiprocess to work on nonunix platforms.

These changes were originally part of #231, which adds windows support, but were split out to allow windows and nonwindows changes to be reviewed separately.

@DrahtBot

DrahtBot commented Apr 22, 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.

Type Reviewers
ACK Sjors, ViniciusCestarii
Stale ACK enirox001

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #298 (Fix error handling when creating clients (mp::ConnectStream) by xyzconstant)
  • #296 (ci: Bump channel to nixos-26.05 by maflcko)
  • #209 (cmake: Increase cmake policy version by ryanofsky)
  • #175 (Set cmake_minimum_required(VERSION 3.22) by maflcko)

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.

Comment thread include/mp/util.h Outdated
Comment thread src/mp/util.cpp Outdated
Comment thread include/mp/util.h Outdated

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the review! Will update with suggestions.

Comment thread include/mp/util.h Outdated
Comment thread src/mp/util.cpp Outdated
Comment thread include/mp/util.h Outdated
@Sjors

Sjors commented Jun 17, 2026

Copy link
Copy Markdown
Member

I opened stratum-mining/sv2-tp#110 to test the changes in the Template Provider.

@Sjors Sjors left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Studied two first three commits...

Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread src/mp/util.cpp
Comment thread src/mp/util.cpp Outdated

@Sjors Sjors left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the rest as well, except the CMake changes in c9aa806 and 7cb83a5.

Concept ACK

Comment thread include/mp/util.h
//! errors in python unit tests.
std::string LogEscape(const kj::StringTree& string, size_t max_size);

using Stream = kj::Own<kj::AsyncIoStream>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In 091f5e1 proxy, refactor: Change ConnectStream and ServeStream to accept stream objects: would it make sense to introduce this earlier, in 3c81cf2?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

re: #274 (comment)

In 091f5e1 proxy, refactor: Change ConnectStream and ServeStream to accept stream objects: would it make sense to introduce this earlier, in 3c81cf2?

It could make sense but I feel it's a little clearer if the EventLoop code is using kj::Own<kj::AsyncIoStream> and kj::Own<kj::OutputStream> types directly and not tied to the mp::Stream type for external callers and meant to be more opaque.

But I did extend this commit to use Stream stream type in ConnectStream and ServeStream functions since these are external functions.

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.

In 174604a "proxy, refactor: Change ConnectStream and ServeStream to accept stream objects"

Had a concern that the name Stream is fairly broad, so using it too widely could hide useful information at call sites. For example, inside EventLoop it seems helpful to see the concrete KJ type details directly, otherwise future code might start using mp::Stream everywhere just because the alias exists.

I agree with keeping it limited to external facing methods like ConnectStream and ServeStream, where the abstraction is useful, and avoiding it in internal code where the underlying type is more informative.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

re: #274 (comment)

Agreed, glad the reasoning made sense!

Comment thread include/mp/proxy-io.h Outdated
Comment thread src/mp/proxy.cpp Outdated
Comment thread include/mp/util.h Outdated

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the reviews!

Rebased 7cb83a5 -> 68ed129 (pr/wins.1 -> pr/wins.2, compare) implementing review suggestions and fixing conflict with #279

Updated 68ed129 -> a43e5a8 (pr/wins.2 -> pr/wins.3, compare) to fix environ undeclared on macOS/BSD: add explicit declaration before posix_spawn() https://github.com/bitcoin-core/libmultiprocess/actions/runs/27974097183/job/82787313836

Updated a43e5a8 -> 6cc729f (pr/wins.3 -> pr/wins.4, compare) to fix clang-tidy readability-redundant-declaration on environ declaration https://github.com/bitcoin-core/libmultiprocess/actions/runs/27978804911/job/82803323635

Comment thread include/mp/util.h Outdated
Comment thread src/mp/util.cpp Outdated
Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread include/mp/util.h
Comment thread src/mp/util.cpp Outdated
Comment thread include/mp/util.h
//! errors in python unit tests.
std::string LogEscape(const kj::StringTree& string, size_t max_size);

using Stream = kj::Own<kj::AsyncIoStream>;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

re: #274 (comment)

In 091f5e1 proxy, refactor: Change ConnectStream and ServeStream to accept stream objects: would it make sense to introduce this earlier, in 3c81cf2?

It could make sense but I feel it's a little clearer if the EventLoop code is using kj::Own<kj::AsyncIoStream> and kj::Own<kj::OutputStream> types directly and not tied to the mp::Stream type for external callers and meant to be more opaque.

But I did extend this commit to use Stream stream type in ConnectStream and ServeStream functions since these are external functions.

Comment thread include/mp/proxy-io.h Outdated
Comment thread src/mp/proxy.cpp Outdated
Comment thread include/mp/util.h Outdated
@ryanofsky
ryanofsky force-pushed the pr/wins branch 2 times, most recently from a43e5a8 to 6cc729f Compare June 22, 2026 21:32
ryanofsky and others added 11 commits July 13, 2026 19:56
Needed because the subsequent commit adds a kj::AsyncIoStream::getFd() call.

kj::AsyncIoStream::getFd() was added in capnproto 0.9 (commit
d27bfb8a4175b32b783de68d93dd1dbafadddea5, first released in 0.9.0). The
code now uses getFd() in proxy.cpp, so 0.7 is no longer a sufficient
minimum.

Set olddeps version to 0.9.2, which is the patched 0.9.x release for
CVE-2022-46149.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…objects

Replace the m_wait_fd/m_post_fd raw int members with
m_wait_stream/m_post_stream kj::Own<kj::AsyncIoStream> and
m_post_writer kj::Own<kj::OutputStream>.

The constructor uses provider->newTwoWayPipe() instead of calling
socketpair() directly. The loop() and post() methods write through
m_post_writer instead of calling write() with a raw fd, and
EventLoopRef::reset does the same.
…m objects

Instead of accepting raw file descriptor integers and wrapping them
internally, ConnectStream and ServeStream now accept
kj::Own<kj::AsyncIoStream> directly. This removes the assumption that
the transport is always a local unix fd, making the API easier to adapt
to other I/O types (e.g. Windows handles).

The Stream type alias (kj::Own<kj::AsyncIoStream>) is added as a
convenience.

Callers are updated to wrap their fd with wrapSocketFd() before calling.
Flush pending Cap'n Proto release messages before closing the stream.
When one side of a socket pair closes, the other side does not receive
an onDisconnect event, so it relies on receiving release messages from
the closing side to free its ProxyServer objects and shut down cleanly.
Without this, Server objects are not freed by Cap'n Proto on
disconnection.

This also adds noexception(false) to the ~Connection destructor to start
following the Cap'n Proto error handling pattern described in
https://github.com/capnproto/capnproto/blob/v2/kjdoc/tour.md#exceptions-in-destructors
Following the pattern should not be very important here because
exceptions should never be leaked here, but in general this pattern
should be preferable to alternative of suppressing errors entirely or
terminating, instead of returning them to callers.
…ibraries

On macOS, when libcapnp is built as a dynamic library and Bitcoin Core
REDUCE_EXPORT option is used the RTTI typeinfo for kj::Exception has a
different address in libcapnp.dylib versus the calling binary. This
means catch (const kj::Exception& e) in the calling binary silently
fails to match exceptions thrown by capnp, so the DISCONNECTED exception
from shutdownWrite() propagates as a fatal uncaught exception instead of
being suppressed as intended.

This causes the Bitcoin Core macOS native CI job to fail with:
  Fatal uncaught kj::Exception: kj/async-io-unix.c++:491: disconnected:
    shutdown(fd, SHUT_WR): Socket is not connected

The fix is to use kj::runCatchingExceptions/kj::throwRecoverableException,
which use KJ's own thread-level exception interception mechanism rather
than C++ RTTI-based matching, and therefore work correctly across dynamic
library boundaries. This is the same approach used elsewhere in the
codebase (proxy.cpp EventLoop::post, type-context.h server request handler)
for the same reason.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MSVC error when building multiprocess.vcxproj:

  mp/util.h(146,46): error C2280:
    'std::variant<T *,T>::variant(const std::variant<T *,T> &)':
    attempting to reference a deleted function [with T=mp::Lock]

The PtrOrValue constructor used a ternary expression to initialize data:

  data(ptr ? ptr : std::variant<T*, T>{std::in_place_type<T>, args...})

Both arms are prvalues of type std::variant<T*,T>, so under C++17's
mandatory copy elision no copy/move constructor should be invoked. GCC
and Clang apply this correctly. MSVC does not apply guaranteed copy
elision to ternary expressions in this context: it materializes the
temporary and then attempts to copy-construct data from it. Since
std::variant<Lock*,Lock> has a deleted copy constructor (Lock holds a
std::unique_lock which is move-only), MSVC fails.

Fix by initializing data to hold T*=ptr in the member initializer list,
then emplacing T in-place in the constructor body if ptr is null. This
avoids the ternary entirely and requires only the in-place constructor
of T, not any variant copy or move.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MSVC warns (C4305, treated as error) about truncation from 'int' to
'const bool' when initializing static const bool members from integer
bitwise-and expressions. Use constexpr bool with explicit != 0 to
make the boolean conversion unambiguous.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MSVC does not correctly apply SFINAE when the substitution failure
occurs in a default function argument that uses decltype of another
function parameter (MSVC error C2039). Instead of silently excluding
the overload, MSVC instantiates the function body and reports a hard
error, e.g.:

  type-interface.h(62,56): error C2039: 'Calls': is not a member of
  'capnp::List<mp::test::messages::Pair<capnp::Text,capnp::Text>,
  capnp::Kind::STRUCT>::Builder'

The root cause is that MSVC evaluates default argument expressions
outside the SFINAE immediate context when they reference function
parameters via decltype. GCC and Clang treat this as a substitution
failure and silently exclude the overload, as the standard intends.

Replace all uses of the `* enable = nullptr` default-argument SFINAE
pattern with C++20 requires clauses, which are well-supported on all
three compilers and give cleaner constraint-violation diagnostics.
Add two concepts to util.h to reduce repetition across the type-*.h
headers:

  FieldTypeIs<T, U>   - T's .get() returns exactly type U
  InterfaceField<T>   - T's .get() returns a capnp interface type
                        (a type that exposes a nested ::Calls member)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This repo has introduced API changes to add Windows support to
libmultiprocess (HANDLE-based IPC alongside the existing fd-based IPC).
These changes require corresponding updates to Bitcoin Core, which are
pending in bitcoin/bitcoin#35084. Until that PR merges, the Bitcoin Core
CI jobs fail against master because Bitcoin Core has not yet been updated
to use the new API.

Switch the Bitcoin Core checkout in both jobs to use
refs/pull/35084/merge so CI tests against the compatible version. A
BITCOIN_CORE_REF env var is introduced at the top of the file; once
(and keep the var in place for any future API compatibility cycles).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On MSVC, std::terminate() does not print the exception message before
calling abort()/fastfail, so exceptions thrown during mpgen execution
appear as a bare 0xC0000409 exit code with no diagnostic output. Wrap
main() in a try-catch to explicitly print the error to stderr and
return 1 instead of crashing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ryanofsky

ryanofsky commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

@Sjors @enirox001 Are you able to Re-ACK this? I'd like to merge it to unblock windows support. This PR and bitcoin/bitcoin#35084 contain most of the code changes needed to support windows, with the other changes just being non-invasive #ifdef WIN32 additions.

There are not many changes since your last reviews. They are:

  • Rebasing
  • Dropping cmake commits
  • Catching std::stio exception for more descriptive spawn error messages
  • Catching another exception (in ~Connection and declaring it noexcept(false). These changes just precautionary, the new exception and other exceptions in general are not expected here.
  • In EventLoopRef::reset adding comment and tweaking post_writer call to avoid potential TSAN error (not a real race).
  • Tweaking F_SETFD call to preserve previous flags
  • Changing posix_spawn to posix_spawnp to preserve previous PATH search behavior.

Comment thread doc/versions.md Outdated
Comment thread include/mp/proxy-io.h Outdated
@Sjors

Sjors commented Jul 16, 2026

Copy link
Copy Markdown
Member

Everything else in 254c3e3 looks good to me, just two questions above.

@enirox001 enirox001 left a comment

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.

ACK 254c3e3 Changes look fine to me. Left a few questions, but they are non-blocking nits.

Comment thread src/mp/util.cpp
Comment thread ci/configs/olddeps.bash

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated 254c3e3 -> 6e29e22 (pr/wins.7 -> pr/wins.8, compare) fixing version documentation

Comment thread doc/versions.md Outdated
Comment thread include/mp/proxy-io.h Outdated

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated 6e29e22 -> 9953981 (pr/wins.8 -> pr/wins.9, compare) changing MakeStream signature to avoid potential thread safety issues in the future. Note that this requires an update to bitcoin/bitcoin#35084 so CI will fail until that is pushed (should happen shortly)

Squashed 9953981 -> 1b0f605 (pr/wins.9 -> pr/wins.10, compare) just updating commit message and commit order as suggested

Comment thread include/mp/proxy-io.h Outdated
@Sjors

Sjors commented Jul 16, 2026

Copy link
Copy Markdown
Member

ACK 1b0f605

@DrahtBot
DrahtBot requested a review from enirox001 July 16, 2026 17:28
@ViniciusCestarii

Copy link
Copy Markdown
Contributor

ACK 1b0f605 I like that these changes make the code more readable too, require clauses and KJ Stream abstractions are nice

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the reviews! I plan to merge this soon (as-is) but rereviewed this myself and left some notes for followups

Comment thread src/mp/util.cpp
Comment thread ci/configs/olddeps.bash
Comment thread src/mp/proxy.cpp
} else if (done()) {
// Intentionally do not break if m_post_fn was set, even if done()
// would return true, to ensure that the EventLoopRef write(post_fd)
// would return true, to ensure that the post() m_post_writer->write()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In commit "proxy, refactor: Replace EventLoop wakeup fd integers with KJ stream objects" (e96d5d7)

Note for followup. Unclear why I changed this comment to reference post() instead of EventLoopRef to. I think this is just a mistake and this should reference EventLoopRef::reset() specifically. Both of these methods write but only reset() sets the done() state and needs this logic.

I was also looking into why newTwoWayPipe is used here instead of newOneWayPipe, and it doesn't seem like there is a good reason. And there seems to be a newer kj::Executor mechanism https://github.com/capnproto/capnproto/blob/v2/kjdoc/tour.md#threads that would avoid the need for these pipes entirely.

I think I want to focus on getting windows support fully implemented in #231 and tested in CI but after that it seems like there is room for simplification here with kj::Executor

Comment thread include/mp/util.h

//! Information about parent process passed to child process as a command-line
//! argument. On unix this is the child socket fd number formatted as a string.
using SpawnConnectInfo = std::string;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In commit "util, refactor: Add SpawnConnectInfo type alias and use it" (1389cf3)

Note for followup: Looking at this code I think these type SpawnConnectInfo aliases actually hurt readability more than they help. I think originally I was thinking SpawnConnectInfo might have to be different types on different platforms to deal with the windows implementation #231 which sends a serialized socketed handle over a pipe to the child process instead of passing a file descriptor. But either way the application only sees an opaque string so std::string is a clearer way to represent this than SpawnConnectInfo

@ryanofsky
ryanofsky merged commit 3f221b5 into bitcoin-core:master Jul 17, 2026
13 checks passed
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.

5 participants