multiprocess: Add Ipc interface implementation

This commit is contained in:
Russell Yanofsky 2017-12-05 15:57:12 -05:00
parent 745c9cebd5
commit 10afdf0280
14 changed files with 410 additions and 2 deletions

View File

@ -74,6 +74,7 @@ EXTRA_LIBRARIES += \
$(LIBBITCOIN_CONSENSUS) \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_CLI) \
$(LIBBITCOIN_IPC) \
$(LIBBITCOIN_WALLET) \
$(LIBBITCOIN_WALLET_TOOL) \
$(LIBBITCOIN_ZMQ)
@ -301,6 +302,8 @@ obj/build.h: FORCE
"$(abs_top_srcdir)"
libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
ipc/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
@ -647,7 +650,7 @@ bitcoin_node_SOURCES = $(bitcoin_daemon_sources)
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
@ -811,6 +814,38 @@ if HARDEN
$(AM_V_at) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
endif
libbitcoin_ipc_mpgen_input = \
ipc/capnp/init.capnp
EXTRA_DIST += $(libbitcoin_ipc_mpgen_input)
%.capnp:
if BUILD_MULTIPROCESS
LIBBITCOIN_IPC=libbitcoin_ipc.a
libbitcoin_ipc_a_SOURCES = \
ipc/capnp/init-types.h \
ipc/capnp/protocol.cpp \
ipc/capnp/protocol.h \
ipc/exception.h \
ipc/interfaces.cpp \
ipc/process.cpp \
ipc/process.h \
ipc/protocol.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

2
src/ipc/capnp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# capnp generated files
*.capnp.*

View File

@ -0,0 +1,7 @@
// Copyright (c) 2021 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_IPC_CAPNP_INIT_TYPES_H
#define BITCOIN_IPC_CAPNP_INIT_TYPES_H
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H

16
src/ipc/capnp/init.capnp Normal file
View File

@ -0,0 +1,16 @@
# Copyright (c) 2021 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("ipc::capnp::messages");
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("interfaces/init.h");
$Proxy.includeTypes("ipc/capnp/init-types.h");
interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
}

View File

