Skip to content

Commit

Permalink
multiprocess: Add basic spawn and IPC support
Browse files Browse the repository at this point in the history
This commit adds basic process spawning and IPC method call support to
`bitcoin-node`, `bitcoin-gui`, and `bitcoin-wallet` executables built with
`--enable-multiprocess`.

These changes are used in bitcoin#10102 to let
node, gui, and wallet functionality run in different processes, and in PRs
after that to allow gui and wallet processes to be started and stopped
independently and connect to the node over a socket.

These changes can also be used to implement new functionality outside the
bitcoin-node process like external indexes or pluggable transports
(bitcoin#18988). The LocalInit::spawnProcess
and IpcProcess::serve methods added here are entry points for spawning a child
process and serving a parent process, and being able to make bidirectional,
multithreaded method calls between the processes. A simple example of this is
implemented in the next commit "Add echoipc RPC method and test."
  • Loading branch information
ryanofsky committed Aug 26, 2020
1 parent 7ed844c commit f73918e
Show file tree
Hide file tree
Showing 36 changed files with 629 additions and 31 deletions.
1 change: 1 addition & 0 deletions build_msvc/bitcoin-qt/bitcoin-qt.vcxproj
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ClCompile Include="..\..\src\qt\main.cpp" />
<ClCompile Include="..\..\src\interfaces\init_bitcoind.cpp" />
<ResourceCompile Include="..\..\src\qt\res\bitcoin-qt-res.rc" />
</ItemGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions build_msvc/bitcoind/bitcoind.vcxproj
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<ClCompile Include="..\..\src\bitcoind.cpp" />
<ClCompile Include="..\..\src\interfaces\init_bitcoind.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj">
Expand Down
1 change: 1 addition & 0 deletions build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj
Expand Up @@ -8,6 +8,7 @@
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="..\..\src\interfaces\init_bitcoind.cpp" />
<ClCompile Include="..\..\src\test\util\setup_common.cpp" />
<ClCompile Include="..\..\src\qt\test\addressbooktests.cpp" />
<ClCompile Include="..\..\src\qt\test\apptests.cpp" />
Expand Down
2 changes: 1 addition & 1 deletion doc/multiprocess.md
Expand Up @@ -15,7 +15,7 @@ Specific next steps after [#10102](https://github.com/bitcoin/bitcoin/pull/10102

## Debugging

After [#10102](https://github.com/bitcoin/bitcoin/pull/10102), the `-debug=ipc` command line option can be used to see requests and responses between processes.
The `-debug=ipc` command line option can be used to see requests and responses between processes. As much as possible, calls between processes are meant to work the same as calls within a single process without adding limitations or requiring extra implementation effort. Processes communicate with each other by calling regular [C++ interface methods](../src/interfaces/README.md). Method arguments and return values are automatically serialized and sent between processes. Object references and `std::function` arguments are automatically tracked and mapped to allow invoked code to call back into invoking code at any time, and there is a 1:1 threading model where any thread invoking a method in another process has a corresponding thread in the invoked process responsible for executing all method calls from the source thread, without blocking I/O or holding up another calls, and using the same thread local variables, locks, and callbacks between calls. The forwarding, tracking, and threading is implemented inside the [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) library which has the design goal of making calls between processes look exactly like calls in the same process to the extent possible.

## Installation

Expand Down
42 changes: 39 additions & 3 deletions src/Makefile.am
Expand Up @@ -70,6 +70,7 @@ EXTRA_LIBRARIES += \
$(LIBBITCOIN_CONSENSUS) \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_CLI) \
$(LIBBITCOIN_IPC) \
$(LIBBITCOIN_WALLET) \
$(LIBBITCOIN_WALLET_TOOL) \
$(LIBBITCOIN_ZMQ)
Expand Down Expand Up @@ -147,6 +148,7 @@ BITCOIN_CORE_H = \
interfaces/base.h \
interfaces/chain.h \
interfaces/handler.h \
interfaces/init.h \
interfaces/node.h \
interfaces/wallet.h \
key.h \
Expand Down Expand Up @@ -275,6 +277,8 @@ obj/build.h: FORCE
"$(abs_top_srcdir)"
libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h

interfaces/capnp/libbitcoin_ipc_a-ipc.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h)

# server: shared between bitcoind and bitcoin-qt
# Contains code accessing mempool and chain state that is meant to be separated
# from wallet and gui code (see node/README.md). Shared code should go in
Expand Down Expand Up @@ -516,6 +520,7 @@ libbitcoin_util_a_SOURCES = \
compat/strnlen.cpp \
fs.cpp \
interfaces/base.cpp \
interfaces/init.cpp \
interfaces/handler.cpp \
logging.cpp \
random.cpp \
Expand Down Expand Up @@ -587,17 +592,17 @@ bitcoin_bin_ldadd = \

bitcoin_bin_ldadd += $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS)

