Skip to content

Add windows support#231

Open
ryanofsky wants to merge 13 commits intobitcoin-core:masterfrom
ryanofsky:pr/win
Open

Add windows support#231
ryanofsky wants to merge 13 commits intobitcoin-core:masterfrom
ryanofsky:pr/win

Conversation

@ryanofsky
Copy link
Copy Markdown
Collaborator

Add support for running on windows. These changes make the libmultiprocess API more generic, using stream types instead of file descriptors. All features are supported, including spawning processes with socket connections to the parent process. These changes were originally made in bitcoin/bitcoin#32387

@DrahtBot
Copy link
Copy Markdown

DrahtBot commented Oct 22, 2025

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

Reviews

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #269 (proxy: add local connection limit to ListenConnections by enirox001)
  • #218 (Better error and log messages by ryanofsky)
  • #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.

LLM Linter (✨ experimental)

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):

  • send(m_socket, pos, static_cast<int>(kj::min(size, WRITE_CLAMP_SIZE)), 0) in src/mp/proxy.cpp
  • CreateNamedPipeA(pipe_path.c_str(), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_WAIT, 1, 0, 0, 0, nullptr) in src/mp/util.cpp
  • CreateProcessA(nullptr, const_cast<char*>(cmd.c_str()), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi) in src/mp/util.cpp
  • CreateFileA(connect_info.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr) in src/mp/util.cpp
  • WSASocketA(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, &info, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT) in src/mp/util.cpp

2026-04-17 01:40:53

@ryanofsky
Copy link
Copy Markdown
Collaborator Author

Rebased 7d2f3c4 -> 2975fac (pr/win.1 -> pr/win.2, compare) due to conflicts with #237

@pavlenex
Copy link
Copy Markdown

pavlenex commented Apr 8, 2026

We have miners using Stratum V2 that are on Windows and would like to see Windows IPC support landing, is there any estimate when they can expect this to materialize? We have a dozen of users who can help with testing if that's a blocker.

@Sjors
Copy link
Copy Markdown
Member

Sjors commented Apr 8, 2026

@pavlenex testing would definitely be useful. I guess for that to work we'd need a stack of pull requests: this one here in libmultiprocess, one in Bitcoin Core that enables IPC support in the Windows Guix build, and then an SRI pull request that uses it. Testers would then have download (or build themselves) the custom bitcoin core and SRI binaries.

I've added Windows support to my v32 wish list: bitcoin/bitcoin#33777

No guarantees obviously.

@ryanofsky
Copy link
Copy Markdown
Collaborator Author

I'll try to get this PR ready for review this week, split up into smaller commits and with ci passing. From there as Sjors mentioned there is a lot more work to do: more code changes in bitcoin/bitcoin#32387 that need to be made in bitcoin core, enabling IPC into windows builds in bitcoin core, enabling it in windows CI jobs with pycapnp, adding client support, probably adding a windows CI job to this repository.

v32 sounds like a good target though and it is very useful to know there is demand for this feature, because it hasn't been a priority so far

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.
…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.
@ryanofsky
Copy link
Copy Markdown
Collaborator Author

ryanofsky commented Apr 15, 2026

PR is split up into commits now and should be reviewable. CI is not passing but failures look like IWYU errors. I also opened bitcoin/bitcoin#35084 with corresponding bitcoin core changes. Windows support for bitcoin core can be tested with bitcoin/bitcoin#32387 which combines both PRs and enables IPC by default in windows builds.

Rebased 2975fac -> cb16d2e (pr/win.2 -> pr/win.3, compare) splitting changes up into more reviewable commits

Updated cb16d2e -> e563c96 (pr/win.3 -> pr/win.4, compare) to fix various ci failures #231 (comment): macos/freebsd shutdownwrite fail, bitcoin core ci jobs api incompatibility, iwyu and olddeps fixes

Updated e563c96 -> d9fcac6 (pr/win.4 -> pr/win.5, compare) to fix IWYU errors https://github.com/bitcoin-core/libmultiprocess/actions/runs/24539430990/job/71741856575?pr=231

Added 1 commits d9fcac6 -> a1748e2 (pr/win.5 -> pr/win.6, compare) to fix bitcoin core macos exception type error https://github.com/bitcoin-core/libmultiprocess/actions/runs/24541950788/job/71749481354?pr=231

ryanofsky and others added 5 commits April 16, 2026 18:33
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.
…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, 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.
ryanofsky and others added 2 commits April 16, 2026 18:33
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>
@ryanofsky ryanofsky force-pushed the pr/win branch 2 times, most recently from e563c96 to d9fcac6 Compare April 17, 2026 00:52
…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>
@Sjors
Copy link
Copy Markdown
Member

Sjors commented Apr 20, 2026

How realistic is it to add a Windows CI job here? (can be cross-compiled)

This was referenced Apr 20, 2026
Sjors added a commit to Sjors/sv2-tp that referenced this pull request Apr 20, 2026
The lifecycle test repeatedly constructs and destroys TPTester to
exercise IPC EventLoop teardown. On Windows it hangs intermittently
in std::thread::join during teardown of libmultiprocess thread-local
state. This is a known libmultiprocess Windows issue, not specific
to sv2-tp:

  bitcoin-core/libmultiprocess#231
  bitcoin/bitcoin#32387

The fix lives upstream and rewrites the EventLoop wakeup primitive
(raw fd -> KJ stream) and adds shutdownWrite() in ~Connection. Until
that lands and is backported into our libmultiprocess subtree, gate
this particular test off on _WIN32. The other sv2 unit tests run
normally on Windows; only the explicit teardown loop is affected.

Assisted-by: GitHub Copilot
Assisted-by: Anthropic Claude Opus 4
Sjors added a commit to Sjors/sv2-tp that referenced this pull request Apr 20, 2026
The template_provider_tests `client_tests`, `fee_timer_blocking_test`
and `new_tip_bypasses_fee_timer_test` cases each construct and tear
down a TPTester. Like the lifecycle test skipped in the previous
commit, that teardown deadlocks intermittently on Windows in
std::thread::join during libmultiprocess thread-local state cleanup.
This is a known libmultiprocess Windows issue, not specific to
sv2-tp:

  bitcoin-core/libmultiprocess#231
  bitcoin/bitcoin#32387

Gate the three TPTester-using cases off on _WIN32 with a TODO. The
non-IPC test `block_reserved_weight_floor` continues to run on
Windows.

Assisted-by: GitHub Copilot
Assisted-by: Anthropic Claude Opus 4
Sjors added a commit to Sjors/sv2-tp that referenced this pull request Apr 20, 2026
The sv2 template_provider_tests pass on Windows; only TPTester
teardown intermittently deadlocks in std::thread::join during
libmultiprocess thread-local cleanup. This is a known libmultiprocess
Windows issue, not specific to sv2-tp:

  bitcoin-core/libmultiprocess#231
  bitcoin/bitcoin#32387

Instead of skipping the cases, exercise them normally and intentionally
leak the TPTester so its destructor never runs. The OS reclaims the
remaining loop thread and IPC state at process exit. A MAKE_TPTESTER
helper macro keeps the tests source-compatible across platforms; on
non-Windows it is just a stack-allocated TPTester as before.

The lifecycle test in sv2_tester_lifecycle_tests is left skipped on
Windows because its whole purpose is to exercise repeated construction
and destruction of TPTester; leaking would defeat it.

Assisted-by: GitHub Copilot
Assisted-by: Anthropic Claude Opus 4
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