@ -0,0 +1,90 @@
// Copyright (c) 2021 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/init.h>
#include <ipc/capnp/init.capnp.h>
#include <ipc/capnp/init.capnp.proxy.h>
#include <ipc/capnp/protocol.h>
#include <ipc/exception.h>
#include <ipc/protocol.h>
#include <kj/async.h>
#include <logging.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/util.h>
#include <util/threadnames.h>
#include <assert.h>
#include <errno.h>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
namespace ipc {
namespace capnp {
namespace {
void IpcLogFn(bool raise, std::string message)
{
LogPrint(BCLog::IPC, "%s\n", message);
if (raise) throw Exception(message);
}
class CapnpProtocol : public Protocol
{
public:
~CapnpProtocol() 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, const char* exe_name) override
{
startLoop(exe_name);
return mp::ConnectStream<messages::Init>(*m_loop, fd);
}
void serve(int fd, const char* exe_name, interfaces::Init& init) override
{
assert(!m_loop);
mp::g_thread_context.thread_name = mp::ThreadName(exe_name);
m_loop.emplace(exe_name, &IpcLogFn, nullptr);
mp::ServeStream<messages::Init>(*m_loop, fd, init);
m_loop->loop();
m_loop.reset();
}
void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) override
{
mp::ProxyTypeRegister::types().at(type)(iface).cleanup.emplace_back(std::move(cleanup));
}
void startLoop(const char* exe_name)
{
if (m_loop) return;
std::promise<void> promise;
m_loop_thread = std::thread([&] {
util::ThreadRename("capnp-loop");
m_loop.emplace(exe_name, &IpcLogFn, nullptr);
{
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();
}
std::thread m_loop_thread;
std::optional<mp::EventLoop> m_loop;
};
} // namespace
std::unique_ptr<Protocol> MakeCapnpProtocol() { return std::make_unique<CapnpProtocol>(); }
} // namespace capnp
} // namespace ipc

17
src/ipc/capnp/protocol.h Normal file
View File

@ -0,0 +1,17 @@
// Copyright (c) 2021 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_IPC_CAPNP_PROTOCOL_H
#define BITCOIN_IPC_CAPNP_PROTOCOL_H
#include <memory>
namespace ipc {
class Protocol;
namespace capnp {
std::unique_ptr<Protocol> MakeCapnpProtocol();
} // namespace capnp
} // namespace ipc
#endif // BITCOIN_IPC_CAPNP_PROTOCOL_H

20
src/ipc/exception.h Normal file
View File

@ -0,0 +1,20 @@
// Copyright (c) 2021 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_IPC_EXCEPTION_H
#define BITCOIN_IPC_EXCEPTION_H
#include <stdexcept>
namespace ipc {
//! Exception class thrown when a call to remote method fails due to an IPC
//! error, like a socket getting disconnected.
class Exception : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
} // namespace ipc
#endif // BITCOIN_IPC_EXCEPTION_H

77
src/ipc/interfaces.cpp Normal file
View File

@ -0,0 +1,77 @@
// Copyright (c) 2021 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 <fs.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <ipc/capnp/protocol.h>
#include <ipc/process.h>
#include <ipc/protocol.h>
#include <logging.h>
#include <tinyformat.h>
#include <util/system.h>
#include <functional>
#include <memory>
#include <stdexcept>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <utility>
#include <vector>
namespace ipc {
namespace {
class IpcImpl : public interfaces::Ipc
{
public:
IpcImpl(const char* exe_name, const char* process_argv0, interfaces::Init& init)
: m_exe_name(exe_name), m_process_argv0(process_argv0), m_init(init),
m_protocol(ipc::capnp::MakeCapnpProtocol()), m_process(ipc::MakeProcess())
{
}
std::unique_ptr<interfaces::Init> spawnProcess(const char* new_exe_name) override
{
int pid;
int fd = m_process->spawn(new_exe_name, m_process_argv0, pid);
LogPrint(::BCLog::IPC, "Process %s pid %i launched\n", new_exe_name, pid);
auto init = m_protocol->connect(fd, m_exe_name);
Ipc::addCleanup(*init, [this, new_exe_name, pid] {
int status = m_process->waitSpawned(pid);
LogPrint(::BCLog::IPC, "Process %s pid %i exited with status %i\n", new_exe_name, pid, status);
});
return init;
}
bool startSpawnedProcess(int argc, char* argv[], int& exit_status) override
{
exit_status = EXIT_FAILURE;
int32_t fd = -1;
if (!m_process->checkSpawned(argc, argv, fd)) {
return false;
}
m_protocol->serve(fd, m_exe_name, m_init);
exit_status = EXIT_SUCCESS;
return true;
}
void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) override
{
m_protocol->addCleanup(type, iface, std::move(cleanup));
}
const char* m_exe_name;
const char* m_process_argv0;
interfaces::Init& m_init;
std::unique_ptr<Protocol> m_protocol;
std::unique_ptr<Process> m_process;
};
} // namespace
} // namespace ipc
namespace interfaces {
std::unique_ptr<Ipc> MakeIpc(const char* exe_name, const char* process_argv0, Init& init)
{
return std::make_unique<ipc::IpcImpl>(exe_name, process_argv0, init);
}
} // namespace interfaces

61
src/ipc/process.cpp Normal file
View File

@ -0,0 +1,61 @@
// Copyright (c) 2021 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 <fs.h>
#include <ipc/process.h>
#include <ipc/protocol.h>
#include <mp/util.h>
#include <tinyformat.h>
#include <util/strencodings.h>
#include <cstdint>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <stdlib.h>
#include <string.h>
#include <system_error>
#include <unistd.h>
#include <utility>
#include <vector>
namespace ipc {
namespace {
class ProcessImpl : public Process
{
public:
int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) override
{
return mp::SpawnProcess(pid, [&](int fd) {
fs::path path = argv0_path;
path.remove_filename();
path.append(new_exe_name);
return std::vector<std::string>{path.string(), "-ipcfd", strprintf("%i", fd)};
});
}
int waitSpawned(int pid) override { return mp::WaitProcess(pid); }
bool checkSpawned(int argc, char* argv[], int& fd) override
{
// If this process was not started with a single -ipcfd argument, it is
// not a process spawned by the spawn() call above, so return false and
// do not try to serve requests.
if (argc != 3 || strcmp(argv[1], "-ipcfd") != 0) {
return false;
}
// If a single -ipcfd argument was provided, return true and get the
// file descriptor so Protocol::serve() can be called to handle
// requests from the parent process. The -ipcfd argument is not valid
// in combination with other arguments because the parent process
// should be able to control the child process through the IPC protocol
// without passing information out of band.
if (!ParseInt32(argv[2], &fd)) {
throw std::runtime_error(strprintf("Invalid -ipcfd number '%s'", argv[2]));
}
return true;
}
};
} // namespace
std::unique_ptr<Process> MakeProcess() { return std::make_unique<ProcessImpl>(); }
} // namespace ipc