bitcoind_SOURCES = $(bitcoin_daemon_sources)
bitcoind_SOURCES = $(bitcoin_daemon_sources) interfaces/init_bitcoind.cpp
bitcoind_CPPFLAGS = $(bitcoin_bin_cppflags)
bitcoind_CXXFLAGS = $(bitcoin_bin_cxxflags)
bitcoind_LDFLAGS = $(bitcoin_bin_ldflags)
bitcoind_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd)

bitcoin_node_SOURCES = $(bitcoin_daemon_sources)
bitcoin_node_SOURCES = $(bitcoin_daemon_sources) interfaces/init_bitcoin-node.cpp
bitcoin_node_CPPFLAGS = $(bitcoin_bin_cppflags)
bitcoin_node_CXXFLAGS = $(bitcoin_bin_cxxflags)
bitcoin_node_LDFLAGS = $(bitcoin_bin_ldflags)
bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd)
bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS)

# bitcoin-cli binary #
bitcoin_cli_SOURCES = bitcoin-cli.cpp
Expand Down Expand Up @@ -740,6 +745,37 @@ if HARDEN
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
endif

libbitcoin_ipc_mpgen_input = \
interfaces/capnp/init.capnp
EXTRA_DIST += $(libbitcoin_ipc_mpgen_input)
%.capnp:

if BUILD_MULTIPROCESS
LIBBITCOIN_IPC=libbitcoin_ipc.a
libbitcoin_ipc_a_SOURCES = \
interfaces/capnp/init-types.h \
interfaces/capnp/init.cpp \
interfaces/capnp/init.h \
interfaces/capnp/ipc.cpp \
interfaces/capnp/ipc.h \
interfaces/ipc.cpp \
interfaces/ipc.h
libbitcoin_ipc_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libbitcoin_ipc_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS)

include $(MPGEN_PREFIX)/include/mpgen.mk
libbitcoin_ipc_mpgen_output = \
$(libbitcoin_ipc_mpgen_input:=.c++) \
$(libbitcoin_ipc_mpgen_input:=.h) \
$(libbitcoin_ipc_mpgen_input:=.proxy-client.c++) \
$(libbitcoin_ipc_mpgen_input:=.proxy-server.c++) \
$(libbitcoin_ipc_mpgen_input:=.proxy-types.c++) \
$(libbitcoin_ipc_mpgen_input:=.proxy-types.h) \
$(libbitcoin_ipc_mpgen_input:=.proxy.h)
nodist_libbitcoin_ipc_a_SOURCES = $(libbitcoin_ipc_mpgen_output)
CLEANFILES += $(libbitcoin_ipc_mpgen_output)
endif

if EMBEDDED_LEVELDB
include Makefile.crc32c.include
include Makefile.leveldb.include
Expand Down
6 changes: 3 additions & 3 deletions src/Makefile.qt.include
Expand Up @@ -327,15 +327,15 @@ bitcoin_qt_libtoolflags = $(AM_LIBTOOLFLAGS) --tag CXX

qt_bitcoin_qt_CPPFLAGS = $(bitcoin_qt_cppflags)
qt_bitcoin_qt_CXXFLAGS = $(bitcoin_qt_cxxflags)
qt_bitcoin_qt_SOURCES = $(bitcoin_qt_sources)
qt_bitcoin_qt_SOURCES = $(bitcoin_qt_sources) interfaces/init_bitcoind.cpp
qt_bitcoin_qt_LDADD = $(bitcoin_qt_ldadd)
qt_bitcoin_qt_LDFLAGS = $(bitcoin_qt_ldflags)
qt_bitcoin_qt_LIBTOOLFLAGS = $(bitcoin_qt_libtoolflags)

