Multiprocess bitcoin - #10102
#10102Multiprocess bitcoin#10102ryanofsky wants to merge 24 commits into
Conversation
|
Oh. Nice. Conceptually I think this goes into the right direction, though, I'm not sure if this could end up being only a temporary in-between step that may end up being replaced. Though, I'm aware that capnp has an RPC layer. But this would introduce another API (RPC / ZMQ / REST and then capnp RPC). I'm not saying this is the wrong direction, but we should be careful about adding another API. Three questions:
|
Reason this is currently using capnp is not performance but convenience. Capnp provides a high level API that supports bidirectional, synchronous, and asynchronous calls out of the box and allows me to easily explore implementation choices in bitcoin-qt without having to worry about low level protocol details, write a lot of parameter packing/unpacking boilerplate, and implement things like long polling. Capnp could definitely be replaced by JSON-RPC, though, and I've gone out of my way to support this by not calling capnp functions or using capnp types or headers anywhere except the
It could, but I'm going out of my way right now specifically NOT to add yet another bitcoind public API that could add to the JSON-RPC/REST/ZMQ/-blocknotify/-walletnotify confusion. The IPC here doesn't happen over a TCP port or even a unix socket path but over an anonymous socketpair using an inherited file descriptor. (I haven't done a windows implementation yet but similar things are possible there). I'm trying to make the change completely internal for now and transparent to users. Bitcoin-qt should still be invoked the same way and behave the same way as before, starting its own node and wallet. It just will happen to do this internally now by forking a bitcoind executable rather than calling in-process functions. This change will not add any new command line or GUI options allowing bitcoin-qt to connect to bitcoinds other than the one it spawns internally. Adding these features and supporting new public APIs might be things we want to do in the future, but they would involve downsides and complications that I'm trying to avoid here.
It's not required here because this change doesn't expose any new socket or endpoint, but it could be supported. Capnp's security model is based on capabilities, so to add authentication, you would just define a factory function that takes credentials as parameters and returns a reference to an object exposing the appropriate functionality. |
|
I'm really uncomfortable with using capn proto, but fine enough for some example testing stuff! I'm a fan of this general approach (ignoring the use of capn proto) and I think we should have done something like it a long time ago. |
|
strong concept ACK, but if is feasible, would prefer usage of the existing RPC instead of capn'proto |
|
Concept ACK, nice.
Please, let's not turn this into a discussion of serialization and RPC frameworks. To be honest that's been one of the things that's putting me off of doing work like this. If you want to suggest what framework to use, please make a thorough investigation of what method would be best to use for our specific use case, and propose that, but let's not start throwing random "I'm not comfortable with X" comments. We already use google protocol buffers in the GUI for payment requests to in a way that would be the straightforward choice. I'm also happy you didn't choose some XML-based abomonation or ASN.1. But anyhow, not here. For this pull it's fine to use whatever RPC mechanism you're comfortable with.
I'm also perfectly fine with keeping the scope here to "communication between GUI and bitcoind". This is not the place for introducing another external interface. Might be an option at some point in the future, but for now process isolation is enough motivation. |
ryanofsky
left a comment
There was a problem hiding this comment.
Updated and rebased bf5f8ed -> 0ca73bc (pr/ipc.0 -> pr/ipc.1)
There's a lot of new changes here. More functions and callbacks have been wrapped, and there's now support for asynchronous calls that don't block event threads in the client and server.
At this point it would be very helpful to have code review for the main commit (0ca73bc "Add barebones IPC framework to bitcoin-qt and bitcoind"), because all the real infrastructure is now in place, and the main thing left to do is wrap up more functions and callbacks in IPC calls so the GUI can be functional.
|
Updated and rebased 0ca73bc -> 5e28c2f (pr/ipc.1 -> pr/ipc.3) to avoid a conflict. Main addition is an expanded src/ipc/README.md file. Again it would be very helpful to have some code review for the main commit (5e28c2f "Add barebones IPC framework to bitcoin-qt and bitcoind"). Giving feedback on the README file would be an easy place to start. |
5e28c2f to
dda3756
Compare
|
Updated 5e28c2f -> dda3756 (pr/ipc.3 -> pr/ipc.4) This implements two suggestions from @JeremyRubin:
|
|
@laanwj pointed out in IRC (https://botbot.me/freenode/bitcoin-core-dev/msg/83983170/) that this change could help make the GUI more responsive by preventing Qt event processing from getting blocked, which currently happens in the monolithic At the time in IRC, I didn't think this change could directly help gui responsiveness, because although it does move libbitcoin and LOCK calls out of the However, this doesn't have to be the case. The place where IPC calls currently block waiting for responses is the But the This would add more overhead and make the average IPC call a little slower. But it would avoid situations where an unexpectedly slow IPC call ties up the whole gui, so it might be worth doing anyway. |
|
@ryanofsky Yes, integrating the IPC event loop and Qt event loop would help responsiveness. |
|
@ryanofsky I'm not familiar with Qt or capnproto, but I don't understand what the move to a different process has to do with making things less blocking. Any changes in architecture that would result in less blocks should equally be possible within the same process. |
I don't understand the goal here. On itself, there seems little benefit in separating the GUI and the rest into separate processes if those two processes still depend on each other (this is different from separating the wallet from the node, for example, as there as security considerations there... but for that use case the easiest approach seems to just have a lightweight mode and running two instances). I think it would be awesome if bitcoin-qt could be started and stopped independently to control a bitcoind process in the background, but if that's not the intent, what is the purpose? |
Let's say there are 50 places where bitcoin-qt calls a libbitcoin function. That means there are 50 places to update if you want bitcoin-qt handle to events while the function calls are executing. WIth the IPC framework, there is only one place you have to update instead of 50 places (if you want to do this).
Ok, so you think the benefits are small, and I think they are more significant.
This is trivial once bitcoin-qt is controlling bitcoind across a socket. I'm just implementing the socket part first, without introducing new UI features for now. |
Ok, that's what I was missing. It wasn't clear to me that this was a just first step towards a more useful separation. |
This doesn't crash currently because the method doesn't access any object members, but this behavior is fragile and incompatible with bitcoin#10102.
- Add capnp ToBlob, ToArray, Wrap, Serialize, and Unserialize helper functions - Add support for std::chrono::seconds capnp serialization - Add support for util::Result and util::Expected capnp serialization
275c8ee Merge bitcoin-core/libmultiprocess#345: Remove trailing whitespace and Add -Wtrailing-whitespace to default ci config cd7162f Merge bitcoin-core/libmultiprocess#304: proxy: fix BuildList to use non-const iteration for interface types 9b13678 ci: Add -Wtrailing-whitespace to default config 2f4be9e refactor: Remove trailing whitespace 390b5f9 Merge bitcoin-core/libmultiprocess#344: test: listen_tests and connect_tests follow-ups d6f8588 proxy: fix BuildList to use non-const iteration for interface types e18ca52 Merge bitcoin-core/libmultiprocess#343: test: fix race in connect_tests disconnect-deferred-failure test c39c785 doc: note construct() call in valid init interface test b9c36c6 test: close sockets unconditionally and check errors with KJ_SYSCALL 7eb741e test: drop unnecessary KJ_EXPECT(true) 113f1d4 test: join server thread unconditionally in connect tests 44bc463 test: drop mp:: prefixes in connect tests 038d33e test: share DefaultLogHandler between test files b54a163 test: drop TestSetup socket members in connect tests 70467c5 test: add m_ prefix to TestSetup members in connect tests cc260f2 test: replace capnp fix link with upstream PR 137a6e4 test: fix race in connect_tests disconnect-deferred-failure test 8dab0d4 Merge bitcoin-core/libmultiprocess#341: ci: add -Wextra-semi to llvm config b3b134e ci: add -Wextra-semi to llvm config bdd0cd6 Merge bitcoin-core/libmultiprocess#339: refactor: add `[[noreturn]]` attributes a779a09 ci: add -Wmissing-noreturn 636aaff refactor: add missing [[noreturn]] attributes cc11c2b Merge bitcoin-core/libmultiprocess#338: test: check ReadList return value 2d6e863 Merge bitcoin-core/libmultiprocess#334: ci: Set CMAKE_BUILD_PARALLEL_LEVEL to enable parallelism by default d4d10ff Merge bitcoin-core/libmultiprocess#332: ci: add -Wextra-semi to default config b540e70 Merge bitcoin-core/libmultiprocess#324: proxy: Name threads spawned by the event loop e5e367e Merge bitcoin-core/libmultiprocess#312: util: report back child errors to parent and throw 2220df6 Merge bitcoin-core/libmultiprocess#298: Fix error handling when creating clients (`mp::ConnectStream`) 51defb7 Merge bitcoin-core/libmultiprocess#340: ci: Update `capnproto` prerequisites on NetBSD 7e94790 ci: Update `capnproto` prerequisites on NetBSD 9f25ffc test: Cover OS thread names for worker, pool, and async threads 648a185 proxy: Name threads spawned by the event loop 49834b2 ci: add -Wextra-semi to default config fae9a63 example: Remove unused kj/async.h include bb47369 Fix error handling when creating clients 44d1914 Add test coverage for ConnectStream 231361a Correct stale UnixListener doc comment 060c1a5 Extract `UnixListener` class to a dedicated file 62f25af test: check ReadList return value ce51d73 ci: Set CMAKE_BUILD_PARALLEL_LEVEL to enable parallism in build jobs by default 67302cd Merge bitcoin-core/libmultiprocess#331: Remove code for Cap'n Proto versions before 0.9 f13c64a Merge bitcoin-core/libmultiprocess#330: ci: Compile with minimum supported g++ in olddeps 8e026f6 Merge bitcoin-core/libmultiprocess#327: build: avoid unnecessary capnp-rpc dependency for mpgen e5206e9 Merge bitcoin-core/libmultiprocess#325: cmake: Remove `QUIET` option from `find_package(CapnProto ...)` 879efea Merge bitcoin-core/libmultiprocess#321: ci: Roll NetBSD releases to 11.0, drop 9.4 abf127a Merge bitcoin-core/libmultiprocess#317: ipc: Fix mpgen capnp tool path for vcpkg/Windows builds c437d7f Merge bitcoin-core/libmultiprocess#310: test: cover immediate client disconnects for `ListenConnections` 31bff8a Merge bitcoin-core/libmultiprocess#307: refactor: memcpy -> std::ranges::copy f355108 Merge bitcoin-core/libmultiprocess#303: type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point 2d67817 Merge bitcoin-core/libmultiprocess#296: ci: Bump channel to nixos-26.05 3f05b11 util: kill and reap child on SpawnProcess error 4a56c18 util: report back child error to parent and throw a9e70db ci: Add NetBSD release 11.0 2d33b14 ci: Switch to default compiler on NetBSD 9.4 36f7400 ci: Drop NetBSD release 9.4 bd50831 refactor: Drop stray semicolons after function definitions 788f17a Remove code for Cap'n Proto versions before 0.9 7402aff ci: Pin oldeps config to older nixpkgs channel to compile older cmake with older gcc edf6343 ci: Compile with minimum supported g++-11 in olddeps fa47449 cmake: avoid unnecessary capnp-rpc dependency for mpgen a494b76 cmake: Remove `QUIET` option from `find_package(CapnProto ...)` 26452e0 refactor: memcpy -> std::ranges::copy e1dcc6e Merge bitcoin-core/libmultiprocess#316: cmake: Fix stale codegen when mpgen binary changes 7a72df0 type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point 45b685c type-number, type-chrono: Fix static assert signed/unsigned comparisons 45f6255 type-number: exclude bool from the integral overload 8d6d464 Merge bitcoin-core/libmultiprocess#315: Fix startup race in example a6fc80d Merge bitcoin-core/libmultiprocess#311: bugfix: clear FD_CLOEXEC in child instead of parent before fork 496fb84 test: cover immediate client disconnects for `ListenConnections` 36c6c63 doc: Document reference-counted EventLoop lifetime 3a997e1 Fix startup race in mpexample f5c15ce Merge bitcoin-core/libmultiprocess#323: refactor: access ThreadContext through CurrentThread(), ci: switch Bitcoin Core to master 66298c7 ci: Switch back to Bitcoin Core's master branch 86b4810 refactor: access ThreadContext through CurrentThread() eea9c64 cmake: Fix stale codegen when mpgen binary changes a26a084 cmake: Fix mpgen capnp tool path for vcpkg/Windows builds 140d9ba test: allow custom log handler in `ListenSetup` 1e0c7ff util: Clear FD_CLOEXEC in child instead of parent before fork 8550ee6 util, refactor: Add ChildFail helper for post-fork child errors 17eab90 test: Fix typo in listen_tests.cpp ce865a9 refactor: Directly use value in CustomBuildField 3f221b5 Merge bitcoin-core/libmultiprocess#274: Add nonunix platform support 1b0f605 doc: Remove trailing whitespace d8f8ca3 ipc: Wrap mpgen main() in try-catch to print errors fbe5a14 ci: Check out bitcoin/bitcoin PR bitcoin#35084 instead of master 39d3690 types: Replace SFINAE with requires clauses to avoid MSVC C2039 error ba68520 proxy, refactor: Fix C4305 truncation warning in Accessor on MSVC 1d81d47 util, refactor: Fix PtrOrValue constructor for move-only types on MSVC b883fe1 proxy: Fix shutdownWrite() exception handling on macOS with dynamic libraries 0012411 proxy: Call shutdownWrite() in Connection destructor 38312ad proxy, refactor: Change ConnectStream and ServeStream to accept stream objects e96d5d7 proxy, refactor: Replace EventLoop wakeup fd integers with KJ stream objects db4f9a3 cmake: Bump minimum required Cap'n Proto version to 0.9 652934f util, refactor: Add SocketPair() and use it in SpawnProcess 1c6ef7a util, refactor: Do not fork() and exec() separately 1389cf3 util, refactor: Add SpawnConnectInfo type alias and use it c7ca1f0 util, refactor: Add SocketId type alias and use it be46a35 util, refactor: Add ProcessId type alias and use it 91a78db doc: Bump version 13 > 14 fa2c56e ci: Bump channel to nixos-26.05 git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: 275c8ee
Expose Chain interface to external processes spawning or connecting to bitcoin-node.
Lambda [[noreturn]] attributes before the parameter list are a C++23 extension intentionally used in C++20 builds (needed for compatibility with -Wmissing-noreturn). Suppress the extension warning rather than removing the attributes or working around them. Uses the existing IF_CHECK_PASSED idiom so GCC, which silently ignores unknown -Wno-* flags, is unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Needed because BlockConnected notifications are a lot slower with the wallet running in separate process.
Make default constructor more generic so it doesn't only work with void types.
Add tests pinning down when a callback object (and any state it owns) is destroyed. Currently disconnect() only disables the callback: the object stays alive until a later connect() call on the same signal garbage collects it and every connection handle referencing it has been released; with no connection handle held it lives until the signal itself is destroyed. This differs from boost::signals2, which btcsignals aims to be api-compatible with: there, disconnecting destroys the slot's function object as soon as no emission is using it. No behavior change; these tests document the status quo so the next commit changing the destruction timing has a clear before/after diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously a disconnected callback and any state it owned could stay alive indefinitely: destruction required both a later connect() call on the same signal to garbage collect it and release of every connection handle referencing it. That made destruction timing unpredictable for callbacks that own resources whose release has side effects, and it diverges from boost::signals2, which destroys a disconnected slot's function object as soon as no emission is running it. (The concrete fallout was in multiprocess bitcoin-gui, where node notification callbacks own IPC proxy objects: their deferred destruction kept bitcoin-node from exiting and hung interface_gui.py until timeout.) Make disconnect() destroy the callback, deferring only while a concurrent emission is mid-call into it, matching boost::signals2 semantics. The previous commit's characterization tests are updated; their diff shows the ownership change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hared_ptr std::atomic<shared_ptr<T>> is a C++20 library feature, but library implementations are not required to ship all C++20 features at once; __cpp_lib_atomic_shared_ptr is the feature-test macro that indicates its presence. Without it, the generic std::atomic<T> requires trivially copyable T — shared_ptr is not, causing build failures on macOS (Xcode 16.2), FreeBSD 15.1, and the MSan/TSan custom libc++ builds in CI. Fall back to the C++14 atomic_load/atomic_store free functions, which are specifically overloaded for shared_ptr. The fallback is intended to be temporary: those functions are deprecated in C++20 and will eventually be removed, so the #else branch can be dropped once all CI platforms carry the C++20 specialization. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Libmultiprocess requires output parameters to be ordered after input parameter, so move warnings parameter last. Problematic order was introduced in 4ec2d18 from bitcoin-core/gui#877
Spawn node subprocess instead of running node code internally
Spawn wallet subprocess instead of running wallet code internally
Add .wallet/.gui suffixes to log files created by bitcoin-gui and bitcoin-wallet processes so they don't clash with bitcoin-node log file.
Needed due to base PR, can be dropped before merge
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
|
🐙 This pull request conflicts with the target branch and needs rebase. |
This is based on # + #29409. The non-base commits are:
d1aa6f6f1d7test: Increase feature_block.py and feature_taproot.py timeouts73e5e8204fctest: Fix multiprocess test for unclean shutdown on killca100c345eautil: Add util::Result workaround to be compatible with libmultiprocess1eafa6031e1test: characterize btcsignals callback destruction timing1e3b364e39cbtcsignals: destroy callbacks eagerly on disconnecte1f91bffe5ebtcsignals: fall back to atomic_load/store without __cpp_lib_atomic_shared_ptrd2823d69a8bmultiprocess: Add capnp serialization code for bitcoin types740a5059029interfaces, refactor: Change WalletLoader::restoreWallet parameter order7a9a6b876c0multiprocess: Add capnp wrapper for Wallet interfaced34e8f85fb4multiprocess: Add capnp wrapper for Node interface4464fe2b583multiprocess: Make bitcoin-gui spawn a bitcoin-node processbea8f39310cmultiprocess: Make bitcoin-node spawn a bitcoin-wallet processb7b846d2162multiprocess: Add debug.log .wallet/.gui suffixes9d2c04b8e75doc: Multiprocess misc doc and comment updatesc1adb53309fcombine_logs: Handle multiprocess wallet log filesThis PR adds an
--enable-multiprocessconfigure option which builds newbitcoin-node,bitcoin-wallet, andbitcoin-guiexecutables with relevant functionality isolated into different processes. See doc/design/multiprocess.md for usage details and future plans.The change is implemented by adding a new
Initinterface that spawns new wallet and node subprocesses that can be controlled over a socketpair by callingNode,Wallet, andChainClientmethods. When running with split processes, you can see the IPC messages going back and forth in-debug=1output. Followup PR's #19460 and #19461 add-ipcbindand-ipcconnectoptions that allow more flexibility in how processes are connected.The IPC protocol used is Cap'n Proto, but this could be swapped out for another protocol. Cap'n Proto types and libraries are only accessed in the src/ipc/capnp/ directory, and not in any public headers or other parts of bitcoin code.
Slides from a presentation describing this change are available on google drive. Demo code used in the presentation was from an older version this PR (tag ipc.21, commits).
This PR is part of the process separation project.