42
src/ipc/process.h Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2021 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_IPC_PROCESS_H
#define BITCOIN_IPC_PROCESS_H
#include <memory>
#include <string>
namespace ipc {
class Protocol;
//! IPC process interface for spawning bitcoin processes and serving requests
//! in processes that have been spawned.
//!
//! There will be different implementations of this interface depending on the
//! platform (e.g. unix, windows).
class Process
{
public:
virtual ~Process() = default;
//! Spawn process and return socket file descriptor for communicating with
//! it.
virtual int spawn(const std::string& new_exe_name, const fs::path& argv0_path, int& pid) = 0;
//! Wait for spawned process to exit and return its exit code.
virtual int waitSpawned(int pid) = 0;
//! Parse command line and determine if current process is a spawned child
//! process. If so, return true and a file descriptor for communicating
//! with the parent process.
virtual bool checkSpawned(int argc, char* argv[], int& fd) = 0;
};
//! Constructor for Process interface. Implementation will vary depending on
//! the platform (unix or windows).
std::unique_ptr<Process> MakeProcess();
} // namespace ipc
#endif // BITCOIN_IPC_PROCESS_H

39
src/ipc/protocol.h Normal file
View File

@ -0,0 +1,39 @@
// Copyright (c) 2021 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_IPC_PROTOCOL_H
#define BITCOIN_IPC_PROTOCOL_H
#include <interfaces/init.h>
#include <functional>
#include <memory>
#include <typeindex>
namespace ipc {
//! IPC protocol interface for calling IPC methods over sockets.
//!
//! There may be different implementations of this interface for different IPC
//! protocols (e.g. Cap'n Proto, gRPC, JSON-RPC, or custom protocols).
class Protocol
{
public:
virtual ~Protocol() = default;
//! Return Init interface that forwards requests over given socket descriptor.
//! Socket communication is handled on a background thread.
virtual std::unique_ptr<interfaces::Init> connect(int fd, const char* exe_name) = 0;
//! Handle requests on provided socket descriptor, forwarding them to the
//! provided Init interface. Socket communication is handled on the
//! current thread, and this call blocks until the socket is closed.
virtual void serve(int fd, const char* exe_name, interfaces::Init& init) = 0;
//! Add cleanup callback to interface that will run when the interface is
//! deleted.
virtual void addCleanup(std::type_index type, void* iface, std::function<void()> cleanup) = 0;
};
} // namespace ipc
#endif // BITCOIN_IPC_PROTOCOL_H

View File

@ -157,6 +157,7 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::LEVELDB, "leveldb"},
{BCLog::VALIDATION, "validation"},
{BCLog::I2P, "i2p"},
{BCLog::IPC, "ipc"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};

View File

@ -58,6 +58,7 @@ namespace BCLog {
LEVELDB = (1 << 20),
VALIDATION = (1 << 21),
I2P = (1 << 22),
IPC = (1 << 23),
ALL = ~(uint32_t)0,
};

View File

@ -15,7 +15,7 @@ REGEXP_EXCLUDE_FILES_WITH_PREFIX="src/(crypto/ctaes/|leveldb/|crc32c/|secp256k1/
EXIT_CODE=0
for HEADER_FILE in $(git ls-files -- "*.h" | grep -vE "^${REGEXP_EXCLUDE_FILES_WITH_PREFIX}")
do
HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr "[:lower:]" "[:upper:]")
HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr - _ | tr "[:lower:]" "[:upper:]")
HEADER_ID="${HEADER_ID_PREFIX}${HEADER_ID_BASE}${HEADER_ID_SUFFIX}"
if [[ $(grep -cE "^#(ifndef|define) ${HEADER_ID}" "${HEADER_FILE}") != 2 ]]; then
echo "${HEADER_FILE} seems to be missing the expected include guard:"