Based on #231
CI commits are entirely vibe coded, so will need some polishing if it actually works,
Add ProcessId = int type alias and apply it to WaitProcess, SpawnProcess
(pid output argument), and callers.
Add SocketId = int and SocketError = -1 type aliases and apply SocketId
to SpawnProcess (return type and callback parameter) and callers.
Add ConnectInfo type alias to pass socket handle from parent process to
child process in more platform independent way.
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.
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>
Extract socket pair creation from SpawnProcess into a standalone
SocketPair() function, and use it to replace the inline socketpair()
call. No behavior change.
Explicitly clear FD_CLOEXEC on the child's socket before calling exec,
so the fd survives into the spawned process regardless of how the socket
was created. Previously this relied on socketpair() not setting
FD_CLOEXEC by default, which is not guaranteed if the caller creates
sockets with SOCK_CLOEXEC or if the flag gets set by other means.
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, along with StreamSocketId() to extract the underlying fd
from a Stream when needed.
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.
Add Windows-specific code to support building and running on Windows:
- util.h: Guard ProcessId/SocketId/SocketError type aliases with WIN32
ifdefs so they use SOCKET/uintptr_t on Windows and int on Unix.
Add winsock2.h include on Windows.
- util.cpp: Guard Unix-specific system headers with WIN32 ifdefs. Add
Windows-specific includes (windows.h, winsock2.h). Guard MaxFd() with
#ifndef WIN32. Add GetCurrentThreadId() branch in ThreadName(). Add
win32Socketpair() forward-declare. Add Windows branch in SocketPair()
using win32Socketpair(). Add CommandLineFromArgv() helper needed to
construct CreateProcess command lines. Add Windows branch in
SpawnProcess() using named pipes and WSADuplicateSocket to pass socket
to child. Add Windows branch in StartSpawned() reading socket from
named pipe. Add Windows branch in WaitProcess() using
WaitForSingleObject/GetExitCodeProcess.
- proxy-io.h: Add Windows branch in StreamSocketId() using
getWin32Handle().
- proxy.cpp: Add SocketOutputStream class on Windows (analogous to
FdOutputStream but using SOCKET/send()). Add Windows branch in
EventLoop constructor to create m_post_writer using SocketOutputStream.
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 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><!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--021abf342d371248e50ceaed478a90ca-->
See the guideline for information on the review process. A summary of reviews will appear here.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
Possible typos and grammar issues:
# Temporary: use PR [#35084](/bitcoin-core-multiprocess/35084/) until it merges; revert to refs/heads/master after -> # Temporary: use PR [#35084](/bitcoin-core-multiprocess/35084/) until it merges; revert to refs/heads/master after it merges [The comment sentence is incomplete (“after” has no object), so the intended meaning requires guessing.]
- Adds windows support. -> - Adds Windows support. [“windows” should be capitalized as a proper noun (“Windows”).]
Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):
wait_stream->read(&buffer, 0, 1) in src/mp/proxy.cppsend(m_socket, pos, static_cast<int>(kj::min(size, WRITE_CLAMP_SIZE)), 0) in src/mp/proxy.cppsocketpair(AF_UNIX, SOCK_STREAM, 0, pair) in src/mp/util.cppCreateNamedPipeA(pipe_path.c_str(), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_WAIT, 1, 0, 0, 0, nullptr) in src/mp/util.cppCreateProcessA(nullptr, const_cast<char*>(cmd.c_str()), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi) in src/mp/util.cppCreateFileA(connect_info.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr) in src/mp/util.cppCreateProcessA(nullptr, const_cast<char*>(cmd.c_str()), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi) in src/mp/util.cpp<sup>2026-04-20 15:00:56</sup>
This will need whitelisting of msys2/setup-msys2.
But I'm thinking of switching to cross-compilation, since that's how most users will use this library anyway.
Ok, similar to Bitcoin Core we now cross-compile and then run the result on a Windows machine.
The agent made a few other adjustments to the code that I haven't looked at yet.
This needs the following whitelist: