Skip to content

Commit 10afdf0

Browse files
committed
multiprocess: Add Ipc interface implementation
1 parent 745c9ce commit 10afdf0

File tree

14 files changed

+410
-2
lines changed

14 files changed

+410
-2
lines changed

src/Makefile.am

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ EXTRA_LIBRARIES += \
7474
$(LIBBITCOIN_CONSENSUS) \
7575
$(LIBBITCOIN_SERVER) \
7676
$(LIBBITCOIN_CLI) \
77+
$(LIBBITCOIN_IPC) \
7778
$(LIBBITCOIN_WALLET) \
7879
$(LIBBITCOIN_WALLET_TOOL) \
7980
$(LIBBITCOIN_ZMQ)
@@ -301,6 +302,8 @@ obj/build.h: FORCE
301302
"$(abs_top_srcdir)"
302303
libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
303304

305+
ipc/capnp/libbitcoin_ipc_a-ipc.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h)
306+
304307
# server: shared between bitcoind and bitcoin-qt
305308
# Contains code accessing mempool and chain state that is meant to be separated
306309
# from wallet and gui code (see node/README.md). Shared code should go in
@@ -647,7 +650,7 @@ bitcoin_node_SOURCES = $(bitcoin_daemon_sources)
647650
bitcoin_node_CPPFLAGS = $(bitcoin_bin_cppflags)
648651
bitcoin_node_CXXFLAGS = $(bitcoin_bin_cxxflags)
649652
bitcoin_node_LDFLAGS = $(bitcoin_bin_ldflags)
650-
bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd)
653+
bitcoin_node_LDADD = $(LIBBITCOIN_SERVER) $(bitcoin_bin_ldadd) $(LIBBITCOIN_IPC) $(LIBMULTIPROCESS_LIBS)
651654

652655
# bitcoin-cli binary #
653656
bitcoin_cli_SOURCES = bitcoin-cli.cpp
@@ -811,6 +814,38 @@ if HARDEN
811814
$(AM_V_at) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
812815
endif
813816

817+
libbitcoin_ipc_mpgen_input = \
818+
ipc/capnp/init.capnp
819+
EXTRA_DIST += $(libbitcoin_ipc_mpgen_input)
820+
%.capnp:
821+
822+
if BUILD_MULTIPROCESS
823+
LIBBITCOIN_IPC=libbitcoin_ipc.a
824+
libbitcoin_ipc_a_SOURCES = \
825+
ipc/capnp/init-types.h \
826+
ipc/capnp/protocol.cpp \
827+
ipc/capnp/protocol.h \
828+
ipc/exception.h \
829+
ipc/interfaces.cpp \
830+
ipc/process.cpp \
831+
ipc/process.h \
832+
ipc/protocol.h
833+
libbitcoin_ipc_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
834+
libbitcoin_ipc_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(LIBMULTIPROCESS_CFLAGS)
835+
836+
include $(MPGEN_PREFIX)/include/mpgen.mk
837+
libbitcoin_ipc_mpgen_output = \
838+
$(libbitcoin_ipc_mpgen_input:=.c++) \
839+
$(libbitcoin_ipc_mpgen_input:=.h) \
840+
$(libbitcoin_ipc_mpgen_input:=.proxy-client.c++) \
841+
$(libbitcoin_ipc_mpgen_input:=.proxy-server.c++) \
842+
$(libbitcoin_ipc_mpgen_input:=.proxy-types.c++) \
843+
$(libbitcoin_ipc_mpgen_input:=.proxy-types.h) \
844+
$(libbitcoin_ipc_mpgen_input:=.proxy.h)
845+
nodist_libbitcoin_ipc_a_SOURCES = $(libbitcoin_ipc_mpgen_output)
846+
CLEANFILES += $(libbitcoin_ipc_mpgen_output)
847+
endif
848+
814849
if EMBEDDED_LEVELDB
815850
include Makefile.crc32c.include
816851
include Makefile.leveldb.include

src/ipc/capnp/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# capnp generated files
2+
*.capnp.*

src/ipc/capnp/init-types.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_IPC_CAPNP_INIT_TYPES_H
6+
#define BITCOIN_IPC_CAPNP_INIT_TYPES_H
7+
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H

src/ipc/capnp/init.capnp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2021 The Bitcoin Core developers
2+
# Distributed under the MIT software license, see the accompanying
3+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
@0xf2c5cfa319406aa6;
6+
7+
using Cxx = import "/capnp/c++.capnp";
8+
$Cxx.namespace("ipc::capnp::messages");
9+
10+
using Proxy = import "/mp/proxy.capnp";
11+
$Proxy.include("interfaces/init.h");
12+
$Proxy.includeTypes("ipc/capnp/init-types.h");
13+
14+
interface Init $Proxy.wrap("interfaces::Init") {
15+
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
16+
}

src/ipc/capnp/protocol.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <interfaces/init.h>
6+
#include <ipc/capnp/init.capnp.h>
7+
#include <ipc/capnp/init.capnp.proxy.h>
8+
#include <ipc/capnp/protocol.h>
9+
#include <ipc/exception.h>
10+
#include <ipc/protocol.h>
11+
#include <kj/async.h>
12+
#include <logging.h>
13+
#include <mp/proxy-io.h>
14+
#include <mp/proxy-types.h>
15+
#include <mp/util.h>
16+
#include <util/threadnames.h>
17+
18+
#include <assert.h>
19+
#include <errno.h>
20+
#include <future>
21+
#include <memory>
22+
#include <mutex>
23+
#include <optional>
24+
#include <string>
25+
#include <thread>
26+
27+
namespace ipc {
28+
namespace capnp {
29+
namespace {
30+
void IpcLogFn(bool raise, std::string message)
31+
{
32+
LogPrint(BCLog::IPC, "%s\n", message);
33+
if (raise) throw Exception(message);
34+
}
35+
36+
class CapnpProtocol : public Protocol
37+
{
38+
public:
39+
~CapnpProtocol() noexcept(true)
40+
{
41+
if (m_loop) {
42+
std::unique_lock<std::mutex> lock(m_loop->m_mutex);
43+
m_loop->removeClient(lock);
44+
}
45+
if (m_loop_thread.joinable()) m_loop_thread.join();
46+
assert(!m_loop);
47+
};
48+
std::unique_ptr<interfaces::Init> connect(int fd, const char* exe_name) override
49+
{
50+
startLoop(exe_name);
51+
return mp::ConnectStream<messages::Init>(*m_loop, fd);
52+
}
53+
void serve(int fd, const char* exe_name, interfaces::Init& init) override
54+
{
55+
assert(!m_loop);
56+
mp::g_thread_context.thread_name = mp::ThreadName(exe_name);
57+
m_loop.emplace(exe_name, &IpcLogFn, nullptr);
58+
mp::ServeStream<messages::Init>(*m_loop, fd, init);
59+
m_loop->loop();
60+
m_loop.reset();
61+
}
62+
void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) override
63+
{
64+
mp::ProxyTypeRegister::types().at(type)(iface).cleanup.emplace_back(std::move(cleanup));
65+
}
66+
void startLoop(const char* exe_name)
67+
{
68+
if (m_loop) return;
69+
std::promise<void> promise;
70+
m_loop_thread = std::thread([&] {
71+
util::ThreadRename("capnp-loop");
72+
m_loop.emplace(exe_name, &IpcLogFn, nullptr);
73+
{
74+
std::unique_lock<std::mutex> lock(m_loop->m_mutex);
75+
m_loop->addClient(lock);
76+
}
77+
promise.set_value();
78+
m_loop->loop();
79+
m_loop.reset();
80+
});
81+
promise.get_future().wait();
82+
}
83+
std::thread m_loop_thread;
84+
std::optional<mp::EventLoop> m_loop;
85+
};
86+
} // namespace
87+
88+
std::unique_ptr<Protocol> MakeCapnpProtocol() { return std::make_unique<CapnpProtocol>(); }
89+
} // namespace capnp
90+
} // namespace ipc

src/ipc/capnp/protocol.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_IPC_CAPNP_PROTOCOL_H
6+
#define BITCOIN_IPC_CAPNP_PROTOCOL_H
7+
8+
#include <memory>
9+
10+
namespace ipc {
11+
class Protocol;
12+
namespace capnp {
13+
std::unique_ptr<Protocol> MakeCapnpProtocol();
14+
} // namespace capnp
15+
} // namespace ipc
16+
17+
#endif // BITCOIN_IPC_CAPNP_PROTOCOL_H

src/ipc/exception.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_IPC_EXCEPTION_H
6+
#define BITCOIN_IPC_EXCEPTION_H
7+
8+
#include <stdexcept>
9+
10+
namespace ipc {
11+
//! Exception class thrown when a call to remote method fails due to an IPC
12+
//! error, like a socket getting disconnected.
13+
class Exception : public std::runtime_error
14+
{
15+
public:
16+
using std::runtime_error::runtime_error;
17+
};
18+
} // namespace ipc
19+
20+
#endif // BITCOIN_IPC_EXCEPTION_H

src/ipc/interfaces.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <fs.h>
6+
#include <interfaces/init.h>
7+
#include <interfaces/ipc.h>
8+
#include <ipc/capnp/protocol.h>
9+
#include <ipc/process.h>
10+
#include <ipc/protocol.h>
11+
#include <logging.h>
12+
#include <tinyformat.h>
13+
#include <util/system.h>
14+
15+
#include <functional>
16+
#include <memory>
17+
#include <stdexcept>
18+
#include <stdio.h>
19+
#include <stdlib.h>
20+
#include <string.h>
21+
#include <string>
22+
#include <unistd.h>
23+
#include <utility>
24+
#include <vector>
25+
26+
namespace ipc {
27+
namespace {
28+
class IpcImpl : public interfaces::Ipc
29+
{
30+
public:
31+
IpcImpl(const char* exe_name, const char* process_argv0, interfaces::Init& init)
32+
: m_exe_name(exe_name), m_process_argv0(process_argv0), m_init(init),
33+
m_protocol(ipc::capnp::MakeCapnpProtocol()), m_process(ipc::MakeProcess())
34+
{
35+
}
36+
std::unique_ptr<interfaces::Init> spawnProcess(const char* new_exe_name) override
37+
{
38+
int pid;
39+
int fd = m_process->spawn(new_exe_name, m_process_argv0, pid);
40+
LogPrint(::BCLog::IPC, "Process %s pid %i launched\n", new_exe_name, pid);
41+
auto init = m_protocol->connect(fd, m_exe_name);
42+
Ipc::addCleanup(*init, [this, new_exe_name, pid] {
43+
int status = m_process->waitSpawned(pid);
44+
LogPrint(::BCLog::IPC, "Process %s pid %i exited with status %i\n", new_exe_name, pid, status);
45+
});
46+
return init;
47+
}
48+
bool startSpawnedProcess(int argc, char* argv[], int& exit_status) override
49+
{
50+
exit_status = EXIT_FAILURE;
51+
int32_t fd = -1;
52+
if (!m_process->checkSpawned(argc, argv, fd)) {
53+
return false;
54+
}
55+
m_protocol->serve(fd, m_exe_name, m_init);
56+
exit_status = EXIT_SUCCESS;
57+
return true;
58+
}
59+
void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) override
60+
{
61+
m_protocol->addCleanup(type, iface, std::move(cleanup));
62+
}
63+
const char* m_exe_name;
64+
const char* m_process_argv0;
65+
interfaces::Init& m_init;
66+
std::unique_ptr<Protocol> m_protocol;
67+
std::unique_ptr<Process> m_process;
68+
};
69+
} // namespace
70+
} // namespace ipc
71+
72+
namespace interfaces {
73+
std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* process_argv0, Init& init)
74+
{
75+
return std::make_unique<ipc::IpcImpl>(exe_name, process_argv0, init);
76+
}
77+
} // namespace interfaces

src/ipc/process.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <fs.h>
6+
#include <ipc/process.h>
7+
#include <ipc/protocol.h>
8+
#include <mp/util.h>
9+
#include <tinyformat.h>
10+
#include <util/strencodings.h>
11+
12+
#include <cstdint>
13+
#include <exception>
14+
#include <iostream>
15+
#include <stdexcept>
16+
#include <stdlib.h>
17+
#include <string.h>
18+
#include <system_error>
19+
#include <unistd.h>
20+
#include <utility>
21+
#include <vector>
22+
23+
namespace ipc {
24+
namespace {
25+
class ProcessImpl : public Process
26+
{
27+
public:
28+
int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) override
29+
{
30+
return mp::SpawnProcess(pid, [&](int fd) {
31+
fs::path path = argv0_path;
32+
path.remove_filename();
33+
path.append(new_exe_name);
34+
return std::vector<std::string>{path.string(), "-ipcfd", strprintf("%i", fd)};
35+
});
36+
}
37+
int waitSpawned(int pid) override { return mp::WaitProcess(pid); }
38+
bool checkSpawned(int argc, char* argv[], int& fd) override
39+
{
40+
// If this process was not started with a single -ipcfd argument, it is
41+
// not a process spawned by the spawn() call above, so return false and
42+
// do not try to serve requests.
43+
if (argc != 3 || strcmp(argv[1], "-ipcfd") != 0) {
44+
return false;
45+
}
46+
// If a single -ipcfd argument was provided, return true and get the
47+
// file descriptor so Protocol::serve() can be called to handle
48+
// requests from the parent process. The -ipcfd argument is not valid
49+
// in combination with other arguments because the parent process
50+
// should be able to control the child process through the IPC protocol
51+
// without passing information out of band.
52+
if (!ParseInt32(argv[2], &fd)) {
53+
throw std::runtime_error(strprintf("Invalid -ipcfd number '%s'", argv[2]));
54+
}
55+
return true;
56+
}
57+
};
58+
} // namespace
59+
60+
std::unique_ptr<Process> MakeProcess() { return std::make_unique<ProcessImpl>(); }
61+
} // namespace ipc

0 commit comments

Comments
 (0)