bitcoin_gui_CPPFLAGS = $(bitcoin_qt_cppflags)
bitcoin_gui_CXXFLAGS = $(bitcoin_qt_cxxflags)
bitcoin_gui_SOURCES = $(bitcoin_qt_sources)
bitcoin_gui_LDADD = $(bitcoin_qt_ldadd)
bitcoin_gui_SOURCES = $(bitcoin_qt_sources) interfaces/init_bitcoin-node.cpp
bitcoin_gui_LDADD = $(bitcoin_qt_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS)
bitcoin_gui_LDFLAGS = $(bitcoin_qt_ldflags)
bitcoin_gui_LIBTOOLFLAGS = $(bitcoin_qt_libtoolflags)

Expand Down
3 changes: 2 additions & 1 deletion src/Makefile.qttest.include
Expand Up @@ -36,7 +36,8 @@ qt_test_test_bitcoin_qt_SOURCES = \
qt/test/test_main.cpp \
qt/test/uritests.cpp \
qt/test/util.cpp \
$(TEST_QT_H)
$(TEST_QT_H) \
interfaces/init_bitcoind.cpp
if ENABLE_WALLET
qt_test_test_bitcoin_qt_SOURCES += \
qt/test/addressbooktests.cpp \
Expand Down
20 changes: 16 additions & 4 deletions src/bitcoind.cpp
Expand Up @@ -12,6 +12,8 @@
#include <compat.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <node/context.h>
#include <node/ui_interface.h>
#include <noui.h>
Expand Down Expand Up @@ -41,18 +43,18 @@ static void WaitForShutdown(NodeContext& node)
//
// Start
//
static bool AppInit(int argc, char* argv[])
static bool AppInit(interfaces::LocalInit& init, int argc, char* argv[])
{
NodeContext node;
NodeContext& node = init.node();
node.chain = interfaces::MakeChain(node);

bool fRet = false;

util::ThreadSetInternalName("init");

// If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
SetupServerArgs(node);
ArgsManager& args = *Assert(node.args);
SetupServerArgs(args);
std::string error;
if (!args.ParseParameters(argc, argv, error)) {
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error)));
Expand Down Expand Up @@ -169,10 +171,20 @@ int main(int argc, char* argv[])
util::WinCmdLineArgs winArgs;
std::tie(argc, argv) = winArgs.get();
#endif

std::unique_ptr<interfaces::LocalInit> init = interfaces::MakeInit(argc, argv);

// Check if bitcoind is being invoked as an IPC server. If so, then bypass
// normal execution and just respond to requests over the IPC channel.
int exit_status;
if (init->m_process && init->m_process->serve(exit_status)) {
return exit_status;
}

SetupEnvironment();

// Connect bitcoind signal handlers
noui_connect();

return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
return (AppInit(*init, argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
}
6 changes: 1 addition & 5 deletions src/init.cpp
Expand Up @@ -367,12 +367,8 @@ static void OnRPCStopped()
LogPrint(BCLog::RPC, "RPC stopped.\n");
}

void SetupServerArgs(NodeContext& node)
void SetupServerArgs(ArgsManager& argsman)
{
assert(!node.args);
node.args = &gArgs;
ArgsManager& argsman = *node.args;

SetupHelpOptions(argsman);
argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now

Expand Down
2 changes: 1 addition & 1 deletion src/init.h
Expand Up @@ -62,7 +62,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
/**
* Register all arguments with the ArgsManager
*/
void SetupServerArgs(NodeContext& node);
void SetupServerArgs(ArgsManager& argsman);

/** Returns licensing information (for -version) */
std::string LicenseInfo();
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/README.md
Expand Up @@ -12,7 +12,7 @@ The following interfaces are defined here:

* [`Handler`](handler.h) — returned by `handleEvent` methods on interfaces above and used to manage lifetimes of event handlers.

* [`Init`](init.h) — used by multiprocess code to access interfaces above on startup. Added in [#10102](https://github.com/bitcoin/bitcoin/pull/10102).
* [`Init`](init.h) — used by multiprocess code to access interfaces above on startup. Added in [#19160](https://github.com/bitcoin/bitcoin/pull/19160).

* [`Base`](base.h) — base interface class used by multiprocess code for bookkeeping and cleanup. Added in [#19160](https://github.com/bitcoin/bitcoin/pull/19160).

Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/capnp/.gitignore
@@ -0,0 +1,2 @@
# capnp generated files
*.capnp.*
7 changes: 7 additions & 0 deletions src/interfaces/capnp/init-types.h
@@ -0,0 +1,7 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H
#define BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H
#endif // BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H
14 changes: 14 additions & 0 deletions src/interfaces/capnp/init.capnp
@@ -0,0 +1,14 @@
# Copyright (c) 2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0xf2c5cfa319406aa6;

using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("interfaces::capnp::messages");

using Proxy = import "/mp/proxy.capnp";

interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
}
3 changes: 3 additions & 0 deletions src/interfaces/capnp/init.cpp
@@ -0,0 +1,3 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
11 changes: 11 additions & 0 deletions src/interfaces/capnp/init.h
@@ -0,0 +1,11 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_CAPNP_INIT_H
#define BITCOIN_INTERFACES_CAPNP_INIT_H

#include <interfaces/init.h>
#include <mp/proxy.h>

#endif // BITCOIN_INTERFACES_CAPNP_INIT_H
96 changes: 96 additions & 0 deletions src/interfaces/capnp/ipc.cpp
@@ -0,0 +1,96 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <interfaces/capnp/ipc.h>

#include <interfaces/base.h>
#include <interfaces/capnp/init.capnp.h>
#include <interfaces/capnp/init.capnp.proxy.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <sync.h>
#include <util/memory.h>
#include <util/threadnames.h>

#include <assert.h>
#include <boost/optional.hpp>
#include <future>
#include <list>
#include <memory>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/util.h>
#include <string>
#include <thread>
#include <utility>

namespace interfaces {
namespace capnp {
namespace {
void IpcLogFn(bool raise, std::string message)
{
LogPrint(BCLog::IPC, "%s\n", message);
if (raise) throw IpcException(message);
}

class IpcProtocolImpl : public IpcProtocol
{
public:
IpcProtocolImpl(const char* exe_name, LocalInit& init) : m_exe_name(exe_name), m_init(init) {}
~IpcProtocolImpl() noexcept(true)
{
if (m_loop) {
std::unique_lock<std::mutex> lock(m_loop->m_mutex);
m_loop->removeClient(lock);
}
if (m_loop_thread.joinable()) m_loop_thread.join();
assert(!m_loop);
};

std::unique_ptr<interfaces::Init> connect(int fd) override
{
startLoop();
return mp::ConnectStream<messages::Init>(*m_loop, fd);
}
void serve(int fd) override
{
assert(!m_loop);
mp::g_thread_context.thread_name = mp::ThreadName(m_exe_name);
m_loop.emplace(m_exe_name, &IpcLogFn, &m_init);
mp::ServeStream<messages::Init>(*m_loop, fd, m_init);
m_loop->loop();
m_loop.reset();
}

private:
void startLoop()
{
if (m_loop) return;
std::promise<void> promise;
m_loop_thread = std::thread([&] {
util::ThreadRename("capnp-loop");
m_loop.emplace(m_exe_name, &IpcLogFn, &m_init);
{
std::unique_lock<std::mutex> lock(m_loop->m_mutex);
m_loop->addClient(lock);
}
promise.set_value();
m_loop->loop();
m_loop.reset();
});
promise.get_future().wait();
}
const char* m_exe_name;
LocalInit& m_init;
std::thread m_loop_thread;
boost::optional<mp::EventLoop> m_loop;
};
} // namespace

std::unique_ptr<IpcProtocol> MakeCapnpProtocol(const char* exe_name, LocalInit& init)
{
return MakeUnique<IpcProtocolImpl>(exe_name, init);
}
} // namespace capnp
} // namespace interfaces

0 comments on commit f73918e

Please sign in to comment.