0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-07 10:27:47 -05:00
This commit is contained in:
Sjors Provoost 2025-02-01 00:05:12 +01:00 committed by GitHub
commit 226f6477bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 5957 additions and 56 deletions

View file

@ -186,13 +186,13 @@ task:
FILE_ENV: "./ci/test/00_setup_env_native_fuzz.sh"
task:
name: 'multiprocess, i686, DEBUG'
name: 'no multiprocess, i686, DEBUG'
<< : *GLOBAL_TASK_TEMPLATE
persistent_worker:
labels:
type: medium
env:
FILE_ENV: "./ci/test/00_setup_env_i686_multiprocess.sh"
FILE_ENV: "./ci/test/00_setup_env_i686_no_multiprocess.sh"
task:
name: 'no wallet, libbitcoinkernel'

View file

@ -67,12 +67,12 @@ jobs:
echo "TEST_BASE=$(git rev-list -n$((${{ env.MAX_COUNT }} + 1)) --reverse HEAD $EXCLUDE_MERGE_BASE_ANCESTORS | head -1)" >> "$GITHUB_ENV"
- run: |
sudo apt-get update
sudo apt-get install clang ccache build-essential cmake pkgconf python3-zmq libevent-dev libboost-dev libsqlite3-dev libdb++-dev systemtap-sdt-dev libzmq3-dev qtbase5-dev qttools5-dev qttools5-dev-tools qtwayland5 libqrencode-dev -y
sudo apt-get install clang ccache build-essential cmake pkgconf python3-zmq libevent-dev libboost-dev libsqlite3-dev libdb++-dev systemtap-sdt-dev libzmq3-dev qtbase5-dev qttools5-dev qttools5-dev-tools qtwayland5 libqrencode-dev libcapnp-dev capnproto -y
- name: Compile and run tests
run: |
# Run tests on commits after the last merge commit and before the PR head commit
# Use clang++, because it is a bit faster and uses less memory than g++
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' && cmake --build build -j $(nproc) && ctest --output-on-failure --stop-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }}
git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DBUILD_MULTIPROCESS=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' && cmake --build build -j $(nproc) && ctest --output-on-failure --stop-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }}
macos-native-arm64:
name: ${{ matrix.job-name }}
@ -124,7 +124,7 @@ jobs:
run: |
# A workaround for "The `brew link` step did not complete successfully" error.
brew install --quiet python@3 || brew link --overwrite python@3
brew install --quiet coreutils ninja pkgconf gnu-getopt ccache boost libevent zeromq qt@5 qrencode
brew install --quiet coreutils ninja pkgconf gnu-getopt ccache boost libevent zeromq qt@5 qrencode capnp
- name: Set Ccache directory
run: echo "CCACHE_DIR=${RUNNER_TEMP}/ccache_dir" >> "$GITHUB_ENV"

View file

@ -141,8 +141,9 @@ endif()
cmake_dependent_option(WITH_DBUS "Enable DBus support." ON "CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND BUILD_GUI" OFF)
option(WITH_MULTIPROCESS "Build multiprocess bitcoin-node and bitcoin-gui executables in addition to monolithic bitcoind and bitcoin-qt executables. Requires libmultiprocess library. Experimental." OFF)
if(WITH_MULTIPROCESS)
option(BUILD_MULTIPROCESS "Build multiprocess bitcoin-node and bitcoin-gui executables in addition to monolithic bitcoind and bitcoin-qt executables. Requires libmultiprocess library. Experimental." OFF)
option(WITH_LIBMULTIPROCESS "Build with external libmultiprocess library instead of with local git submodule when BUILD_MULTIPROCESS is enabled. This is necessary in cross-compiled builds to provide a native code-generation tool, and also useful for making changes to the upstream project." OFF)
if(WITH_LIBMULTIPROCESS)
find_package(Libmultiprocess REQUIRED COMPONENTS Lib)
find_package(LibmultiprocessNative REQUIRED COMPONENTS Bin
NAMES Libmultiprocess
@ -609,9 +610,9 @@ message("Configure summary")
message("=================")
message("Executables:")
message(" bitcoind ............................ ${BUILD_DAEMON}")
message(" bitcoin-node (multiprocess) ......... ${WITH_MULTIPROCESS}")
message(" bitcoin-node (multiprocess) ......... ${BUILD_MULTIPROCESS}")
message(" bitcoin-qt (GUI) .................... ${BUILD_GUI}")
if(BUILD_GUI AND WITH_MULTIPROCESS)
if(BUILD_GUI AND BUILD_MULTIPROCESS)
set(bitcoin_gui_status ON)
else()
set(bitcoin_gui_status OFF)

View file

@ -70,6 +70,7 @@
"BUILD_GUI": "ON",
"BUILD_GUI_TESTS": "ON",
"BUILD_KERNEL_LIB": "ON",
"BUILD_MULTIPROCESS": "ON",
"BUILD_SHARED_LIBS": "ON",
"BUILD_TESTS": "ON",
"BUILD_TX": "ON",
@ -81,7 +82,6 @@
"ENABLE_WALLET": "ON",
"WARN_INCOMPATIBLE_BDB": "OFF",
"WITH_BDB": "ON",
"WITH_MULTIPROCESS": "ON",
"WITH_QRENCODE": "ON",
"WITH_SQLITE": "ON",
"WITH_USDT": "ON",

View file

@ -16,6 +16,7 @@ export USE_BUSY_BOX=true
export RUN_UNIT_TESTS=true
export RUN_FUNCTIONAL_TESTS=false
export GOAL="install"
export DEP_OPTS="MULTIPROCESS=1"
# -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1"
# This could be removed once the ABI change warning does not show up by default
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DCMAKE_CXX_FLAGS='-Wno-psabi -Wno-error=maybe-uninitialized'"

View file

@ -7,11 +7,11 @@
export LC_ALL=C.UTF-8
export HOST=i686-pc-linux-gnu
export CONTAINER_NAME=ci_i686_multiprocess
export CONTAINER_NAME=ci_i686_no_multiprocess
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CI_IMAGE_PLATFORM="linux/amd64"
export PACKAGES="llvm clang g++-multilib"
export DEP_OPTS="DEBUG=1 MULTIPROCESS=1"
export DEP_OPTS="DEBUG=1"
export GOAL="install"
export TEST_RUNNER_EXTRA="--v2transport"
export BITCOIN_CONFIG="\
@ -21,4 +21,3 @@ export BITCOIN_CONFIG="\
-DCMAKE_CXX_FLAGS='-Wno-error=documentation' \
-DAPPEND_CPPFLAGS='-DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE' \
"
export BITCOIND=bitcoin-node # Used in functional tests

View file

@ -17,4 +17,5 @@ export XCODE_BUILD_ID=15A240d
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
export DEP_OPTS="MULTIPROCESS=1"
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DREDUCE_EXPORTS=ON"

View file

@ -11,7 +11,13 @@ export LC_ALL=C.UTF-8
export PIP_PACKAGES="--break-system-packages zmq"
export GOAL="install"
export CMAKE_GENERATOR="Ninja"
export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DREDUCE_EXPORTS=ON"
export BITCOIN_CONFIG="\
-DBUILD_MULTIPROCESS=ON \
-DBUILD_GUI=ON \
-DWITH_ZMQ=ON \
-DREDUCE_EXPORTS=ON \
"
export CI_OS_NAME="macos"
export NO_DEPENDS=1
export OSX_SDK=""
export BITCOIND=bitcoin-node # Used in functional tests

View file

@ -20,11 +20,16 @@ fi
export CONTAINER_NAME=ci_native_asan
export APT_LLVM_V="19"
export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev python3-zmq qtbase5-dev qttools5-dev qttools5-dev-tools libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}"
export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev python3-zmq qtbase5-dev qttools5-dev qttools5-dev-tools libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE} libcapnp-dev capnproto"
export NO_DEPENDS=1
export GOAL="install"
export BITCOIN_CONFIG="\
-DWITH_USDT=ON -DWITH_ZMQ=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=ON \
-DBUILD_MULTIPROCESS=ON \
-DWITH_USDT=ON \
-DWITH_ZMQ=ON \
-DWITH_BDB=ON \
-DWARN_INCOMPATIBLE_BDB=OFF \
-DBUILD_GUI=ON \
-DSANITIZERS=address,float-divide-by-zero,integer,undefined \
-DCMAKE_C_COMPILER=clang-${APT_LLVM_V} \
-DCMAKE_CXX_COMPILER=clang++-${APT_LLVM_V} \

View file

@ -10,6 +10,7 @@ export CONTAINER_NAME=ci_native_centos
export CI_IMAGE_NAME_TAG="quay.io/centos/centos:stream10"
export CI_BASE_PACKAGES="gcc-c++ glibc-devel libstdc++-devel ccache make git python3 python3-pip which patch xz procps-ng ksh rsync coreutils bison e2fsprogs cmake"
export PIP_PACKAGES="pyzmq"
export DEP_OPTS="DEBUG=1" # Temporarily enable a DEBUG=1 build to check for GCC-bug-117966 regressions. This can be removed once the minimum GCC version is bumped to 12 in the previous releases task, see https://github.com/bitcoin/bitcoin/issues/31436#issuecomment-2530717875
export DEP_OPTS="DEBUG=1 MULTIPROCESS=1" # Temporarily enable a DEBUG=1 build to check for GCC-bug-117966 regressions. This can be removed once the minimum GCC version is bumped to 12 in the previous releases task, see https://github.com/bitcoin/bitcoin/issues/31436#issuecomment-2530717875
export GOAL="install"
export BITCOIN_CONFIG="-DWITH_ZMQ=ON -DBUILD_GUI=ON -DREDUCE_EXPORTS=ON -DCMAKE_BUILD_TYPE=Debug"
export BITCOIND=bitcoin-node # Used in functional tests

View file

@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CONTAINER_NAME=ci_native_fuzz
export APT_LLVM_V="19"
export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libevent-dev libboost-dev libsqlite3-dev"
export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libevent-dev libboost-dev libsqlite3-dev libcapnp-dev capnproto"
export NO_DEPENDS=1
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false

View file

@ -15,7 +15,7 @@ export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}"
export CONTAINER_NAME="ci_native_fuzz_msan"
export PACKAGES="ninja-build"
# BDB generates false-positives and will be removed in future
export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 MULTIPROCESS=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export GOAL="install"
# Setting CMAKE_{C,CXX}_FLAGS_DEBUG flags to an empty string ensures that the flags set in MSAN_FLAGS remain unaltered.
# _FORTIFY_SOURCE is not compatible with MSAN.

View file

@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CONTAINER_NAME=ci_native_fuzz_valgrind
export PACKAGES="clang-16 llvm-16 libclang-rt-16-dev libevent-dev libboost-dev libsqlite3-dev valgrind"
export PACKAGES="clang-16 llvm-16 libclang-rt-16-dev libevent-dev libboost-dev libsqlite3-dev valgrind libcapnp-dev capnproto"
export NO_DEPENDS=1
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
@ -16,6 +16,7 @@ export RUN_FUZZ_TESTS=true
export FUZZ_TESTS_CONFIG="--valgrind"
export GOAL="install"
export BITCOIN_CONFIG="\
-DBUILD_MULTIPROCESS=ON \
-DBUILD_FOR_FUZZING=ON \
-DSANITIZERS=fuzzer \
-DCMAKE_C_COMPILER=clang-16 \

View file

@ -15,7 +15,7 @@ export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}"
export CONTAINER_NAME="ci_native_msan"
export PACKAGES="ninja-build"
# BDB generates false-positives and will be removed in future
export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 MULTIPROCESS=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export GOAL="install"
# Setting CMAKE_{C,CXX}_FLAGS_DEBUG flags to an empty string ensures that the flags set in MSAN_FLAGS remain unaltered.
# _FORTIFY_SOURCE is not compatible with MSAN.

View file

@ -10,6 +10,6 @@ export CONTAINER_NAME=ci_native_nowallet_libbitcoinkernel
export CI_IMAGE_NAME_TAG="docker.io/debian:bookworm"
# Use minimum supported python3.10 (or best-effort 3.11) and clang-16, see doc/dependencies.md
export PACKAGES="python3-zmq clang-16 llvm-16 libc++abi-16-dev libc++-16-dev"
export DEP_OPTS="NO_WALLET=1 CC=clang-16 CXX='clang++-16 -stdlib=libc++'"
export DEP_OPTS="NO_WALLET=1 MULTIPROCESS=1 CC=clang-16 CXX='clang++-16 -stdlib=libc++'"
export GOAL="install"
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_LIB=ON -DBUILD_SHARED_LIBS=ON"

View file

@ -10,7 +10,7 @@ export CONTAINER_NAME=ci_native_previous_releases
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:22.04"
# Use minimum supported python3.10 and gcc-11, see doc/dependencies.md
export PACKAGES="gcc-11 g++-11 python3-zmq"
export DEP_OPTS="DEBUG=1 CC=gcc-11 CXX=g++-11"
export DEP_OPTS="DEBUG=1 MULTIPROCESS=1 CC=gcc-11 CXX=g++-11"
export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude feature_dbcrash" # Run extended tests so that coverage does not fail, but exclude the very slow dbcrash
export RUN_UNIT_TESTS_SEQUENTIAL="true"
export RUN_UNIT_TESTS="false"

View file

@ -10,7 +10,7 @@ export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CONTAINER_NAME=ci_native_tidy
export TIDY_LLVM_V="19"
export APT_LLVM_V="${TIDY_LLVM_V}"
export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libzmq3-dev systemtap-sdt-dev qtbase5-dev qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev"
export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libzmq3-dev systemtap-sdt-dev qtbase5-dev qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev libcapnp-dev capnproto"
export NO_DEPENDS=1
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
@ -19,7 +19,13 @@ export RUN_CHECK_DEPS=true
export RUN_TIDY=true
export GOAL="install"
export BITCOIN_CONFIG="\
-DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DWITH_USDT=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF \
-DBUILD_MULTIPROCESS=ON \
-DWITH_ZMQ=ON \
-DBUILD_GUI=ON \
-DBUILD_BENCH=ON \
-DWITH_USDT=ON \
-DWITH_BDB=ON \
-DWARN_INCOMPATIBLE_BDB=OFF \
-DENABLE_HARDENING=OFF \
-DCMAKE_C_COMPILER=clang-${TIDY_LLVM_V} \
-DCMAKE_CXX_COMPILER=clang++-${TIDY_LLVM_V} \

View file

@ -10,7 +10,12 @@ export CONTAINER_NAME=ci_native_tsan
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export APT_LLVM_V="19"
export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libc++abi-${APT_LLVM_V}-dev libc++-${APT_LLVM_V}-dev python3-zmq"
export DEP_OPTS="CC=clang-${APT_LLVM_V} CXX='clang++-${APT_LLVM_V} -stdlib=libc++'"
export DEP_OPTS="CC=clang-${APT_LLVM_V} CXX='clang++-${APT_LLVM_V} -stdlib=libc++' MULTIPROCESS=1"
export LIBMULTIPROCESS_CMAKE_OPTIONS="-DCMAKE_CXX_FLAGS=-fsanitize=thread"
export GOAL="install"
export BITCOIN_CONFIG="-DWITH_ZMQ=ON -DSANITIZERS=thread \
-DAPPEND_CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES'"
export BITCOIN_CONFIG="\
-DBUILD_MULTIPROCESS=ON \
-DWITH_ZMQ=ON \
-DSANITIZERS=thread \
-DAPPEND_CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES'\
"

View file

@ -8,14 +8,18 @@ export LC_ALL=C.UTF-8
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CONTAINER_NAME=ci_native_valgrind
export PACKAGES="valgrind clang-16 llvm-16 libclang-rt-16-dev python3-zmq libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libsqlite3-dev"
export PACKAGES="valgrind clang-16 llvm-16 libclang-rt-16-dev python3-zmq libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libsqlite3-dev libcapnp-dev capnproto"
export USE_VALGRIND=1
export NO_DEPENDS=1
export TEST_RUNNER_EXTRA="--exclude feature_init,rpc_bind,feature_bind_extra" # feature_init excluded for now, see https://github.com/bitcoin/bitcoin/issues/30011 ; bind tests excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
export GOAL="install"
# TODO enable GUI
export BITCOIN_CONFIG="\
-DWITH_ZMQ=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=OFF \
-DBUILD_MULTIPROCESS=ON \
-DWITH_ZMQ=ON
-DWITH_BDB=ON
-DWARN_INCOMPATIBLE_BDB=OFF
-DBUILD_GUI=OFF \
-DCMAKE_C_COMPILER=clang-16 \
-DCMAKE_CXX_COMPILER=clang++-16 \
"

View file

@ -14,4 +14,5 @@ export CI_IMAGE_PLATFORM="linux/s390x"
export TEST_RUNNER_EXTRA="--exclude rpc_bind,feature_bind_extra" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
export RUN_FUNCTIONAL_TESTS=true
export GOAL="install"
export DEP_OPTS="MULTIPROCESS=1"
export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON"

View file

@ -42,7 +42,7 @@ function(add_maintenance_targets)
VERBATIM
)
foreach(target IN ITEMS bitcoind bitcoin-qt bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet test_bitcoin bench_bitcoin)
foreach(target IN ITEMS bitcoind bitcoin-node bitcoin-qt bitcoin-gui bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet test_bitcoin bench_bitcoin)
if(TARGET ${target})
list(APPEND executables $<TARGET_FILE:${target}>)
endif()

View file

@ -251,8 +251,9 @@ endef
define check_or_remove_sources
mkdir -p $($(package)_source_dir); cd $($(package)_source_dir); \
test -f $($(package)_fetched) && ( $(build_SHA256SUM) -c $($(package)_fetched) >/dev/null 2>/dev/null || \
test -f $($(package)_fetched) && ( $(build_SHA256SUM) -c $($(package)_fetched) >/dev/null || \
( echo "Checksum missing or mismatched for $(package) source. Forcing re-download."; \
set -x; cat $($(package)_fetched); : $($(package)_sha256_hash); ls -ld --full-time $(CURDIR)/$($(package)_local_dir) $($(package)_fetched) $($(package)_source); \
rm -f $($(package)_all_sources) $($(1)_fetched))) || true
endef

View file

@ -119,7 +119,7 @@ The following can be set when running make: `make FOO=bar`
- `NO_BDB`: Don't download/build/cache BerkeleyDB
- `NO_SQLITE`: Don't download/build/cache SQLite
- `NO_USDT`: Don't download/build/cache packages needed for enabling USDT tracepoints
- `MULTIPROCESS`: Build libmultiprocess (experimental)
- `MULTIPROCESS`: Build libmultiprocess (experimental, no Windows and OpenBSD)
- `DEBUG`: Disable some optimizations and enable more runtime checking
- `HOST_ID_SALT`: Optional salt to use when generating host package ids
- `BUILD_ID_SALT`: Optional salt to use when generating build package ids

View file

@ -39,8 +39,23 @@ define fetch_file
$(call fetch_file_inner,$(1),$(FALLBACK_DOWNLOAD_PATH),$(3),$(4),$(5))))
endef
define fetch_local_dir_sha256
set -x; if ! [ -f $($(1)_source) ] || [ $($(1)_source) -ot $($(1)_local_dir) ]; then \
mkdir -p $(dir $($(1)_source)) && \
$(build_TAR) -c -f $($(1)_source) -C $($(1)_local_dir) . && \
rm -f $($(1)_fetched); \
fi && \
if ! [ -f $($(1)_fetched) ] || [ $($(1)_fetched) -ot $($(1)_source) ]; then \
mkdir -p $(dir $($(1)_fetched)) && \
$(build_SHA256SUM) $($(1)_source) > $($(1)_fetched); \
fi && \
cut -d" " -f1 $($(1)_fetched)
endef
define int_get_build_recipe_hash
$(eval $(1)_all_file_checksums:=$(shell $(build_SHA256SUM) $(meta_depends) packages/$(1).mk $(addprefix $(PATCHES_PATH)/$(1)/,$($(1)_patches)) | cut -d" " -f1))
$(if $($(1)_local_dir),$(eval $(1)_sha256_hash:=$(shell $(call fetch_local_dir_sha256,$(1)))))
$(if $($(1)_local_dir),$(eval $(1)_all_file_checksums+=$($(1)_sha256_hash)))
$(eval $(1)_recipe_hash:=$(shell echo -n "$($(1)_all_file_checksums)" | $(build_SHA256SUM) | cut -d" " -f1))
endef
@ -53,20 +68,23 @@ $(eval $(1)_build_id:=$(shell echo -n "$($(1)_build_id_long)" | $(build_SHA256SU
final_build_id_long+=$($(package)_build_id_long)
#compute package-specific paths
$(1)_build_subdir?=.
$(1)_download_file?=$($(1)_file_name)
$(1)_source_dir:=$(SOURCES_PATH)
$(1)_source:=$$($(1)_source_dir)/$($(1)_file_name)
$(1)_staging_dir=$(base_staging_dir)/$(host)/$(1)/$($(1)_version)-$($(1)_build_id)
$(1)_staging_prefix_dir:=$$($(1)_staging_dir)$($($(1)_type)_prefix)
$(1)_extract_dir:=$(base_build_dir)/$(host)/$(1)/$($(1)_version)-$($(1)_build_id)
$(1)_download_dir:=$(base_download_dir)/$(1)-$($(1)_version)
$(1)_build_dir:=$$($(1)_extract_dir)/$$($(1)_build_subdir)
$(1)_cached_checksum:=$(BASE_CACHE)/$(host)/$(1)/$(1)-$($(1)_version)-$($(1)_build_id).tar.gz.hash
$(1)_patch_dir:=$(base_build_dir)/$(host)/$(1)/$($(1)_version)-$($(1)_build_id)/.patches-$($(1)_build_id)
$(1)_prefixbin:=$($($(1)_type)_prefix)/bin/
$(1)_cached:=$(BASE_CACHE)/$(host)/$(1)/$(1)-$($(1)_version)-$($(1)_build_id).tar.gz
$(1)_build_log:=$(BASEDIR)/$(1)-$($(1)_version)-$($(1)_build_id).log
endef
define int_get_build_properties
$(1)_build_subdir?=.
$(1)_download_file?=$($(1)_file_name)
$(1)_source_dir:=$(SOURCES_PATH)
$(1)_source:=$$($(1)_source_dir)/$(if $($(1)_local_dir),$(subst $(null) $(null),-,$(strip $(subst ., ,$(subst /, ,$($(1)_local_dir))))).tar,$($(1)_file_name))
$(1)_download_dir:=$(base_download_dir)/$(1)-$($(1)_version)
$(1)_prefixbin:=$($($(1)_type)_prefix)/bin/
$(1)_all_sources=$($(1)_file_name) $($(1)_extra_sources)
#stamps
@ -287,6 +305,9 @@ $(foreach package,$(all_packages),$(eval $(call int_vars,$(package))))
$(foreach native_package,$(native_packages),$(eval include packages/$(native_package).mk))
$(foreach package,$(packages),$(eval include packages/$(package).mk))
#set build properties for included package files
$(foreach package,$(all_packages),$(eval $(call int_get_build_properties,$(package))))
#compute a hash of all files that comprise this package's build recipe
$(foreach package,$(all_packages),$(eval $(call int_get_build_recipe_hash,$(package))))

View file

@ -11,7 +11,8 @@ General tips:
[below](#secondary-dependencies) for more details.
## Identifiers
Each package is required to define at least these variables:
If package does not define a `$(package)_local_dir` variable, it is required to
define these variables:
$(package)_version:
Version of the upstream library or program. If there is no version, a
@ -28,6 +29,9 @@ Each package is required to define at least these variables:
$(package)_sha256_hash:
The sha256 hash of the upstream file
If a package does define a `$(package)_local_dir` variable, the above variables
are not required and will be ignored.
These variables are optional:
$(package)_build_subdir:
@ -48,6 +52,18 @@ These variables are optional:
Any extra files that will be fetched via $(package)_fetch_cmds. These are
specified so that they can be fetched and verified via 'make download'.
## Local packages
If a package defines a `$(package)_local_dir` variable, the specified directory
will be treated as a download source, and a tarball of its contents will be
saved to `sources/`. A hash of the tarball will also become part of the package
build id, so if the directory contents change, the package and everything
depending on it will be rebuilt. For efficiency, the tarball is cached once it
has been created, but if the local directory is touched, it will be rebuilt.
Local packages can be useful for using git submodules or subtrees to manage
package sources, or for testing local changes that are not available to
download from an external source.
## Build Variables:
After defining the main identifiers, build variables may be added or customized

View file

@ -1,5 +1,6 @@
package=libmultiprocess
$(package)_version=$(native_$(package)_version)
$(package)_local_dir=$(native_$(package)_local_dir)
$(package)_download_path=$(native_$(package)_download_path)
$(package)_file_name=$(native_$(package)_file_name)
$(package)_sha256_hash=$(native_$(package)_sha256_hash)

View file

@ -1,12 +1,9 @@
package=native_libmultiprocess
$(package)_version=07c917f7ca910d66abc6d3873162fc9061704074
$(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive
$(package)_file_name=$($(package)_version).tar.gz
$(package)_sha256_hash=ac9db311e3b22aac3c7b7b7b3f6b7fee5cf3043ebb3c3bf412049e8b17166de8
$(package)_local_dir=../src/ipc/libmultiprocess
$(package)_dependencies=native_capnp
define $(package)_config_cmds
$($(package)_cmake) .
$($(package)_cmake) . $(LIBMULTIPROCESS_CMAKE_OPTIONS)
endef
define $(package)_build_cmds

View file

@ -153,9 +153,11 @@ else()
endif()
if("@multiprocess@" STREQUAL "1")
set(WITH_MULTIPROCESS ON CACHE BOOL "")
set(BUILD_MULTIPROCESS ON CACHE BOOL "")
set(WITH_LIBMULTIPROCESS ON CACHE BOOL "")
set(Libmultiprocess_ROOT "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "")
set(LibmultiprocessNative_ROOT "${CMAKE_CURRENT_LIST_DIR}/native" CACHE PATH "")
else()
set(WITH_MULTIPROCESS OFF CACHE BOOL "")
set(BUILD_MULTIPROCESS OFF CACHE BOOL "")
set(WITH_LIBMULTIPROCESS OFF CACHE BOOL "")
endif()

View file

@ -8,7 +8,7 @@
| *libbitcoin_crypto* | Hardware-optimized functions for data encryption, hashing, message authentication, and key derivation. |
| *libbitcoin_kernel* | Consensus engine and support library used for validation by *libbitcoin_node*. |
| *libbitcoinqt* | GUI functionality used by *bitcoin-qt* and *bitcoin-gui* executables. |
| *libbitcoin_ipc* | IPC functionality used by *bitcoin-node*, *bitcoin-wallet*, *bitcoin-gui* executables to communicate when [`-DWITH_MULTIPROCESS=ON`](multiprocess.md) is used. |
| *libbitcoin_ipc* | IPC functionality used by *bitcoin-node*, *bitcoin-wallet*, *bitcoin-gui* executables to communicate when [`-DBUILD_MULTIPROCESS=ON`](multiprocess.md) is used. |
| *libbitcoin_node* | P2P and RPC server functionality used by *bitcoind* and *bitcoin-qt* executables. |
| *libbitcoin_util* | Home for common functionality shared by different executables and libraries. Similar to *libbitcoin_common*, but lower-level (see [Dependencies](#dependencies)). |
| *libbitcoin_wallet* | Wallet functionality used by *bitcoind* and *bitcoin-wallet* executables. |

View file

@ -1232,6 +1232,9 @@ Current subtrees include:
- src/minisketch
- Upstream at https://github.com/sipa/minisketch ; maintained by Core contributors.
- src/ipc/libmultiprocess
- Upstream at https://github.com/chaincodelabs/libmultiprocess ; maintained by Core contributors.
Upgrading LevelDB
---------------------

View file

@ -4,7 +4,7 @@ _This document describes usage of the multiprocess feature. For design informati
## Build Option
On Unix systems, the `-DWITH_MULTIPROCESS=ON` build option can be passed to build the supplemental `bitcoin-node` and `bitcoin-gui` multiprocess executables.
The `-DBUILD_MULTIPROCESS=ON` build option, supported on Unix systems, can be passed to build the supplemental `bitcoin-node` and `bitcoin-gui` multiprocess executables.
## Debugging
@ -12,7 +12,11 @@ The `-debug=ipc` command line option can be used to see requests and responses b
## Installation
The multiprocess feature requires [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) as dependencies. A simple way to get started using it without installing these dependencies manually is to use the [depends system](../depends) with the `MULTIPROCESS=1` [dependency option](../depends#dependency-options) passed to make:
The multiprocess feature requires [Cap'n Proto](https://capnproto.org/) as an external dependency, which is available from most package managers. See [build-unix.md](build-unix.md) and [build-osx.md](build-osx.md) for information about installing dependencies.
### Depends installation
Alternately the [depends system](../depends) can be used to avoid need to install dependencies. A simple way to get started is to pass the `MULTIPROCESS=1` [dependency option](../depends#dependency-options) to make:
```
cd <BITCOIN_SOURCE_DIRECTORY>
@ -25,9 +29,13 @@ build/src/bitcoin-node -regtest -printtoconsole -debug=ipc
BITCOIND=$(pwd)/build/src/bitcoin-node build/test/functional/test_runner.py
```
The `cmake` build will pick up settings and library locations from the depends directory, so there is no need to pass `-DWITH_MULTIPROCESS=ON` as a separate flag when using the depends system (it's controlled by the `MULTIPROCESS=1` option).
The `cmake` build will pick up settings and library locations from the depends directory, so there is no need to pass `-DBUILD_MULTIPROCESS=ON` as a separate flag when using the depends system (it's controlled by the `MULTIPROCESS=1` option).
Alternately, you can install [Cap'n Proto](https://capnproto.org/) and [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) packages on your system, and just run `cmake -B build -DWITH_MULTIPROCESS=ON` without using the depends system. The `cmake` build will be able to locate the installed packages via [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/). See [Installation](https://github.com/chaincodelabs/libmultiprocess/blob/master/doc/install.md) section of the libmultiprocess readme for install steps. See [build-unix.md](build-unix.md) and [build-osx.md](build-osx.md) for information about installing dependencies in general.
### External libmultiprocess installation
Optionally, when not using depends, the [libmultiprocess](https://github.com/chaincodelabs/libmultiprocess) library can be installed as an external dependency. This can enabled with the `-DWITH_LIBMULTIPROCESS=ON` option, which is off by default. When it is disabled, the libmultiprocess sources in ../src/ipc/libmultiprocess are used instead. See [Installation](https://github.com/chaincodelabs/libmultiprocess/blob/master/doc/install.md) section of the libmultiprocess documentation for install steps.
Installing libmultiprocess externally is only necessary when cross-compiling (to provide a code generation binary that can run on the native system). It can also be useful when developing changes meant to be submitted upstream.
## Usage

View file

@ -26,7 +26,7 @@ add_dependencies(bitcoin_clientversion generate_build_info)
add_subdirectory(crypto)
add_subdirectory(univalue)
add_subdirectory(util)
if(WITH_MULTIPROCESS)
if(BUILD_MULTIPROCESS)
add_subdirectory(ipc)
endif()
@ -320,7 +320,7 @@ if(BUILD_DAEMON)
)
list(APPEND installable_targets bitcoind)
endif()
if(WITH_MULTIPROCESS)
if(BUILD_MULTIPROCESS)
add_executable(bitcoin-node
bitcoind.cpp
init/bitcoin-node.cpp

View file

@ -2,6 +2,18 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.
# Don't use internal libmultiprocess library if an external one is enabled.
if (NOT WITH_LIBMULTIPROCESS)
add_subdirectory(libmultiprocess EXCLUDE_FROM_ALL)
# Share MP_INCLUDE_DIR variable with parent scope so target_capnp_sources
# build rules can find the libmultiprocess include directory and avoid
# "error: Import failed: /mp/proxy.capnp" errors from capnp.
set(MP_INCLUDE_DIR "${MP_INCLUDE_DIR}" PARENT_SCOPE)
# Add mptest unit tests to "all" target so ctest can run them
set_target_properties(mptest PROPERTIES EXCLUDE_FROM_ALL OFF)
set_target_properties(mpcalculator mpprinter mpexample PROPERTIES EXPORT_COMPILE_COMMANDS OFF)
endif()
add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL
capnp/mining.cpp
capnp/protocol.cpp

View file

@ -0,0 +1,39 @@
Checks: '
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-move-forwarding-reference,
-bugprone-narrowing-conversions,
-bugprone-reserved-identifier,
misc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unconventional-assign-operator,
-misc-unused-parameters,
modernize-*,
-modernize-avoid-c-arrays,
-modernize-concat-nested-namespaces,
-modernize-deprecated-headers,
-modernize-use-nodiscard,
-modernize-use-trailing-return-type,
-modernize-use-using,
performance-*,
-performance-noexcept-move-constructor,
readability-*,
-readability-braces-around-statements,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-implicit-bool-conversion,
-readability-inconsistent-declaration-parameter-name,
-readability-magic-numbers,
-readability-named-parameter,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
'
CheckOptions:
- key: modernize-use-override.IgnoreDestructors
value: true
HeaderFilterRegex: 'example/calculator.h|example/init.h|example/printer.h|include/mp/proxy-io.h|include/mp/proxy-types.h|include/mp/proxy.h|include/mp/util.h|test/mp/test/foo-types.h|test/mp/test/foo.h'

View file

@ -0,0 +1,174 @@
# 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.
cmake_minimum_required(VERSION 3.8)
project("Libmultiprocess" CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
include("cmake/compat_find.cmake")
find_package(CapnProto REQUIRED)
find_package(Threads REQUIRED)
option(Libmultiprocess_ENABLE_CLANG_TIDY "Run clang-tidy with the compiler." OFF)
if(Libmultiprocess_ENABLE_CLANG_TIDY)
find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy)
if(NOT CLANG_TIDY_EXECUTABLE)
message(FATAL_ERROR "Libmultiprocess_ENABLE_CLANG_TIDY is ON but clang-tidy is not found.")
endif()
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE}")
endif()
include("cmake/compat_config.cmake")
include("cmake/pthread_checks.cmake")
include(GNUInstallDirs)
# Set convenience variables for subdirectories.
set(MP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(MP_STANDALONE TRUE)
else()
# Set MP_INCLUDE_DIR for parent directories too, so target_capnp_sources calls
# in parent directories can use it and not need to specify include directories
# manually or see capnproto error "error: Import failed: /mp/proxy.capnp"
set(MP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
set(MP_STANDALONE FALSE)
endif()
# Prevent include directories from parent project from leaking into this one.
set_property(DIRECTORY PROPERTY INCLUDE_DIRECTORIES "")
# Generated C++ preprocessor defines
configure_file(include/mp/config.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/mp/config.h")
# Generated C++ Capn'Proto schema files
capnp_generate_cpp(MP_PROXY_SRCS MP_PROXY_HDRS include/mp/proxy.capnp)
# util library
add_library(mputil OBJECT src/mp/util.cpp)
target_include_directories(mputil PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
${CAPNP_INCLUDE_DIRECTORY})
# libmultiprocess.a runtime library
set(MP_PUBLIC_HEADERS
${MP_PROXY_HDRS}
include/mp/proxy-io.h
include/mp/proxy-types.h
include/mp/proxy.h
include/mp/type-char.h
include/mp/type-chrono.h
include/mp/type-context.h
include/mp/type-data.h
include/mp/type-decay.h
include/mp/type-exception.h
include/mp/type-function.h
include/mp/type-interface.h
include/mp/type-map.h
include/mp/type-message.h
include/mp/type-number.h
include/mp/type-optional.h
include/mp/type-pair.h
include/mp/type-pointer.h
include/mp/type-set.h
include/mp/type-string.h
include/mp/type-struct.h
include/mp/type-threadmap.h
include/mp/type-tuple.h
include/mp/type-vector.h
include/mp/type-void.h
include/mp/util.h)
add_library(multiprocess STATIC
${MP_PROXY_SRCS}
${MP_PUBLIC_HEADERS}
src/mp/proxy.cpp
$<TARGET_OBJECTS:mputil>)
add_library(Libmultiprocess::multiprocess ALIAS multiprocess)
target_include_directories(multiprocess PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
${CAPNP_INCLUDE_DIRECTORY})
target_link_libraries(multiprocess PUBLIC CapnProto::capnp)
target_link_libraries(multiprocess PUBLIC CapnProto::capnp-rpc)
target_link_libraries(multiprocess PUBLIC CapnProto::kj)
target_link_libraries(multiprocess PUBLIC CapnProto::kj-async)
set_target_properties(multiprocess PROPERTIES
PUBLIC_HEADER "${MP_PUBLIC_HEADERS}")
install(TARGETS multiprocess EXPORT LibTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT lib
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mp COMPONENT lib)
# mpgen code generator
add_executable(mpgen src/mp/gen.cpp $<TARGET_OBJECTS:mputil>)
add_executable(Libmultiprocess::mpgen ALIAS mpgen)
target_include_directories(mpgen PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>)
target_include_directories(mpgen PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(mpgen PRIVATE CapnProto::capnp)
target_link_libraries(mpgen PRIVATE CapnProto::capnp-rpc)
target_link_libraries(mpgen PRIVATE CapnProto::capnpc)
target_link_libraries(mpgen PRIVATE CapnProto::kj)
target_link_libraries(mpgen PRIVATE Threads::Threads)
set_target_properties(mpgen PROPERTIES
INSTALL_RPATH_USE_LINK_PATH TRUE)
set_target_properties(mpgen PROPERTIES
PUBLIC_HEADER include/mp/proxy.capnp)
install(TARGETS mpgen EXPORT BinTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT bin
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mp COMPONENT bin)
# makefile include to invoke mpgen code generator, for downstream Make projects
install(FILES "include/mpgen.mk"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT bin)
# pkg-config module to build against libmultiprocess library, for downstream autoconf projects
configure_file(pkgconfig/libmultiprocess.pc.in "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/libmultiprocess.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/libmultiprocess.pc"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT lib)
# cmake include to invoke mpgen code generator, for downstream CMake projects
install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/TargetCapnpSources.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Libmultiprocess COMPONENT bin)
# CMake target import files, for downstream CMake projects
install(EXPORT BinTargets
NAMESPACE Libmultiprocess::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Libmultiprocess COMPONENT bin)
install(EXPORT LibTargets
NAMESPACE Libmultiprocess::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Libmultiprocess COMPONENT lib)
# CMake find_package config file, for downstream CMake projects
include(CMakePackageConfigHelpers)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in
LibmultiprocessConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Libmultiprocess
NO_SET_AND_CHECK_MACRO)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/LibmultiprocessConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Libmultiprocess
COMPONENT common)
# Makefile targets to support "make install-bin" "make install-lib"
add_custom_target(install-bin
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=bin -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=common -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
VERBATIM)
add_dependencies(install-bin mpgen)
add_custom_target(install-lib
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=lib -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
COMMAND ${CMAKE_COMMAND} -DCOMPONENT=common -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
VERBATIM)
add_dependencies(install-lib multiprocess)
# Example and test subdirectories
add_subdirectory(example EXCLUDE_FROM_ALL)
add_subdirectory(test EXCLUDE_FROM_ALL)

View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2009-2019 The Bitcoin Core developers
Copyright (c) 2009-2019 Bitcoin Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,8 @@
# libmultiprocess
`libmultiprocess` is a C++ library and code generator making it easy to call functions and reference objects in different processes.
For more information see the [usage instructions](doc/usage.md), [installation instructions](doc/install.md), or [design documentation](doc/design.md).
If you have any questions, comments, or feedback, please submit an [issue](https://github.com/chaincodelabs/libmultiprocess/issues/new).
Duplicate issues are perfectly fine and all discussion about the project is welcome, since there isn't another discussion forum currently.

View file

@ -0,0 +1,33 @@
@PACKAGE_INIT@
# CMake find_package compatible package file, for downstream CMake projects
#
# Based on https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html#adding-components
set(_Libmultiprocess_supported_components Bin Lib)
# If no components specified, include all components.
list(LENGTH ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS_len)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS_len EQUAL 0)
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS ${_Libmultiprocess_supported_components})
endif()
if ("Bin" IN_LIST ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
include("${CMAKE_CURRENT_LIST_DIR}/TargetCapnpSources.cmake")
endif()
if ("Lib" IN_LIST ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
# Setting FOUND_LIBATOMIC is needed on debian & ubuntu systems to work around bug in
# their capnproto packages. See compat_find.cmake for a more complete explanation.
set(FOUND_LIBATOMIC TRUE)
include(CMakeFindDependencyMacro)
find_dependency(CapnProto)
endif()
foreach(_comp ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS})
if (NOT _comp IN_LIST _Libmultiprocess_supported_components)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False)
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/${_comp}Targets.cmake")
endforeach()

View file

@ -0,0 +1,104 @@
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.
#[=[
target_capnp_sources
--------------------
This function adds build steps to generate C++ files from Cap'n Proto files
and build them as part of a specified target.
Arguments:
target: The name of the CMake target (e.g., a library or executable) to
which the generated source files will be added. This target must already
be defined elsewhere in the CMake scripts.
include_prefix: Absolute path indicating what portion of capnp source paths
should be used in relative #include statements in the generated C++
files. For example, if the .capnp path is /home/src/lib/schema.capnp
and include_prefix is /home/src, generated includes look like:
#include <lib/schema.capnp.h>
And if include_prefix is /home/src/lib, generated includes look like:
#include <schema.capnp.h>
The specified include_prefix should be ${CMAKE_SOURCE_DIR} or a
subdirectory of it to include files relative to the project root. It can
be ${CMAKE_CURRENT_SOURCE_DIR} to include files relative to the current
source directory.
Additional Unnamed Arguments:
After `target` and `include_prefix`, all unnamed arguments are treated as
paths to `.capnp` schema files. These should be paths relative to
${CMAKE_CURRENT_SOURCE_DIR}.
Optional Keyword Arguments:
IMPORT_PATHS: Specifies additional directories to search for imported
`.capnp` files.
Example:
# Assuming `my_library` is a target and `lib/` contains `.capnp` schema
# files with imports from `include/`.
target_capnp_sources(my_library "${CMAKE_SOURCE_DIR}"
lib/schema1.capnp lib/schema2.capnp
IMPORT_PATHS ${CMAKE_SOURCE_DIR}/include)
#]=]
function(target_capnp_sources target include_prefix)
cmake_parse_arguments(PARSE_ARGV 2
"TCS" # prefix
"" # options
"" # one_value_keywords
"IMPORT_PATHS" # multi_value_keywords
)
if(NOT TARGET Libmultiprocess::mpgen)
message(FATAL_ERROR "Target 'Libmultiprocess::mpgen' does not exist.")
endif()
set(generated_headers "")
foreach(capnp_file IN LISTS TCS_UNPARSED_ARGUMENTS)
add_custom_command(
OUTPUT ${capnp_file}.c++ ${capnp_file}.h ${capnp_file}.proxy-client.c++ ${capnp_file}.proxy-types.h ${capnp_file}.proxy-server.c++ ${capnp_file}.proxy-types.c++ ${capnp_file}.proxy.h
COMMAND Libmultiprocess::mpgen ${CMAKE_CURRENT_SOURCE_DIR} ${include_prefix} ${CMAKE_CURRENT_SOURCE_DIR}/${capnp_file} ${TCS_IMPORT_PATHS} ${MP_INCLUDE_DIR}
DEPENDS ${capnp_file}
VERBATIM
)
target_sources(${target} PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/${capnp_file}.c++
${CMAKE_CURRENT_BINARY_DIR}/${capnp_file}.proxy-client.c++
${CMAKE_CURRENT_BINARY_DIR}/${capnp_file}.proxy-server.c++
${CMAKE_CURRENT_BINARY_DIR}/${capnp_file}.proxy-types.c++
)
list(APPEND generated_headers ${capnp_file}.h)
endforeach()
# Translate include_prefix from a source path to a binary path and add it as a
# target include directory.
set(build_include_prefix ${CMAKE_BINARY_DIR})
file(RELATIVE_PATH relative_path ${CMAKE_SOURCE_DIR} ${include_prefix})
if(relative_path)
string(APPEND build_include_prefix "/" "${relative_path}")
endif()
target_include_directories(${target} PUBLIC $<BUILD_INTERFACE:${build_include_prefix}> ${MP_INCLUDE_DIR})
if(TARGET Libmultiprocess::multiprocess)
target_link_libraries(${target} PRIVATE Libmultiprocess::multiprocess)
endif()
# Add a custom target that can be specified as a dependency of c++ targets
# that include generated headers. It can be necessary to specify these
# dependencies explicitly because while cmake detect dependencies of non
# generated files on generated headers, it does not reliably detect
# dependencies of generated headers on other generated headers.
add_custom_target("${target}_headers" DEPENDS ${generated_headers})
endfunction()

View file

@ -0,0 +1,59 @@
# 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.
# compat_config.cmake -- compatibility workarounds meant to be included after
# cmake find_package() calls are made, before configuring the ebuild
# Define capnp_PREFIX if not defined to avoid issue on macos
# https://github.com/chaincodelabs/libmultiprocess/issues/26
if (NOT DEFINED capnp_PREFIX AND DEFINED CAPNP_INCLUDE_DIRS)
get_filename_component(capnp_PREFIX "${CAPNP_INCLUDE_DIRS}" DIRECTORY)
endif()
if (NOT DEFINED CAPNPC_OUTPUT_DIR)
set(CAPNPC_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
# CMake target definitions for backwards compatibility with Ubuntu bionic
# capnproto 0.6.1 package (https://packages.ubuntu.com/bionic/libcapnp-dev)
# https://github.com/chaincodelabs/libmultiprocess/issues/27
if (NOT DEFINED CAPNP_LIB_CAPNPC AND DEFINED CAPNP_LIB_CAPNP-RPC)
string(REPLACE "-rpc" "c" CAPNP_LIB_CAPNPC "${CAPNP_LIB_CAPNP-RPC}")
endif()
if (NOT DEFINED CapnProto_capnpc_IMPORTED_LOCATION AND DEFINED CapnProto_capnp-rpc_IMPORTED_LOCATION)
string(REPLACE "-rpc" "c" CapnProto_capnpc_IMPORTED_LOCATION "${CapnProto_capnp-rpc_IMPORTED_LOCATION}")
endif()
if (NOT TARGET CapnProto::capnp AND DEFINED CAPNP_LIB_CAPNP)
add_library(CapnProto::capnp SHARED IMPORTED)
set_target_properties(CapnProto::capnp PROPERTIES IMPORTED_LOCATION "${CAPNP_LIB_CAPNP}")
endif()
if (NOT TARGET CapnProto::capnpc AND DEFINED CAPNP_LIB_CAPNPC)
add_library(CapnProto::capnpc SHARED IMPORTED)
set_target_properties(CapnProto::capnpc PROPERTIES IMPORTED_LOCATION "${CAPNP_LIB_CAPNPC}")
endif()
if (NOT TARGET CapnProto::capnpc AND DEFINED CapnProto_capnpc_IMPORTED_LOCATION)
add_library(CapnProto::capnpc SHARED IMPORTED)
set_target_properties(CapnProto::capnpc PROPERTIES IMPORTED_LOCATION "${CapnProto_capnpc_IMPORTED_LOCATION}")
endif()
if (NOT TARGET CapnProto::capnp-rpc AND DEFINED CAPNP_LIB_CAPNP-RPC)
add_library(CapnProto::capnp-rpc SHARED IMPORTED)
set_target_properties(CapnProto::capnp-rpc PROPERTIES IMPORTED_LOCATION "${CAPNP_LIB_CAPNP-RPC}")
endif()
if (NOT TARGET CapnProto::kj AND DEFINED CAPNP_LIB_KJ)
add_library(CapnProto::kj SHARED IMPORTED)
set_target_properties(CapnProto::kj PROPERTIES IMPORTED_LOCATION "${CAPNP_LIB_KJ}")
endif()
if (NOT TARGET CapnProto::kj-async AND DEFINED CAPNP_LIB_KJ-ASYNC)
add_library(CapnProto::kj-async SHARED IMPORTED)
set_target_properties(CapnProto::kj-async PROPERTIES IMPORTED_LOCATION "${CAPNP_LIB_KJ-ASYNC}")
endif()

View file

@ -0,0 +1,20 @@
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# compat_find.cmake -- compatibility workarounds meant to be included before
# cmake find_package() calls are made
# Set FOUND_LIBATOMIC to work around bug in debian capnproto package that is
# debian-specific and does not happpen upstream. Debian includes a patch
# https://sources.debian.org/patches/capnproto/1.0.1-4/07_libatomic.patch/ which
# uses check_library_exists(atomic __atomic_load_8 ...) and it fails because the
# symbol name conflicts with a compiler instrinsic as described
# https://github.com/chaincodelabs/libmultiprocess/issues/68#issuecomment-1135150171.
# This could be fixed by improving the check_library_exists function as
# described in the github comment, or by changing the debian patch to check for
# the symbol a different way, but simplest thing to do is work around the
# problem by setting FOUND_LIBATOMIC. This problem has probably not
# been noticed upstream because it only affects CMake packages depending on
# capnproto, not autoconf packages.
set(FOUND_LIBATOMIC TRUE)

View file

@ -0,0 +1,41 @@
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Define HAVE_PTHREAD_* variables depending on what pthread functions are
# available.
include(CMakePushCheckState)
include(CheckCXXSourceCompiles)
cmake_push_check_state()
set(CMAKE_REQUIRED_LIBRARIES Threads::Threads)
check_cxx_source_compiles("
#include <pthread.h>
int main(int argc, char** argv)
{
char thread_name[16];
return pthread_getname_np(pthread_self(), thread_name, sizeof(thread_name));
}"
HAVE_PTHREAD_GETNAME_NP)
check_cxx_source_compiles("
#include <cstdint>
#include <pthread.h>
int main(int argc, char** argv)
{
uint64_t tid;
pthread_threadid_np(NULL, &tid);
return 0;
}"
HAVE_PTHREAD_THREADID_NP)
check_cxx_source_compiles("
#include <pthread.h>
#include <pthread_np.h>
int main(int argc, char** argv)
{
return pthread_getthreadid_np();
}"
HAVE_PTHREAD_GETTHREADID_NP)
cmake_pop_check_state()

View file

@ -0,0 +1,44 @@
# libmultiprocess Design
Given an interface description of an object with one or more methods, libmultiprocess generates:
* A C++ `ProxyClient` class with an implementation of each interface method that sends a request over a socket, waits for a response, and returns the result.
* A C++ `ProxyServer` class that listens for requests over a socket and calls a wrapped C++ object implementing the same interface to actually execute the requests.
The function call ⇆ request translation supports input and output arguments, standard types like `unique_ptr`, `vector`, `map`, and `optional`, and bidirectional calls between processes through interface pointer and `std::function` arguments.
If the wrapped C++ object inherits from an abstract base class declaring virtual methods, the generated `ProxyClient` objects can inherit from the same class, allowing interprocess calls to replace local calls without changes to existing code.
There is also optional support for thread mapping, so each thread making interprocess calls can have a dedicated thread processing requests from it, and callbacks from processing threads are executed on corresponding request threads (so recursive mutexes and thread names function as expected in callbacks).
Libmultiprocess acts as a pure wrapper or layer over the underlying protocol. Clients and servers written in other languages, but using a shared capnproto schema can communicate with interprocess counterparties using libmultiprocess without having to use libmultiprocess themselves or having to know about the implementation details of libmultiprocess.
### Internals
The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap c++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods establish connections through sockets, internally creating `Connection` objects wrapping a `capnp::RpcSystem` configured for client and server mode respectively.
The `InitInterface` interface will typically have methods which return other interfaces, giving the connecting process the ability to call other functions in the serving process. Interfaces can also have methods accepting other interfaces as parameters, giving serving processes the ability to call back and invoke functions in connecting processes. Creating new interfaces does not create new connections, and typically many interface objects will share the same connection.
Both `ConnectStream` and `ServeStream` also require an instantiation of the `EventLoop`. The `EventLoop` owns pending requests, notifies on request dispatch, allows clients from multiple threads to make synchronous calls, and handles some cleanup routines on exit. It must be run in a separate thread so it is always active and can process incoming requests from local clients and remote connections.
When a generated method on the `ProxyClient` is called, it calls `clientInvoke` with the capnp-translated types. `clientInvoke` creates a self-executing promise (`kj::TaskSet`) that drives the execution of the request and gives ownership of it to the `EventLoop`. `clientInvoke` blocks until a response is received, or until there is a call from the server that needs to run on the same client thread, using a `Waiter` object.
On the server side, the `capnp::RpcSystem` receives the capnp request and invokes the corresponding c++ method through the corresponding `ProxyServer` and the heavily templated `serverInvoke` triggering a `ServerCall`. Its return values from the actual c++ methods are copied into capnp responses by `ServerRet` and exceptions are caught and copied by `ServerExcept`. The two are connected through `ServerField`. The main method driving execution of a request is `PassField`, which is invoked through `ServerField`. Instantiated interfaces, or capabilities in capnp speak, are tracked and owned by the server's `capnp::RpcSystem`.
## Interface descriptions
As explained in the [usage](usage.md) document, interface descriptions need to be consumed both by the _libmultiprocess_ code generator, and by C++ code that calls and implements the interfaces. The C++ code only needs to know about C++ arguments and return types, while the code generator only needs to know about capnp arguments and return types, but both need to know class and method names, so the corresponding `.h` and `.capnp` source files contain some of the same information, and have to be kept in sync manually when methods or parameters change. Despite the redundancy, reconciling the interface definitions is designed to be _straightforward_ and _safe_. _Straightforward_ because there is no need to write manual serialization code or use awkward intermediate types like [`UniValue`](https://github.com/bitcoin/bitcoin/blob/master/src/univalue/include/univalue.h) instead of native types. _Safe_ because if there are any inconsistencies between API and data definitions (even minor ones like using a narrow int data type for a wider int API input), there are errors at build time instead of errors or bugs at runtime.
In the future, it would be possible to combine API and data definitions together using [C++ attributes](https://en.cppreference.com/w/cpp/language/attributes). To do this we would add attributes to the API definition files, and then generate the data definitions from the API definitions and attributes. I didn't take this approach mostly because it would be extra work, but also because until c++ standardizes reflection, this would require either hooking into compiler APIs like https://github.com/RosettaCommons/binder, or parsing c++ code manually like http://www.swig.org/.
## What is `kj`?
KJ is a concurrency framework [bundled with
capnproto](https://capnproto.org/cxxrpc.html#kj-concurrency-framework); it is used as a
basis in this library to construct the event-loop necessary to service IPC requests.
## Future directions
_libmultiprocess_ uses the [Cap'n Proto](https://capnproto.org) interface description language and protocol, but it could be extended or changed to use a different IDL/protocol like [gRPC](https://grpc.io). The nice thing about _Cap'n Proto_ compared to _gRPC_ and most other lower level protocols is that it allows interface pointers (_Services_ in gRPC parlance) to be passed as method arguments and return values, so object references and bidirectional requests work out of the box. Supporting a lower-level protocol would require writing adding maps and tracking code to proxy objects.
_libmultiprocess_ is currently compatible with sandboxing but could add platform-specific sandboxing support or integration with a sandboxing library like [SAPI](https://github.com/google/sandboxed-api).

View file

@ -0,0 +1,52 @@
# libmultiprocess Installation
Installation currently requires Cap'n Proto:
```sh
apt install libcapnp-dev capnproto
brew install capnp cmake
dnf install capnproto
```
Installation steps are:
```sh
mkdir build
cd build
cmake ..
make
make check # Optionally build and run tests
make install
```
To build with libmultiprocess in a CMake project can specify:
```cmake
find_package(Libmultiprocess)
target_capnp_sources(mytarget ${CMAKE_CURRENT_SOURCE_DIR} myschema.capnp)
```
Which will locate the libmultiprocess cmake package, and call the
`target_capnp_sources` function to generate C++ files and link them into a
library or executable target. See `example/CMakeLists.txt` for a complete
example.
To build with libmultiprocess in a non-CMake project can use installed
`<prefix>/include/mpgen.mk` Makefile rule to generate C++ files, and
`<prefix>/lib/pkgconfig/libmultiprocess.pc` pkg-config definition to link
against the runtime library.
For cross-compilation, it may be useful to build the runtime library and code
generation binaries separately, which can be done with:
```sh
make install-bin # install bin/mpgen and related files
make install-lib # install lib/libmultiprocess.a and related files
```
It is also possible to import CMake targets separately with:
```cmake
find_package(Libmultiprocess COMPONENTS Bin)
find_package(Libmultiprocess COMPONENTS Lib)
```

View file

@ -0,0 +1,24 @@
# libmultiprocess Usage
## Overview
_libmultiprocess_ is a library and code generator that allows calling C++ class interfaces across different processes. For an interface to be available from other processes, it needs two definitions:
- An **API definition** declaring how the interface is called. Included examples: [calculator.h](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/calculator.h), [printer.h](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/printer.h), [init.h](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/init.h). Bitcoin examples: [node.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/node.h), [wallet.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/wallet.h), [echo.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/echo.h), [init.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/init.h).
- A **data definition** declaring how interface calls get sent across the wire. Included examples: [calculator.capnp](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/calculator.capnp), [printer.capnp](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/printer.capnp), [init.capnp](https://github.com/chaincodelabs/libmultiprocess/blob/master/example/init.capnp). Bitcoin examples: [node.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/node.capnp), [wallet.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/wallet.capnp), [echo.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/echo.capnp), [init.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/init.capnp).
The `*.capnp` data definition files are consumed by the _libmultiprocess_ code generator and each `X.capnp` file generates `X.capnp.c++`, `X.capnp.h`, `X.capnp.proxy-client.c++`, `X.capnp.proxy-server.c++`, `X.capnp.proxy-types.c++`, `X.capnp.proxy-types.h`, and `X.capnp.proxy.h` output files. The generated files include `mp::ProxyClient<Interface>` and `mp::ProxyServer<Interface>` class specializations for all the interfaces in the `.capnp` files. These allow methods on C++ objects in one process to be called from other processes over IPC sockets.
The `ProxyServer` objects help translate IPC requests from a socket to method calls on a local object. The `ProxyServer` objects are just used internally by the `mp::ServeStream(loop, socket, wrapped_object)` and `mp::ListenConnections(loop, socket, wrapped_object)` functions, and aren't exposed externally. The `ProxyClient` classes are exposed, and returned from the `mp::ConnectStream(loop, socket)` function and meant to be used directly. The classes implement methods described in `.capnp` definitions, and whenever any method is called, a request with the method arguments is sent over the associated IPC connection, and the corresponding `wrapped_object` method on the other end of the connection is called, with the `ProxyClient` method blocking until it returns and forwarding back any return value to the `ProxyClient` method caller.
## Example
A simple interface description can be found at [test/mp/test/foo.capnp](../test/mp/test/foo.capnp), implementation in [test/mp/test/foo.h](../test/mp/test/foo.h), and usage in [test/mp/test/test.cpp](../test/mp/test/test.cpp).
A more complete example can be found in [example](../example/) and run with:
```sh
make -C build example
build/example/mpexample
```

View file

@ -0,0 +1,29 @@
# 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(${PROJECT_SOURCE_DIR}/cmake/TargetCapnpSources.cmake)
add_executable(mpcalculator
calculator.cpp
)
target_capnp_sources(mpcalculator ${CMAKE_CURRENT_SOURCE_DIR} init.capnp calculator.capnp printer.capnp)
target_include_directories(mpcalculator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mpcalculator PRIVATE Threads::Threads)
add_executable(mpprinter
printer.cpp
)
target_capnp_sources(mpprinter ${CMAKE_CURRENT_SOURCE_DIR} init.capnp calculator.capnp printer.capnp)
target_include_directories(mpprinter PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mpprinter PRIVATE Threads::Threads)
add_executable(mpexample
example.cpp
)
target_capnp_sources(mpexample ${CMAKE_CURRENT_SOURCE_DIR} init.capnp calculator.capnp printer.capnp)
target_include_directories(mpexample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mpexample PRIVATE Threads::Threads)
target_link_libraries(mpexample PRIVATE stdc++fs)
add_custom_target(mpexamples DEPENDS mpexample mpcalculator mpprinter)

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.
@0xb67dbf34061180a9;
using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("calculator.h");
$Proxy.includeTypes("types.h");
interface CalculatorInterface $Proxy.wrap("Calculator") {
destroy @0 (context :Proxy.Context) -> ();
solveEquation @1 (context :Proxy.Context, eqn: Text) -> ();
}

View file

@ -0,0 +1,51 @@
// 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 <calculator.h>
#include <fstream>
#include <init.capnp.h>
#include <init.capnp.proxy-types.h>
#include <init.h>
#include <iostream>
#include <memory>
#include <mp/proxy-io.h>
#include <printer.h>
#include <stdexcept>
class CalculatorImpl : public Calculator
{
public:
CalculatorImpl(std::unique_ptr<Printer> printer) : m_printer(std::move(printer)) {}
void solveEquation(const std::string& eqn) override { m_printer->print("Wow " + eqn + ", that's a tough one.\n"); }
std::unique_ptr<Printer> m_printer;
};
class InitImpl : public Init
{
public:
std::unique_ptr<Calculator> makeCalculator(std::unique_ptr<Printer> printer) override
{
return std::make_unique<CalculatorImpl>(std::move(printer));
}
};
void LogPrint(bool raise, const std::string& message)
{
if (raise) throw std::runtime_error(message);
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 2) {
std::cout << "Usage: mpcalculator <fd>\n";
return 1;
}
mp::EventLoop loop("mpcalculator", LogPrint);
int fd = std::stoi(argv[1]);
std::unique_ptr<Init> init = std::make_unique<InitImpl>();
mp::ServeStream<InitInterface>(loop, fd, *init);
loop.loop();
return 0;
}

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 EXAMPLE_CALCULATOR_H
#define EXAMPLE_CALCULATOR_H
#include <string>
class Calculator
{
public:
virtual ~Calculator() = default;
virtual void solveEquation(const std::string& eqn) = 0;
};
#endif // EXAMPLE_CALCULATOR_H

View file

@ -0,0 +1,65 @@
// 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 <filesystem>
#include <fstream>
#include <init.capnp.h>
#include <init.capnp.proxy-types.h>
#include <iostream>
#include <mp/proxy-io.h>
namespace fs = std::filesystem;
auto Spawn(mp::EventLoop& loop, const std::string& process_argv0, const std::string& new_exe_name)
{
int pid;
int fd = mp::SpawnProcess(pid, [&](int fd) -> std::vector<std::string> {
fs::path path = process_argv0;
path.remove_filename();
path.append(new_exe_name);
return {path.string(), std::to_string(fd)};
});
return std::make_tuple(mp::ConnectStream<InitInterface>(loop, fd), pid);
}
void LogPrint(bool raise, const std::string& message)
{
if (raise) throw std::runtime_error(message);
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 1) {
std::cout << "Usage: mpexample\n";
return 1;
}
std::promise<mp::EventLoop*> promise;
std::thread loop_thread([&] {
mp::EventLoop loop("mpexample", LogPrint);
promise.set_value(&loop);
loop.loop();
});
mp::EventLoop* loop = promise.get_future().get();
auto [printer_init, printer_pid] = Spawn(*loop, argv[0], "mpprinter");
auto [calc_init, calc_pid] = Spawn(*loop, argv[0], "mpcalculator");
auto calc = calc_init->makeCalculator(printer_init->makePrinter());
while (true) {
std::string eqn;
std::cout << "Enter the equation, or \"exit\" to quit: ";
std::getline(std::cin, eqn);
if (eqn == "exit") break;
calc->solveEquation(eqn);
}
calc.reset();
calc_init.reset();
mp::WaitProcess(calc_pid);
printer_init.reset();
mp::WaitProcess(printer_pid);
loop_thread.join();
std::cout << "Bye!" << std::endl;
return 0;
}

View file

@ -0,0 +1,22 @@
# 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.
@0xba5a7448664901b1;
using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";
using Calculator = import "calculator.capnp";
using Printer = import "printer.capnp";
$Proxy.include("calculator.h");
$Proxy.include("init.h");
$Proxy.include("printer.h");
$Proxy.includeTypes("calculator.capnp.proxy-types.h");
$Proxy.includeTypes("printer.capnp.proxy-types.h");
interface InitInterface $Proxy.wrap("Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeCalculator @1 (context :Proxy.Context, print :Printer.PrinterInterface) -> (result :Calculator.CalculatorInterface);
makePrinter @2 (context :Proxy.Context) -> (result :Printer.PrinterInterface);
}

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 EXAMPLE_INIT_H
#define EXAMPLE_INIT_H
#include <calculator.h>
#include <memory>
#include <printer.h>
class Init
{
public:
virtual ~Init() = default;
virtual std::unique_ptr<Printer> makePrinter() { return nullptr; }
virtual std::unique_ptr<Calculator> makeCalculator(std::unique_ptr<Printer> printer) { return nullptr; }
};
#endif // EXAMPLE_INIT_H

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.
@0x893db95f456ed0e3;
using Cxx = import "/capnp/c++.capnp";
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("printer.h");
$Proxy.includeTypes("types.h");
interface PrinterInterface $Proxy.wrap("Printer") {
destroy @0 (context :Proxy.Context) -> ();
print @1 (context :Proxy.Context, text: Text) -> ();
}

View file

@ -0,0 +1,45 @@
// 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 <fstream>
#include <init.capnp.h>
#include <init.capnp.proxy-types.h>
#include <init.h>
#include <iostream>
#include <memory>
#include <mp/proxy-io.h>
#include <printer.h>
#include <stdexcept>
class PrinterImpl : public Printer
{
public:
void print(const std::string& message) override { std::cout << "mpprinter: " << message << std::endl; }
};
class InitImpl : public Init
{
public:
std::unique_ptr<Printer> makePrinter() override { return std::make_unique<PrinterImpl>(); }
};
void LogPrint(bool raise, const std::string& message)
{
if (raise) throw std::runtime_error(message);
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 2) {
std::cout << "Usage: mpprinter <fd>\n";
return 1;
}
mp::EventLoop loop("mpprinter", LogPrint);
int fd = std::stoi(argv[1]);
std::unique_ptr<Init> init = std::make_unique<InitImpl>();
mp::ServeStream<InitInterface>(loop, fd, *init);
loop.loop();
return 0;
}

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 EXAMPLE_PRINTER_H
#define EXAMPLE_PRINTER_H
#include <string>
class Printer
{
public:
virtual ~Printer() = default;
virtual void print(const std::string& message) = 0;
};
#endif // EXAMPLE_PRINTER_H

View file

@ -0,0 +1,14 @@
// Copyright (c) 2025 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 EXAMPLE_TYPES_H
#define EXAMPLE_TYPES_H
#include <mp/type-context.h>
#include <mp/type-decay.h>
#include <mp/type-interface.h>
#include <mp/type-string.h>
#include <mp/type-threadmap.h>
#endif // EXAMPLE_TYPES_H

View file

@ -0,0 +1,16 @@
// 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 MP_CONFIG_H
#define MP_CONFIG_H
#cmakedefine CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
#cmakedefine capnp_PREFIX "@capnp_PREFIX@"
#cmakedefine HAVE_KJ_FILESYSTEM
#cmakedefine HAVE_PTHREAD_GETNAME_NP @HAVE_PTHREAD_GETNAME_NP@
#cmakedefine HAVE_PTHREAD_THREADID_NP @HAVE_PTHREAD_THREADID_NP@
#cmakedefine HAVE_PTHREAD_GETTHREADID_NP @HAVE_PTHREAD_GETTHREADID_NP@
#endif // MP_CONFIG_H

View file

@ -0,0 +1,641 @@
// 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 MP_PROXY_IO_H
#define MP_PROXY_IO_H
#include <mp/proxy.h>
#include <mp/util.h>
#include <mp/proxy.capnp.h>
#include <capnp/rpc-twoparty.h>
#include <assert.h>
#include <functional>
#include <optional>
#include <map>
#include <memory>
#include <sstream>
#include <string>
namespace mp {
struct ThreadContext;
struct InvokeContext
{
Connection& connection;
};
struct ClientInvokeContext : InvokeContext
{
ThreadContext& thread_context;
ClientInvokeContext(Connection& conn, ThreadContext& thread_context)
: InvokeContext{conn}, thread_context{thread_context}
{
}
};
template <typename ProxyServer, typename CallContext_>
struct ServerInvokeContext : InvokeContext
{
using CallContext = CallContext_;
ProxyServer& proxy_server;
CallContext& call_context;
int req;
ServerInvokeContext(ProxyServer& proxy_server, CallContext& call_context, int req)
: InvokeContext{*proxy_server.m_context.connection}, proxy_server{proxy_server}, call_context{call_context}, req{req}
{
}
};
template <typename Interface, typename Params, typename Results>
using ServerContext = ServerInvokeContext<ProxyServer<Interface>, ::capnp::CallContext<Params, Results>>;
template <>
struct ProxyClient<Thread> : public ProxyClientBase<Thread, ::capnp::Void>
{
using ProxyClientBase::ProxyClientBase;
// https://stackoverflow.com/questions/22357887/comparing-two-mapiterators-why-does-it-need-the-copy-constructor-of-stdpair
ProxyClient(const ProxyClient&) = delete;
~ProxyClient();
void setCleanup(std::function<void()> fn);
//! Cleanup function to run when the connection is closed. If the Connection
//! gets destroyed before this ProxyClient<Thread> object, this cleanup
//! callback lets it destroy this object and remove its entry in the
//! thread's request_threads or callback_threads map (after resetting
//! m_cleanup_it so the destructor does not try to access it). But if this
//! object gets destroyed before the Connection, there's no need to run the
//! cleanup function and the destructor will unregister it.
std::optional<CleanupIt> m_cleanup_it;
};
template <>
struct ProxyServer<Thread> final : public Thread::Server
{
public:
ProxyServer(ThreadContext& thread_context, std::thread&& thread);
~ProxyServer();
kj::Promise<void> getName(GetNameContext context) override;
ThreadContext& m_thread_context;
std::thread m_thread;
};
//! Handler for kj::TaskSet failed task events.
class LoggingErrorHandler : public kj::TaskSet::ErrorHandler
{
public:
LoggingErrorHandler(EventLoop& loop) : m_loop(loop) {}
void taskFailed(kj::Exception&& exception) override;
EventLoop& m_loop;
};
using LogFn = std::function<void(bool raise, std::string message)>;
class Logger
{
public:
Logger(bool raise, LogFn& fn) : m_raise(raise), m_fn(fn) {}
Logger(Logger&& logger) : m_raise(logger.m_raise), m_fn(logger.m_fn), m_buffer(std::move(logger.m_buffer)) {}
~Logger() noexcept(false)
{
if (m_fn) m_fn(m_raise, m_buffer.str());
}
template <typename T>
friend Logger& operator<<(Logger& logger, T&& value)
{
if (logger.m_fn) logger.m_buffer << std::forward<T>(value);
return logger;
}
template <typename T>
friend Logger& operator<<(Logger&& logger, T&& value)
{
return logger << std::forward<T>(value);
}
bool m_raise;
LogFn& m_fn;
std::ostringstream m_buffer;
};
std::string LongThreadName(const char* exe_name);
//! Event loop implementation.
//!
//! Based on https://groups.google.com/d/msg/capnproto/TuQFF1eH2-M/g81sHaTAAQAJ
class EventLoop
{
public:
//! Construct event loop object.
EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr);
~EventLoop();
//! Run event loop. Does not return until shutdown. This should only be
//! called once from the m_thread_id thread. This will block until
//! the m_num_clients reference count is 0.
void loop();
//! Run function on event loop thread. Does not return until function completes.
//! Must be called while the loop() function is active.
void post(const std::function<void()>& fn);
//! Wrapper around EventLoop::post that takes advantage of the
//! fact that callable will not go out of scope to avoid requirement that it
//! be copyable.
template <typename Callable>
void sync(Callable&& callable)
{
return post(std::ref(callable));
}
//! Start asynchronous worker thread if necessary. This is only done if
//! there are ProxyServerBase::m_impl objects that need to be destroyed
//! asynchronously, without tying up the event loop thread. This can happen
//! when an interface does not declare a destroy() method that would allow
//! the client to wait for the destructor to finish and run it on a
//! dedicated thread. It can also happen whenever this is a broken
//! connection and the client is no longer around to call the destructors
//! and the server objects need to be garbage collected. In both cases, it
//! is important that ProxyServer::m_impl destructors do not run on the
//! eventloop thread because they may need it to do I/O if they perform
//! other IPC calls.
void startAsyncThread(std::unique_lock<std::mutex>& lock);
//! Add/remove remote client reference counts.
void addClient(std::unique_lock<std::mutex>& lock);
void removeClient(std::unique_lock<std::mutex>& lock);
//! Check if loop should exit.
bool done(std::unique_lock<std::mutex>& lock);
Logger log()
{
Logger logger(false, m_log_fn);
logger << "{" << LongThreadName(m_exe_name) << "} ";
return logger;
}
Logger logPlain() { return {false, m_log_fn}; }
Logger raise() { return {true, m_log_fn}; }
//! Process name included in thread names so combined debug output from
//! multiple processes is easier to understand.
const char* m_exe_name;
//! ID of the event loop thread
std::thread::id m_thread_id = std::this_thread::get_id();
//! Handle of an async worker thread. Joined on destruction. Unset if async
//! method has not been called.
std::thread m_async_thread;
//! Callback function to run on event loop thread during post() or sync() call.
const std::function<void()>* m_post_fn = nullptr;
//! Callback functions to run on async thread.
CleanupList m_async_fns;
//! Pipe read handle used to wake up the event loop thread.
int m_wait_fd = -1;
//! Pipe write handle used to wake up the event loop thread.
int m_post_fd = -1;
//! Number of clients holding references to ProxyServerBase objects that
//! reference this event loop.
int m_num_clients = 0;
//! Mutex and condition variable used to post tasks to event loop and async
//! thread.
std::mutex m_mutex;
std::condition_variable m_cv;
//! Capnp IO context.
kj::AsyncIoContext m_io_context;
//! Capnp error handler. Needs to outlive m_task_set.
LoggingErrorHandler m_error_handler{*this};
//! Capnp list of pending promises.
std::unique_ptr<kj::TaskSet> m_task_set;
//! List of connections.
std::list<Connection> m_incoming_connections;
//! External logging callback.
LogFn m_log_fn;
//! External context pointer.
void* m_context;
};
//! Single element task queue used to handle recursive capnp calls. (If server
//! makes an callback into the client in the middle of a request, while client
//! thread is blocked waiting for server response, this is what allows the
//! client to run the request in the same thread, the same way code would run in
//! single process, with the callback sharing same thread stack as the original
//! call.
struct Waiter
{
Waiter() = default;
template <typename Fn>
void post(Fn&& fn)
{
std::unique_lock<std::mutex> lock(m_mutex);
assert(!m_fn);
m_fn = std::move(fn);
m_cv.notify_all();
}
template <class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred)
{
m_cv.wait(lock, [&] {
// Important for this to be "while (m_fn)", not "if (m_fn)" to avoid
// a lost-wakeup bug. A new m_fn and m_cv notification might be sent
// after the fn() call and before the lock.lock() call in this loop
// in the case where a capnp response is sent and a brand new
// request is immediately received.
while (m_fn) {
auto fn = std::move(m_fn);
m_fn = nullptr;
lock.unlock();
fn();
lock.lock();
}
bool done = pred();
return done;
});
}
std::mutex m_mutex;
std::condition_variable m_cv;
std::function<void()> m_fn;
};
//! Object holding network & rpc state associated with either an incoming server
//! connection, or an outgoing client connection. It must be created and destroyed
//! on the event loop thread.
//! In addition to Cap'n Proto state, it also holds lists of callbacks to run
//! when the connection is closed.
class Connection
{
public:
Connection(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream_)
: m_loop(loop), m_stream(kj::mv(stream_)),
m_network(*m_stream, ::capnp::rpc::twoparty::Side::CLIENT, ::capnp::ReaderOptions()),
m_rpc_system(::capnp::makeRpcClient(m_network))
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.addClient(lock);
}
Connection(EventLoop& loop,
kj::Own<kj::AsyncIoStream>&& stream_,
const std::function<::capnp::Capability::Client(Connection&)>& make_client)
: m_loop(loop), m_stream(kj::mv(stream_)),
m_network(*m_stream, ::capnp::rpc::twoparty::Side::SERVER, ::capnp::ReaderOptions()),
m_rpc_system(::capnp::makeRpcServer(m_network, make_client(*this)))
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.addClient(lock);
}
//! Run cleanup functions. Must be called from the event loop thread. First
//! calls synchronous cleanup functions while blocked (to free capnp
//! Capability::Client handles owned by ProxyClient objects), then schedules
//! asynchronous cleanup functions to run in a worker thread (to run
//! destructors of m_impl instances owned by ProxyServer objects).
~Connection();
//! Register synchronous cleanup function to run on event loop thread (with
//! access to capnp thread local variables) when disconnect() is called.
//! any new i/o.
CleanupIt addSyncCleanup(std::function<void()> fn);
void removeSyncCleanup(CleanupIt it);
//! Register asynchronous cleanup function to run on worker thread when
//! disconnect() is called.
void addAsyncCleanup(std::function<void()> fn);
//! Add disconnect handler.
template <typename F>
void onDisconnect(F&& f)
{
// Add disconnect handler to local TaskSet to ensure it is cancelled and
// will never run after connection object is destroyed. But when disconnect
// handler fires, do not call the function f right away, instead add it
// to the EventLoop TaskSet to avoid "Promise callback destroyed itself"
// error in cases where f deletes this Connection object.
m_on_disconnect.add(m_network.onDisconnect().then(
[f = std::move(f), this]() mutable { m_loop.m_task_set->add(kj::evalLater(kj::mv(f))); }));
}
EventLoop& m_loop;
kj::Own<kj::AsyncIoStream> m_stream;
LoggingErrorHandler m_error_handler{m_loop};
kj::TaskSet m_on_disconnect{m_error_handler};
::capnp::TwoPartyVatNetwork m_network;
std::optional<::capnp::RpcSystem<::capnp::rpc::twoparty::VatId>> m_rpc_system;
// ThreadMap interface client, used to create a remote server thread when an
// client IPC call is being made for the first time from a new thread.
ThreadMap::Client m_thread_map{nullptr};
//! Collection of server-side IPC worker threads (ProxyServer<Thread> objects previously returned by
//! ThreadMap.makeThread) used to service requests to clients.
::capnp::CapabilityServerSet<Thread> m_threads;
//! Cleanup functions to run if connection is broken unexpectedly.
//! Lists will be empty if all ProxyClient and ProxyServer objects are
//! destroyed cleanly before the connection is destroyed.
CleanupList m_sync_cleanup_fns;
CleanupList m_async_cleanup_fns;
};
//! Vat id for server side of connection. Required argument to RpcSystem::bootStrap()
//!
//! "Vat" is Cap'n Proto nomenclature for a host of various objects that facilitates
//! bidirectional communication with other vats; it is often but not always 1-1 with
//! processes. Cap'n Proto doesn't reference clients or servers per se; instead everything
//! is just a vat.
//!
//! See also: https://github.com/capnproto/capnproto/blob/9021f0c722b36cb11e3690b0860939255ebad39c/c%2B%2B/src/capnp/rpc.capnp#L42-L56
struct ServerVatId
{
::capnp::word scratch[4]{};
::capnp::MallocMessageBuilder message{scratch};
::capnp::rpc::twoparty::VatId::Builder vat_id{message.getRoot<::capnp::rpc::twoparty::VatId>()};
ServerVatId() { vat_id.setSide(::capnp::rpc::twoparty::Side::SERVER); }
};
template <typename Interface, typename Impl>
ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client client,
Connection* connection,
bool destroy_connection)
: m_client(std::move(client)), m_context(connection)
{
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.addClient(lock);
}
// Handler for the connection getting destroyed before this client object.
auto cleanup_it = m_context.connection->addSyncCleanup([this]() {
// Release client capability by move-assigning to temporary.
{
typename Interface::Client(std::move(m_client));
}
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
m_context.connection = nullptr;
});
// Two shutdown sequences are supported:
//
// - A normal sequence where client proxy objects are deleted by external
// code that no longer needs them
//
// - A garbage collection sequence where the connection or event loop shuts
// down while external code is still holding client references.
//
// The first case is handled here when m_context.connection is not null. The
// second case is handled by the cleanup function, which sets m_context.connection to
// null so nothing happens here.
m_context.cleanup_fns.emplace_front([this, destroy_connection, cleanup_it]{
if (m_context.connection) {
// Remove cleanup callback so it doesn't run and try to access
// this object after it's already destroyed.
m_context.connection->removeSyncCleanup(cleanup_it);
// If the capnp interface defines a destroy method, call it to destroy
// the remote object, waiting for it to be deleted server side. If the
// capnp interface does not define a destroy method, this will just call
// an empty stub defined in the ProxyClientBase class and do nothing.
Sub::destroy(*this);
// FIXME: Could just invoke removed addCleanup fn here instead of duplicating code
m_context.connection->m_loop.sync([&]() {
// Release client capability by move-assigning to temporary.
{
typename Interface::Client(std::move(m_client));
}
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
if (destroy_connection) {
delete m_context.connection;
m_context.connection = nullptr;
}
});
}
});
Sub::construct(*this);
}
template <typename Interface, typename Impl>
ProxyClientBase<Interface, Impl>::~ProxyClientBase() noexcept
{
CleanupRun(m_context.cleanup_fns);
}
template <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection)
: m_impl(std::move(impl)), m_context(&connection)
{
assert(m_impl);
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.addClient(lock);
}
//! ProxyServer destructor, called from the EventLoop thread by Cap'n Proto
//! garbage collection code after there are no more references to this object.
template <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::~ProxyServerBase()
{
if (m_impl) {
// If impl is non-null at this point, it means no client is waiting for
// the m_impl server object to be destroyed synchronously. This can
// happen either if the interface did not define a "destroy" method (see
// invokeDestroy method below), or if a destroy method was defined, but
// the connection was broken before it could be called.
//
// In either case, be conservative and run the cleanup on an
// asynchronous thread, to avoid destructors or cleanup functions
// blocking or deadlocking the current EventLoop thread, since they
// could be making IPC calls.
//
// Technically this is a little too conservative since if the interface
// defines a "destroy" method, but the destroy method does not accept a
// Context parameter specifying a worker thread, the cleanup method
// would run on the EventLoop thread normally (when connection is
// unbroken), but will not run on the EventLoop thread now (when
// connection is broken). Probably some refactoring of the destructor
// and invokeDestroy function is possible to make this cleaner and more
// consistent.
m_context.connection->addAsyncCleanup([impl=std::move(m_impl), fns=std::move(m_context.cleanup_fns)]() mutable {
impl.reset();
CleanupRun(fns);
});
}
assert(m_context.cleanup_fns.size() == 0);
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
//! If the capnp interface defined a special "destroy" method, as described the
//! ProxyClientBase class, this method will be called and synchronously destroy
//! m_impl before returning to the client.
//!
//! If the capnp interface does not define a "destroy" method, this will never
//! be called and the ~ProxyServerBase destructor will be responsible for
//! deleting m_impl asynchronously, whenever the ProxyServer object gets garbage
//! collected by Cap'n Proto.
//!
//! This method is called in the same way other proxy server methods are called,
//! via the serverInvoke function. Basically serverInvoke just calls this as a
//! substitute for a non-existent m_impl->destroy() method. If the destroy
//! method has any parameters or return values they will be handled in the
//! normal way by PassField/ReadField/BuildField functions. Particularly if a
//! Context.thread parameter was passed, this method will run on the worker
//! thread specified by the client. Otherwise it will run on the EventLoop
//! thread, like other server methods without an assigned thread.
template <typename Interface, typename Impl>
void ProxyServerBase<Interface, Impl>::invokeDestroy()
{
m_impl.reset();
CleanupRun(m_context.cleanup_fns);
}
using ConnThreads = std::map<Connection*, ProxyClient<Thread>>;
using ConnThread = ConnThreads::iterator;
// Retrieve ProxyClient<Thread> object associated with this connection from a
// map, or create a new one and insert it into the map. Return map iterator and
// inserted bool.
std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex, Connection* connection, std::function<Thread::Client()> make_thread);
struct ThreadContext
{
//! Identifying string for debug.
std::string thread_name;
//! Waiter object used to allow client threads blocked waiting for a server
//! response to execute callbacks made from the client's corresponding
//! server thread.
std::unique_ptr<Waiter> waiter = nullptr;
//! When client is making a request to a server, this is the
//! `callbackThread` argument it passes in the request, used by the server
//! in case it needs to make callbacks into the client that need to execute
//! while the client is waiting. This will be set to a local thread object.
ConnThreads callback_threads;
//! When client is making a request to a server, this is the `thread`
//! argument it passes in the request, used to control which thread on
//! server will be responsible for executing it. If client call is being
//! made from a local thread, this will be a remote thread object returned
//! by makeThread. If a client call is being made from a thread currently
//! handling a server request, this will be set to the `callbackThread`
//! request thread argument passed in that request.
ConnThreads request_threads;
//! Whether this thread is a capnp event loop thread. Not really used except
//! to assert false if there's an attempt to execute a blocking operation
//! which could deadlock the thread.
bool loop_thread = false;
};
//! Given stream file descriptor, make a new ProxyClient object to send requests
//! over the stream. Also create a new Connection object embedded in the
//! client that is freed when the client is closed.
template <typename InitInterface>
std::unique_ptr<ProxyClient<InitInterface>> ConnectStream(EventLoop& loop, int fd)
{
typename InitInterface::Client init_client(nullptr);
std::unique_ptr<Connection> connection;
loop.sync([&] {
auto stream =
loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP);
connection = std::make_unique<Connection>(loop, kj::mv(stream));
init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<InitInterface>();
Connection* connection_ptr = connection.get();
connection->onDisconnect([&loop, connection_ptr] {
loop.log() << "IPC client: unexpected network disconnect.";
delete connection_ptr;
});
});
return std::make_unique<ProxyClient<InitInterface>>(
kj::mv(init_client), connection.release(), /* destroy_connection= */ true);
}
//! Given stream and init objects, construct a new ProxyServer object that
//! handles requests from the stream by calling the init object. Embed the
//! ProxyServer in a Connection object that is stored and erased if
//! disconnected. This should be called from the event loop thread.
template <typename InitInterface, typename InitImpl>
void _Serve(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream, InitImpl& init)
{
loop.m_incoming_connections.emplace_front(loop, kj::mv(stream), [&](Connection& connection) {
// Disable deleter so proxy server object doesn't attempt to delete the
// init implementation when the proxy client is destroyed or
// disconnected.
return kj::heap<ProxyServer<InitInterface>>(std::shared_ptr<InitImpl>(&init, [](InitImpl*){}), connection);
});
auto it = loop.m_incoming_connections.begin();
it->onDisconnect([&loop, it] {
loop.log() << "IPC server: socket disconnected.";
loop.m_incoming_connections.erase(it);
});
}
//! Given connection receiver and an init object, handle incoming connections by
//! calling _Serve, to create ProxyServer objects and forward requests to the
//! init object.
template <typename InitInterface, typename InitImpl>
void _Listen(EventLoop& loop, kj::Own<kj::ConnectionReceiver>&& listener, InitImpl& init)
{
auto* ptr = listener.get();
loop.m_task_set->add(ptr->accept().then(
[&loop, &init, listener = kj::mv(listener)](kj::Own<kj::AsyncIoStream>&& stream) mutable {
_Serve<InitInterface>(loop, kj::mv(stream), init);
_Listen<InitInterface>(loop, kj::mv(listener), init);
}));
}
//! Given stream file descriptor and an init object, handle requests on the
//! stream by calling methods on the Init object.
template <typename InitInterface, typename InitImpl>
void ServeStream(EventLoop& loop, int fd, InitImpl& init)
{
_Serve<InitInterface>(
loop, loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), init);
}
//! Given listening socket file descriptor and an init object, handle incoming
//! connections and requests by calling methods on the Init object.
template <typename InitInterface, typename InitImpl>
void ListenConnections(EventLoop& loop, int fd, InitImpl& init)
{
loop.sync([&]() {
_Listen<InitInterface>(loop,
loop.m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP),
init);
});
}
extern thread_local ThreadContext g_thread_context;
} // namespace mp
#endif // MP_PROXY_IO_H

View file

@ -0,0 +1,724 @@
// 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 MP_PROXY_TYPES_H
#define MP_PROXY_TYPES_H
#include <mp/proxy-io.h>
#include <exception>
#include <optional>
#include <set>
#include <typeindex>
#include <vector>
namespace mp {
template <typename Value>
class ValueField
{
public:
ValueField(Value& value) : m_value(value) {}
ValueField(Value&& value) : m_value(value) {}
Value& m_value;
Value& get() { return m_value; }
Value& init() { return m_value; }
bool has() { return true; }
};
template <typename Accessor, typename Struct>
struct StructField
{
template <typename S>
StructField(S& struct_) : m_struct(struct_)
{
}
Struct& m_struct;
// clang-format off
template<typename A = Accessor> auto get() const -> decltype(A::get(this->m_struct)) { return A::get(this->m_struct); }
template<typename A = Accessor> auto has() const -> typename std::enable_if<A::optional, bool>::type { return A::getHas(m_struct); }
template<typename A = Accessor> auto has() const -> typename std::enable_if<!A::optional && A::boxed, bool>::type { return A::has(m_struct); }
template<typename A = Accessor> auto has() const -> typename std::enable_if<!A::optional && !A::boxed, bool>::type { return true; }
template<typename A = Accessor> auto want() const -> typename std::enable_if<A::requested, bool>::type { return A::getWant(m_struct); }
template<typename A = Accessor> auto want() const -> typename std::enable_if<!A::requested, bool>::type { return true; }
template<typename A = Accessor, typename... Args> decltype(auto) set(Args&&... args) const { return A::set(this->m_struct, std::forward<Args>(args)...); }
template<typename A = Accessor, typename... Args> decltype(auto) init(Args&&... args) const { return A::init(this->m_struct, std::forward<Args>(args)...); }
template<typename A = Accessor> auto setHas() const -> typename std::enable_if<A::optional>::type { return A::setHas(m_struct); }
template<typename A = Accessor> auto setHas() const -> typename std::enable_if<!A::optional>::type { }
template<typename A = Accessor> auto setWant() const -> typename std::enable_if<A::requested>::type { return A::setWant(m_struct); }
template<typename A = Accessor> auto setWant() const -> typename std::enable_if<!A::requested>::type { }
// clang-format on
};
// Destination parameter type that can be passed to ReadField function as an
// alternative to ReadDestUpdate. It allows the ReadField implementation to call
// the provided emplace_fn function with constructor arguments, so it only needs
// to determine the arguments, and can let the emplace function decide how to
// actually construct the read destination object. For example, if a std::string
// is being read, the ReadField call will call the custom emplace_fn with char*
// and size_t arguments, and the emplace function can decide whether to call the
// constructor via the operator or make_shared or emplace or just return a
// temporary string that is moved from.
template <typename LocalType, typename EmplaceFn>
struct ReadDestEmplace
{
ReadDestEmplace(TypeList<LocalType>, EmplaceFn&& emplace_fn) : m_emplace_fn(emplace_fn) {}
//! Simple case. If ReadField impementation calls this construct() method
//! with constructor arguments, just pass them on to the emplace function.
template <typename... Args>
decltype(auto) construct(Args&&... args)
{
return m_emplace_fn(std::forward<Args>(args)...);
}
//! More complicated case. If ReadField implementation works by calling this
//! update() method, adapt it call construct() instead. This requires
//! LocalType to have a default constructor to create new object that can be
//! passed to update()
template <typename UpdateFn>
decltype(auto) update(UpdateFn&& update_fn)
{
if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
// If destination type is const, default construct temporary
// to pass to update, then call move constructor via construct() to
// move from that temporary.
std::remove_cv_t<LocalType> temp;
update_fn(temp);
return construct(std::move(temp));
} else {
// Default construct object and pass it to update_fn.
decltype(auto) temp = construct();
update_fn(temp);
return temp;
}
}
EmplaceFn& m_emplace_fn;
};
//! Helper function to create a ReadDestEmplace object that constructs a
//! temporary, ReadField can return.
template <typename LocalType>
auto ReadDestTemp()
{
return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
return LocalType{std::forward<decltype(args)>(args)...};
}};
}
//! Destination parameter type that can be passed to ReadField function as an
//! alternative to ReadDestEmplace. Instead of requiring an emplace callback to
//! construct a new value, it just takes a reference to an existing value and
//! assigns a new value to it.
template <typename Value>
struct ReadDestUpdate
{
ReadDestUpdate(Value& value) : m_value(value) {}
//! Simple case. If ReadField works by calling update() just forward arguments to update_fn.
template <typename UpdateFn>
Value& update(UpdateFn&& update_fn)
{
update_fn(m_value);
return m_value;
}
//! More complicated case. If ReadField works by calling construct(), need
//! to reconstruct m_value in place.
template <typename... Args>
Value& construct(Args&&... args)
{
m_value.~Value();
new (&m_value) Value(std::forward<Args>(args)...);
return m_value;
}
Value& m_value;
};
template <typename... LocalTypes, typename... Args>
decltype(auto) ReadField(TypeList<LocalTypes...>, Args&&... args)
{
return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
}
template <typename LocalType, typename Input>
void ThrowField(TypeList<LocalType>, InvokeContext& invoke_context, Input&& input)
{
ReadField(
TypeList<LocalType>(), invoke_context, input, ReadDestEmplace(TypeList<LocalType>(),
[](auto&& ...args) -> const LocalType& { throw LocalType{std::forward<decltype(args)>(args)...}; }));
}
//! Special case for generic std::exception. It's an abstract type so it can't
//! be created directly. Rethrow as std::runtime_error so callers expecting it
//! will still catch it.
template <typename Input>
void ThrowField(TypeList<std::exception>, InvokeContext& invoke_context, Input&& input)
{
auto data = input.get();
throw std::runtime_error(std::string(CharCast(data.begin()), data.size()));
}
template <typename... Values>
bool CustomHasValue(InvokeContext& invoke_context, Values&&... value)
{
return true;
}
template <typename... LocalTypes, typename Context, typename... Values, typename Output>
void BuildField(TypeList<LocalTypes...>, Context& context, Output&& output, Values&&... values)
{
if (CustomHasValue(context, std::forward<Values>(values)...)) {
CustomBuildField(TypeList<LocalTypes...>(), Priority<3>(), context, std::forward<Values>(values)...,
std::forward<Output>(output));
}
}
// Adapter to let BuildField overloads methods work set & init list elements as
// if they were fields of a struct. If BuildField is changed to use some kind of
// accessor class instead of calling method pointers, then then maybe this could
// go away or be simplified, because would no longer be a need to return
// ListOutput method pointers emulating capnp struct method pointers..
template <typename ListType>
struct ListOutput;
template <typename T, ::capnp::Kind kind>
struct ListOutput<::capnp::List<T, kind>>
{
using Builder = typename ::capnp::List<T, kind>::Builder;
ListOutput(Builder& builder, size_t index) : m_builder(builder), m_index(index) {}
Builder& m_builder;
size_t m_index;
// clang-format off
decltype(auto) get() const { return this->m_builder[this->m_index]; }
decltype(auto) init() const { return this->m_builder[this->m_index]; }
template<typename B = Builder, typename Arg> decltype(auto) set(Arg&& arg) const { return static_cast<B&>(this->m_builder).set(m_index, std::forward<Arg>(arg)); }
template<typename B = Builder, typename Arg> decltype(auto) init(Arg&& arg) const { return static_cast<B&>(this->m_builder).init(m_index, std::forward<Arg>(arg)); }
// clang-format on
};
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
output.set(BuildPrimitive(invoke_context, std::forward<Value>(value), TypeList<decltype(output.get())>()));
}
//! PassField override for callable interface reference arguments.
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
auto PassField(Priority<1>, TypeList<LocalType&>, ServerContext& server_context, Fn&& fn, Args&&... args)
-> Require<typename decltype(Accessor::get(server_context.call_context.getParams()))::Calls>
{
// Just create a temporary ProxyClient if argument is a reference to an
// interface client. If argument needs to have a longer lifetime and not be
// destroyed after this call, a CustomPassField overload can be implemented
// to bypass this code, and a custom ProxyServerMethodTraits overload can be
// implemented in order to read the capability pointer out of params and
// construct a ProxyClient with a longer lifetime.
const auto& params = server_context.call_context.getParams();
const auto& input = Make<StructField, Accessor>(params);
using Interface = typename Decay<decltype(input.get())>::Calls;
auto param = std::make_unique<ProxyClient<Interface>>(input.get(), server_context.proxy_server.m_context.connection, false);
fn.invoke(server_context, std::forward<Args>(args)..., *param);
}
template <typename... Args>
void MaybeBuildField(std::true_type, Args&&... args)
{
BuildField(std::forward<Args>(args)...);
}
template <typename... Args>
void MaybeBuildField(std::false_type, Args&&...)
{
}
template <typename... Args>
void MaybeReadField(std::true_type, Args&&... args)
{
ReadField(std::forward<Args>(args)...);
}
template <typename... Args>
void MaybeReadField(std::false_type, Args&&...)
{
}
template <typename LocalType, typename Value, typename Output>
void MaybeSetWant(TypeList<LocalType*>, Priority<1>, Value&& value, Output&& output)
{
if (value) {
output.setWant();
}
}
template <typename LocalTypes, typename... Args>
void MaybeSetWant(LocalTypes, Priority<0>, Args&&...)
{
}
//! Default PassField implementation calling MaybeReadField/MaybeBuildField.
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
void PassField(Priority<0>, TypeList<LocalType>, ServerContext& server_context, Fn&& fn, Args&&... args)
{
InvokeContext& invoke_context = server_context;
using ArgType = RemoveCvRef<LocalType>;
std::optional<ArgType> param;
const auto& params = server_context.call_context.getParams();
MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
param.emplace(std::forward<decltype(args)>(args)...);
return *param;
}));
if constexpr (Accessor::in) {
assert(param);
} else {
if (!param) param.emplace();
}
fn.invoke(server_context, std::forward<Args>(args)..., static_cast<LocalType&&>(*param));
auto&& results = server_context.call_context.getResults();
MaybeBuildField(std::integral_constant<bool, Accessor::out>(), TypeList<LocalType>(), invoke_context,
Make<StructField, Accessor>(results), *param);
}
//! Default PassField implementation for count(0) arguments, calling ReadField/BuildField
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
void PassField(Priority<0>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
const auto& params = server_context.call_context.getParams();
const auto& input = Make<StructField, Accessor>(params);
ReadField(TypeList<>(), server_context, input);
fn.invoke(server_context, std::forward<Args>(args)...);
auto&& results = server_context.call_context.getResults();
BuildField(TypeList<>(), server_context, Make<StructField, Accessor>(results));
}
template <typename Derived, size_t N = 0>
struct IterateFieldsHelper
{
template <typename Arg1, typename Arg2, typename ParamList, typename NextFn, typename... NextFnArgs>
void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList, NextFn&& next_fn, NextFnArgs&&... next_fn_args)
{
using S = Split<N, ParamList>;
handleChain(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), typename S::First());
next_fn.handleChain(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), typename S::Second(),
std::forward<NextFnArgs>(next_fn_args)...);
}
template <typename Arg1, typename Arg2, typename ParamList>
void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList)
{
static_cast<Derived*>(this)->handleField(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ParamList());
}
};
struct IterateFields : IterateFieldsHelper<IterateFields, 0>
{
template <typename Arg1, typename Arg2, typename ParamList>
void handleField(Arg1&&, Arg2&&, ParamList)
{
}
};
template <typename Exception, typename Accessor>
struct ClientException
{
struct BuildParams : IterateFieldsHelper<BuildParams, 0>
{
template <typename Params, typename ParamList>
void handleField(InvokeContext& invoke_context, Params& params, ParamList)
{
}
BuildParams(ClientException* client_exception) : m_client_exception(client_exception) {}
ClientException* m_client_exception;
};
struct ReadResults : IterateFieldsHelper<ReadResults, 0>
{
template <typename Results, typename ParamList>
void handleField(InvokeContext& invoke_context, Results& results, ParamList)
{
StructField<Accessor, Results> input(results);
if (input.has()) {
ThrowField(TypeList<Exception>(), invoke_context, input);
}
}
ReadResults(ClientException* client_exception) : m_client_exception(client_exception) {}
ClientException* m_client_exception;
};
};
template <typename Accessor, typename... Types>
struct ClientParam
{
ClientParam(Types&&... values) : m_values(values...) {}
struct BuildParams : IterateFieldsHelper<BuildParams, sizeof...(Types)>
{
template <typename... Args>
void handleField(Args&&... args)
{
callBuild<0>(std::forward<Args>(args)...);
}
// TODO Possible optimization to speed up compile time:
// https://stackoverflow.com/a/7858971 Using enable_if below to check
// position when unpacking tuple might be slower than pattern matching
// approach in the stack overflow solution
template <size_t I, typename... Args>
auto callBuild(Args&&... args) -> typename std::enable_if<(I < sizeof...(Types))>::type
{
callBuild<I + 1>(std::forward<Args>(args)..., std::get<I>(m_client_param->m_values));
}
template <size_t I, typename Params, typename ParamList, typename... Values>
auto callBuild(ClientInvokeContext& invoke_context, Params& params, ParamList, Values&&... values) ->
typename std::enable_if<(I == sizeof...(Types))>::type
{
MaybeBuildField(std::integral_constant<bool, Accessor::in>(), ParamList(), invoke_context,
Make<StructField, Accessor>(params), std::forward<Values>(values)...);
MaybeSetWant(
ParamList(), Priority<1>(), std::forward<Values>(values)..., Make<StructField, Accessor>(params));
}
BuildParams(ClientParam* client_param) : m_client_param(client_param) {}
ClientParam* m_client_param;
};
struct ReadResults : IterateFieldsHelper<ReadResults, sizeof...(Types)>
{
template <typename... Args>
void handleField(Args&&... args)
{
callRead<0>(std::forward<Args>(args)...);
}
template <int I, typename... Args>
auto callRead(Args&&... args) -> typename std::enable_if<(I < sizeof...(Types))>::type
{
callRead<I + 1>(std::forward<Args>(args)..., std::get<I>(m_client_param->m_values));
}
template <int I, typename Results, typename... Params, typename... Values>
auto callRead(ClientInvokeContext& invoke_context, Results& results, TypeList<Params...>, Values&&... values)
-> typename std::enable_if<I == sizeof...(Types)>::type
{
MaybeReadField(std::integral_constant<bool, Accessor::out>(), TypeList<Decay<Params>...>(), invoke_context,
Make<StructField, Accessor>(results), ReadDestUpdate(values)...);
}
ReadResults(ClientParam* client_param) : m_client_param(client_param) {}
ClientParam* m_client_param;
};
std::tuple<Types&&...> m_values;
};
template <typename Accessor, typename... Types>
ClientParam<Accessor, Types...> MakeClientParam(Types&&... values)
{
return {std::forward<Types>(values)...};
}
struct ServerCall
{
// FIXME: maybe call call_context.releaseParams()
template <typename ServerContext, typename... Args>
decltype(auto) invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
{
return ProxyServerMethodTraits<typename decltype(server_context.call_context.getParams())::Reads>::invoke(
server_context,
std::forward<Args>(args)...);
}
};
struct ServerDestroy
{
template <typename ServerContext, typename... Args>
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
{
server_context.proxy_server.invokeDestroy(std::forward<Args>(args)...);
}
};
template <typename Accessor, typename Parent>
struct ServerRet : Parent
{
ServerRet(Parent parent) : Parent(parent) {}
template <typename ServerContext, typename... Args>
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
{
auto&& result = Parent::invoke(server_context, TypeList<>(), std::forward<Args>(args)...);
auto&& results = server_context.call_context.getResults();
InvokeContext& invoke_context = server_context;
BuildField(TypeList<decltype(result)>(), invoke_context, Make<StructField, Accessor>(results),
std::forward<decltype(result)>(result));
}
};
template <typename Exception, typename Accessor, typename Parent>
struct ServerExcept : Parent
{
ServerExcept(Parent parent) : Parent(parent) {}
template <typename ServerContext, typename... Args>
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
{
try {
return Parent::invoke(server_context, TypeList<>(), std::forward<Args>(args)...);
} catch (const Exception& exception) {
auto&& results = server_context.call_context.getResults();
BuildField(TypeList<Exception>(), server_context, Make<StructField, Accessor>(results), exception);
}
}
};
//! Helper for CustomPassField below. Call Accessor::get method if it has one,
//! otherwise return capnp::Void.
template <typename Accessor, typename Message>
decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
{
return Accessor::get(message);
}
template <typename Accessor>
::capnp::Void MaybeGet(...)
{
return {};
}
template <class Accessor>
void CustomPassField();
//! PassField override calling CustomPassField function, if it exists.
//! Defining a CustomPassField or CustomPassMessage overload is useful for
//! input/output parameters. If an overload is not defined these parameters will
//! just be deserialized on the server side with ReadField into a temporary
//! variable, then the server method will be called passing the temporary
//! variable as a parameter, then the temporary variable will be serialized and
//! sent back to the client with BuildField. But if a PassField or PassMessage
//! overload is defined, the overload is called with a callback to invoke and
//! pass parameters to the server side function, and run arbitrary code before
//! and after invoking the function.
template <typename Accessor, typename... Args>
auto PassField(Priority<2>, Args&&... args) -> decltype(CustomPassField<Accessor>(std::forward<Args>(args)...))
{
return CustomPassField<Accessor>(std::forward<Args>(args)...);
};
template <int argc, typename Accessor, typename Parent>
struct ServerField : Parent
{
ServerField(Parent parent) : Parent(parent) {}
const Parent& parent() const { return *this; }
template <typename ServerContext, typename ArgTypes, typename... Args>
decltype(auto) invoke(ServerContext& server_context, ArgTypes, Args&&... args) const
{
return PassField<Accessor>(Priority<2>(),
typename Split<argc, ArgTypes>::First(),
server_context,
this->parent(),
typename Split<argc, ArgTypes>::Second(),
std::forward<Args>(args)...);
}
};
template <int argc, typename Accessor, typename Parent>
ServerField<argc, Accessor, Parent> MakeServerField(Parent parent)
{
return {parent};
}
template <typename Request>
struct CapRequestTraits;
template <typename _Params, typename _Results>
struct CapRequestTraits<::capnp::Request<_Params, _Results>>
{
using Params = _Params;
using Results = _Results;
};
//! Entry point called by all generated ProxyClient destructors. This only logs
//! the object destruction. The actual cleanup happens in the ProxyClient base
//! destructor.
template <typename Client>
void clientDestroy(Client& client)
{
if (client.m_context.connection) {
client.m_context.connection->m_loop.log() << "IPC client destroy " << typeid(client).name();
} else {
KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name());
}
}
template <typename Server>
void serverDestroy(Server& server)
{
server.m_context.connection->m_loop.log() << "IPC server destroy " << typeid(server).name();
}
//! Entry point called by generated client code that looks like:
//!
//! ProxyClient<ClassName>::M0::Result ProxyClient<ClassName>::methodName(M0::Param<0> arg0, M0::Param<1> arg1) {
//! typename M0::Result result;
//! clientInvoke(*this, &InterfaceName::Client::methodNameRequest, MakeClientParam<...>(arg0), MakeClientParam<...>(arg1), MakeClientParam<...>(result));
//! return result;
//! }
//!
//! Ellipses above are where generated Accessor<> type declarations are inserted.
template <typename ProxyClient, typename GetRequest, typename... FieldObjs>
void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields)
{
if (!proxy_client.m_context.connection) {
throw std::logic_error("clientInvoke call made after disconnect");
}
if (!g_thread_context.waiter) {
assert(g_thread_context.thread_name.empty());
g_thread_context.thread_name = ThreadName(proxy_client.m_context.connection->m_loop.m_exe_name);
// If next assert triggers, it means clientInvoke is being called from
// the capnp event loop thread. This can happen when a ProxyServer
// method implementation that runs synchronously on the event loop
// thread tries to make a blocking callback to the client. Any server
// method that makes a blocking callback or blocks in general needs to
// run asynchronously off the event loop thread. This is easy to fix by
// just adding a 'context :Proxy.Context' argument to the capnp method
// declaration so the server method runs in a dedicated thread.
assert(!g_thread_context.loop_thread);
g_thread_context.waiter = std::make_unique<Waiter>();
proxy_client.m_context.connection->m_loop.logPlain()
<< "{" << g_thread_context.thread_name
<< "} IPC client first request from current thread, constructing waiter";
}
ClientInvokeContext invoke_context{*proxy_client.m_context.connection, g_thread_context};
std::exception_ptr exception;
std::string kj_exception;
bool done = false;
proxy_client.m_context.connection->m_loop.sync([&]() {
auto request = (proxy_client.m_client.*get_request)(nullptr);
using Request = CapRequestTraits<decltype(request)>;
using FieldList = typename ProxyClientMethodTraits<typename Request::Params>::Fields;
IterateFields().handleChain(invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...);
proxy_client.m_context.connection->m_loop.logPlain()
<< "{" << invoke_context.thread_context.thread_name << "} IPC client send "
<< TypeName<typename Request::Params>() << " " << LogEscape(request.toString());
proxy_client.m_context.connection->m_loop.m_task_set->add(request.send().then(
[&](::capnp::Response<typename Request::Results>&& response) {
proxy_client.m_context.connection->m_loop.logPlain()
<< "{" << invoke_context.thread_context.thread_name << "} IPC client recv "
<< TypeName<typename Request::Results>() << " " << LogEscape(response.toString());
try {
IterateFields().handleChain(
invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...);
} catch (...) {
exception = std::current_exception();
}
std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
done = true;
invoke_context.thread_context.waiter->m_cv.notify_all();
},
[&](const ::kj::Exception& e) {
kj_exception = kj::str("kj::Exception: ", e).cStr();
proxy_client.m_context.connection->m_loop.logPlain()
<< "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception;
std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
done = true;
invoke_context.thread_context.waiter->m_cv.notify_all();
}));
});
std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
invoke_context.thread_context.waiter->wait(lock, [&done]() { return done; });
if (exception) std::rethrow_exception(exception);
if (!kj_exception.empty()) proxy_client.m_context.connection->m_loop.raise() << kj_exception;
}
//! Invoke callable `fn()` that may return void. If it does return void, replace
//! return value with value of `ret()`. This is useful for avoiding code
//! duplication and branching in generic code that forwards calls to functions.
template <typename Fn, typename Ret>
auto ReplaceVoid(Fn&& fn, Ret&& ret) ->
typename std::enable_if<std::is_same<void, decltype(fn())>::value, decltype(ret())>::type
{
fn();
return ret();
}
//! Overload of above for non-void `fn()` case.
template <typename Fn, typename Ret>
auto ReplaceVoid(Fn&& fn, Ret&& ret) ->
typename std::enable_if<!std::is_same<void, decltype(fn())>::value, decltype(fn())>::type
{
return fn();
}
extern std::atomic<int> server_reqs;
//! Entry point called by generated server code that looks like:
//!
//! kj::Promise<void> ProxyServer<InterfaceName>::methodName(CallContext call_context) {
//! return serverInvoke(*this, call_context, MakeServerField<0, ...>(MakeServerField<1, ...>(Make<ServerRet, ...>(ServerCall()))));
//! }
//!
//! Ellipses above are where generated Accessor<> type declarations are inserted.
template <typename Server, typename CallContext, typename Fn>
kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
{
auto params = call_context.getParams();
using Params = decltype(params);
using Results = typename decltype(call_context.getResults())::Builds;
int req = ++server_reqs;
server.m_context.connection->m_loop.log() << "IPC server recv request #" << req << " "
<< TypeName<typename Params::Reads>() << " " << LogEscape(params.toString());
try {
using ServerContext = ServerInvokeContext<Server, CallContext>;
using ArgList = typename ProxyClientMethodTraits<typename Params::Reads>::Params;
ServerContext server_context{server, call_context, req};
// ReplaceVoid is used to support fn.invoke implementations that
// execute asynchronously and return promises, as well as
// implementations that execute synchronously and return void. The
// invoke function will be synchronous by default, but asynchronous if
// an mp.Context argument is passed, and the mp.Context PassField
// overload returns a promise executing the request in a worker thread
// and waiting for it to complete.
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
.then([&server, req](CallContext call_context) {
server.m_context.connection->m_loop.log() << "IPC server send response #" << req << " " << TypeName<Results>()
<< " " << LogEscape(call_context.getResults().toString());
});
} catch (const std::exception& e) {
server.m_context.connection->m_loop.log() << "IPC server unhandled exception: " << e.what();
throw;
} catch (...) {
server.m_context.connection->m_loop.log() << "IPC server unhandled exception";
throw;
}
}
//! Map to convert client interface pointers to ProxyContext struct references
//! at runtime using typeids.
struct ProxyTypeRegister {
template<typename Interface>
ProxyTypeRegister(TypeList<Interface>) {
types().emplace(typeid(Interface), [](void* iface) -> ProxyContext& { return static_cast<typename mp::ProxyType<Interface>::Client&>(*static_cast<Interface*>(iface)).m_context; });
}
using Types = std::map<std::type_index, ProxyContext&(*)(void*)>;
static Types& types() { static Types types; return types; }
};
} // namespace mp
#endif // MP_PROXY_TYPES_H

View file

@ -0,0 +1,65 @@
# 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.
@0xcc316e3f71a040fb;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("mp");
annotation include(file): Text;
annotation includeTypes(file): Text;
# Extra include paths to add to generated files.
annotation wrap(interface, struct): Text;
# Wrap capnp interface generating ProxyClient / ProxyServer C++ classes that
# forward calls to a C++ interface with same methods and parameters. Text
# string should be the name of the C++ interface.
# If applied to struct rather than an interface, this will generate a ProxyType
# struct with get methods for introspection and copying fields between C++ and
# capnp structs.
annotation count(param, struct, interface): Int32;
# Indicate how many C++ method parameters there are corresponding to one capnp
# parameter (default is 1). If not 1, multiple C++ method arguments will be
# condensed into a single capnp parameter by the client and then expanded by
# the server by CustomReadField/CustomBuildField overloads which need to be
# provided separately. An example would be a capnp Text parameter initialized
# from C++ char* and size arguments. Can be 0 to fill an implicit capnp
# parameter from client or server side context. If annotation is applied to an
# interface or struct type it will apply to all parameters of that type.
annotation exception(param): Text;
# Indicate that a result parameter corresponds to a C++ exception. Text string
# should be the name of a C++ exception type that the generated server class
# will catch and the client class will rethrow.
annotation name(field, method): Text;
# Name of the C++ method or field corresponding to a capnp method or field.
annotation skip(field): Void;
# Synonym for count(0).
interface ThreadMap $count(0) {
# Interface letting clients control which thread a method call should
# execute on. Clients create and name threads and pass the thread handle as
# a call parameter.
makeThread @0 (name :Text) -> (result :Thread);
}
interface Thread {
# Thread handle returned by makeThread corresponding to one server thread.
getName @0 () -> (result: Text);
}
struct Context $count(0) {
# Execution context passed as a parameter from the client class to the server class.
thread @0 : Thread;
# Handle of the server thread the current method call should execute on.
callbackThread @1 : Thread;
# Handle of the client thread that is calling the current method, and that
# any callbacks made by the server thread should be made on.
}

View file

@ -0,0 +1,299 @@
// 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 MP_PROXY_H
#define MP_PROXY_H
#include <mp/util.h>
#include <array>
#include <functional>
#include <list>
#include <stddef.h>
#include <tuple>
#include <type_traits>
#include <utility>
namespace mp {
class Connection;
class EventLoop;
//! Mapping from capnp interface type to proxy client implementation (specializations are generated by
//! proxy-codegen.cpp).
template <typename Interface>
struct ProxyClient;
//! Mapping from capnp interface type to proxy server implementation (specializations are generated by
//! proxy-codegen.cpp).
template <typename Interface>
struct ProxyServer;
//! Mapping from capnp method params type to method traits (specializations are generated by proxy-codegen.cpp).
template <typename Params>
struct ProxyMethod;
//! Mapping from capnp struct type to struct traits (specializations are generated by proxy-codegen.cpp).
template <typename Struct>
struct ProxyStruct;
//! Mapping from local c++ type to capnp type and traits (specializations are generated by proxy-codegen.cpp).
template <typename Type>
struct ProxyType;
using CleanupList = std::list<std::function<void()>>;
using CleanupIt = typename CleanupList::iterator;
inline void CleanupRun(CleanupList& fns) {
while (!fns.empty()) {
auto fn = std::move(fns.front());
fns.pop_front();
fn();
}
}
//! Context data associated with proxy client and server classes.
struct ProxyContext
{
Connection* connection;
CleanupList cleanup_fns;
ProxyContext(Connection* connection) : connection(connection) {}
};
//! Base class for generated ProxyClient classes that implement a C++ interface
//! and forward calls to a capnp interface.
template <typename Interface_, typename Impl_>
class ProxyClientBase : public Impl_
{
public:
using Interface = Interface_;
using Impl = Impl_;
using Sub = ProxyClient<Interface>;
using Super = ProxyClientBase<Interface, Impl>;
ProxyClientBase(typename Interface::Client client, Connection* connection, bool destroy_connection);
~ProxyClientBase() noexcept;
// construct/destroy methods called during client construction/destruction
// that can optionally be defined in capnp interfaces to invoke code on the
// server when proxy client objects are created and destroyed.
//
// The construct() method is not generally very useful, but can be used to
// run custom code on the server automatically when a ProxyClient client is
// constructed. The only current use is adding a construct method to Init
// interfaces that is called automatically on construction, so client and
// server exchange ThreadMap references and set Connection::m_thread_map
// values as soon as the Init client is created.
//
// construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap: Proxy.ThreadMap);
//
// But construct() is not necessary for this, thread maps could be passed
// through a normal method that is just called explicitly rather than
// implicitly.
//
// The destroy() method is more generally useful than construct(), because
// it ensures that the server object will be destroyed synchronously before
// the client destructor returns, instead of asynchronously at some
// unpredictable time after the client object is already destroyed and
// client code has moved on. If the destroy method accepts a Context
// parameter like:
//
// destroy @0 (context: Proxy.Context) -> ();
//
// then it will also ensure that the destructor runs on the same thread the
// client used to make other RPC calls, instead of running on the server
// EventLoop thread and possibly blocking it.
static void construct(Super&) {}
static void destroy(Super&) {}
typename Interface::Client m_client;
ProxyContext m_context;
};
//! Customizable (through template specialization) base class used in generated ProxyClient implementations from
//! proxy-codegen.cpp.
template <typename Interface, typename Impl>
class ProxyClientCustom : public ProxyClientBase<Interface, Impl>
{
using ProxyClientBase<Interface, Impl>::ProxyClientBase;
};
//! Base class for generated ProxyServer classes that implement capnp server
//! methods and forward calls to a wrapped c++ implementation class.
template <typename Interface_, typename Impl_>
struct ProxyServerBase : public virtual Interface_::Server
{
public:
using Interface = Interface_;
using Impl = Impl_;
ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection);
virtual ~ProxyServerBase();
void invokeDestroy();
/**
* Implementation pointer that may or may not be owned and deleted when this
* capnp server goes out of scope. It is owned for servers created to wrap
* unique_ptr<Impl> method arguments, but unowned for servers created to
* wrap Impl& method arguments.
*
* In the case of Impl& arguments, custom code is required on other side of
* the connection to delete the capnp client & server objects since native
* code on that side of the connection will just be taking a plain reference
* rather than a pointer, so won't be able to do its own cleanup. Right now
* this is implemented with addCloseHook callbacks to delete clients at
* appropriate times depending on semantics of the particular method being
* wrapped. */
std::shared_ptr<Impl> m_impl;
ProxyContext m_context;
};
//! Customizable (through template specialization) base class which ProxyServer
//! classes produced by generated code will inherit from. The default
//! specialization of this class just inherits from ProxyServerBase, but custom
//! specializations can be defined to control ProxyServer behavior.
//!
//! Specifically, it can be useful to specialize this class to add additional
//! state to ProxyServer classes, for example to cache state between IPC calls.
//! If this is done, however, care should be taken to ensure that the extra
//! state can be destroyed without blocking, because ProxyServer destructors are
//! called from the EventLoop thread, and if they block, it could deadlock the
//! program. One way to do avoid blocking is to clean up the state by pushing
//! cleanup callbacks to the m_context.cleanup_fns list, which run after the server
//! m_impl object is destroyed on the same thread destroying it (which will
//! either be an IPC worker thread if the ProxyServer is being explicitly
//! destroyed by a client calling a destroy() method with a Context argument and
//! Context.thread value set, or the temporary EventLoop::m_async_thread used to
//! run destructors without blocking the event loop when no-longer used server
//! objects are garbage collected by Cap'n Proto.) Alternately, if cleanup needs
//! to run before m_impl is destroyed, the specialization can override
//! invokeDestroy and destructor methods to do that.
template <typename Interface, typename Impl>
struct ProxyServerCustom : public ProxyServerBase<Interface, Impl>
{
using ProxyServerBase<Interface, Impl>::ProxyServerBase;
};
//! Function traits class used to get method parameter and result types, used in
//! generated ProxyClient and ProxyServer classes produced by gen.cpp to get C++
//! method type information. The generated code accesses these traits via
//! intermediate ProxyClientMethodTraits and ProxyServerMethodTraits classes,
//! which it is possible to specialize to change the way method arguments and
//! return values are handled.
//!
//! Fields of the trait class are:
//!
//! Params - TypeList of C++ ClassName::methodName parameter types
//! Result - Return type of ClassName::method
//! Param<N> - helper to access individual parameters by index number.
//! Fields - helper alias that appends Result type to the Params typelist if
//! it not void.
template <class Fn>
struct FunctionTraits;
//! Specialization of above extracting result and params types assuming the
//! template argument is a pointer-to-method type,
//! decltype(&ClassName::methodName)
template <class _Class, class _Result, class... _Params>
struct FunctionTraits<_Result (_Class::*const)(_Params...)>
{
using Params = TypeList<_Params...>;
using Result = _Result;
template <size_t N>
using Param = typename std::tuple_element<N, std::tuple<_Params...>>::type;
using Fields =
typename std::conditional<std::is_same<void, Result>::value, Params, TypeList<_Params..., _Result>>::type;
};
//! Traits class for a proxy method, providing the same
//! Params/Result/Param/Fields described in the FunctionTraits class above, plus
//! an additional invoke() method that calls the C++ method which is being
//! proxied, forwarding any arguments.
//!
//! The template argument should be the InterfaceName::MethodNameParams class
//! (generated by Cap'n Proto) associated with the method.
//!
//! Note: The class definition here is just the fallback definition used when
//! the other specialization below doesn't match. The fallback is only used for
//! capnp methods which do not have corresponding C++ methods, which in practice
//! is just the two special construct() and destroy() methods described in \ref
//! ProxyClientBase. These methods don't have any C++ parameters or return
//! types, so the trait information below reflects that.
template <typename MethodParams, typename Enable = void>
struct ProxyMethodTraits
{
using Params = TypeList<>;
using Result = void;
using Fields = Params;
template <typename ServerContext>
static void invoke(ServerContext&)
{
}
};
//! Specialization of above for proxy methods that have a
//! ProxyMethod<InterfaceName::MethodNameParams>::impl pointer-to-method
//! constant defined by generated code. This includes all functions defined in
//! the capnp interface except any construct() or destroy() methods, that are
//! assumed not to correspond to real member functions in the C++ class, and
//! will use the fallback traits definition above. The generated code this
//! specialization relies on looks like:
//!
//! struct ProxyMethod<InterfaceName::MethodNameParams>
//! {
//! static constexpr auto impl = &ClassName::methodName;
//! };
template <typename MethodParams>
struct ProxyMethodTraits<MethodParams, Require<decltype(ProxyMethod<MethodParams>::impl)>>
: public FunctionTraits<decltype(ProxyMethod<MethodParams>::impl)>
{
template <typename ServerContext, typename... Args>
static decltype(auto) invoke(ServerContext& server_context, Args&&... args)
{
return (server_context.proxy_server.m_impl.get()->*ProxyMethod<MethodParams>::impl)(std::forward<Args>(args)...);
}
};
//! Customizable (through template specialization) traits class used in generated ProxyClient implementations from
//! proxy-codegen.cpp.
template <typename MethodParams>
struct ProxyClientMethodTraits : public ProxyMethodTraits<MethodParams>
{
};
//! Customizable (through template specialization) traits class used in generated ProxyServer implementations from
//! proxy-codegen.cpp.
template <typename MethodParams>
struct ProxyServerMethodTraits : public ProxyMethodTraits<MethodParams>
{
};
static constexpr int FIELD_IN = 1;
static constexpr int FIELD_OUT = 2;
static constexpr int FIELD_OPTIONAL = 4;
static constexpr int FIELD_REQUESTED = 8;
static constexpr int FIELD_BOXED = 16;
//! Accessor type holding flags that determine how to access a message field.
template <typename Field, int flags>
struct Accessor : public Field
{
static const bool in = flags & FIELD_IN;
static const bool out = flags & FIELD_OUT;
static const bool optional = flags & FIELD_OPTIONAL;
static const bool requested = flags & FIELD_REQUESTED;
static const bool boxed = flags & FIELD_BOXED;
};
//! Wrapper around std::function for passing std::function objects between client and servers.
template <typename Fn>
class ProxyCallback;
//! Specialization of above to separate Result and Arg types.
template <typename Result, typename... Args>
class ProxyCallback<std::function<Result(Args...)>>
{
public:
virtual Result call(Args&&... args) = 0;
};
} // namespace mp
#endif // MP_PROXY_H

View file

@ -0,0 +1,36 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_CHAR_H
#define MP_PROXY_TYPE_CHAR_H
#include <mp/util.h>
namespace mp {
template <typename Output, size_t size>
void CustomBuildField(TypeList<const unsigned char*>,
Priority<3>,
InvokeContext& invoke_context,
const unsigned char (&value)[size],
Output&& output)
{
auto result = output.init(size);
memcpy(result.begin(), value, size);
}
template <size_t size, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<unsigned char[size]>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
memcpy(value, data.begin(), size);
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_CHAR_H

View file

@ -0,0 +1,34 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_CHRONO_H
#define MP_PROXY_TYPE_CHRONO_H
#include <mp/util.h>
#include <chrono>
namespace mp {
//! Overload CustomBuildField and CustomReadField to serialize std::chrono
//! parameters and return values as numbers.
template <class Rep, class Period, typename Value, typename Output>
void CustomBuildField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context, Value&& value,
Output&& output)
{
static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(),
"capnp type does not have enough range to hold lowest std::chrono::duration value");
static_assert(std::numeric_limits<decltype(output.get())>::max() >= std::numeric_limits<Rep>::max(),
"capnp type does not have enough range to hold highest std::chrono::duration value");
output.set(value.count());
}
template <class Rep, class Period, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
return read_dest.construct(input.get());
}
} // namespace mp
#endif // MP_PROXY_TYPE_CHRONO_H

View file

@ -0,0 +1,173 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_CONTEXT_H
#define MP_PROXY_TYPE_CONTEXT_H
#include <mp/proxy-io.h>
#include <mp/util.h>
namespace mp {
template <typename Output>
void CustomBuildField(TypeList<>,
Priority<1>,
ClientInvokeContext& invoke_context,
Output&& output,
typename std::enable_if<std::is_same<decltype(output.get()), Context::Builder>::value>::type* enable = nullptr)
{
auto& connection = invoke_context.connection;
auto& thread_context = invoke_context.thread_context;
// Create local Thread::Server object corresponding to the current thread
// and pass a Thread::Client reference to it in the Context.callbackThread
// field so the function being called can make callbacks to this thread.
// Also store the Thread::Client reference in the callback_threads map so
// future calls over this connection can reuse it.
auto [callback_thread, _]{SetThread(
thread_context.callback_threads, thread_context.waiter->m_mutex, &connection,
[&] { return connection.m_threads.add(kj::heap<ProxyServer<Thread>>(thread_context, std::thread{})); })};
// Call remote ThreadMap.makeThread function so server will create a
// dedicated worker thread to run function calls from this thread. Store the
// Thread::Client reference it returns in the request_threads map.
auto make_request_thread{[&]{
// This code will only run if an IPC client call is being made for the
// first time on this thread. After the first call, subsequent calls
// will use the existing request thread. This code will also never run at
// all if the current thread is a request thread created for a different
// IPC client, because in that case PassField code (below) will have set
// request_thread to point to the calling thread.
auto request = connection.m_thread_map.makeThreadRequest();
request.setName(thread_context.thread_name);
return request.send().getResult(); // Nonblocking due to capnp request pipelining.
}};
auto [request_thread, _1]{SetThread(
thread_context.request_threads, thread_context.waiter->m_mutex,
&connection, make_request_thread)};
auto context = output.init();
context.setThread(request_thread->second.m_client);
context.setCallbackThread(callback_thread->second.m_client);
}
//! PassField override for mp.Context arguments. Return asynchronously and call
//! function on other thread found in context.
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) ->
typename std::enable_if<
std::is_same<decltype(Accessor::get(server_context.call_context.getParams())), Context::Reader>::value,
kj::Promise<typename ServerContext::CallContext>>::type
{
const auto& params = server_context.call_context.getParams();
Context::Reader context_arg = Accessor::get(params);
auto future = kj::newPromiseAndFulfiller<typename ServerContext::CallContext>();
auto& server = server_context.proxy_server;
int req = server_context.req;
auto invoke = MakeAsyncCallable(
[fulfiller = kj::mv(future.fulfiller),
call_context = kj::mv(server_context.call_context), &server, req, fn, args...]() mutable {
const auto& params = call_context.getParams();
Context::Reader context_arg = Accessor::get(params);
ServerContext server_context{server, call_context, req};
bool disconnected{false};
{
// Before invoking the function, store a reference to the
// callbackThread provided by the client in the
// thread_local.request_threads map. This way, if this
// server thread needs to execute any RPCs that call back to
// the client, they will happen on the same client thread
// that is waiting for this function, just like what would
// happen if this were a normal function call made on the
// local stack.
//
// If the request_threads map already has an entry for this
// connection, it will be left unchanged, and it indicates
// that the current thread is an RPC client thread which is
// in the middle of an RPC call, and the current RPC call is
// a nested call from the remote thread handling that RPC
// call. In this case, the callbackThread value should point
// to the same thread already in the map, so there is no
// need to update the map.
auto& thread_context = g_thread_context;
auto& request_threads = thread_context.request_threads;
auto [request_thread, inserted]{SetThread(
request_threads, thread_context.waiter->m_mutex,
server.m_context.connection,
[&] { return context_arg.getCallbackThread(); })};
// If an entry was inserted into the requests_threads map,
// remove it after calling fn.invoke. If an entry was not
// inserted, one already existed, meaning this must be a
// recursive call (IPC call calling back to the caller which
// makes another IPC call), so avoid modifying the map.
const bool erase_thread{inserted};
KJ_DEFER({
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
// Call erase here with a Connection* argument instead
// of an iterator argument, because the `request_thread`
// iterator may be invalid if the connection is closed
// during this function call. More specifically, the
// iterator may be invalid because SetThread adds a
// cleanup callback to the Connection destructor that
// erases the thread from the map, and also because the
// ProxyServer<Thread> destructor calls
// request_threads.clear().
if (erase_thread) {
disconnected = !request_threads.erase(server.m_context.connection);
} else {
disconnected = !request_threads.count(server.m_context.connection);
}
});
fn.invoke(server_context, args...);
}
if (disconnected) {
// If disconnected is true, the Connection object was
// destroyed during the method call. Deal with this by
// returning without ever fulfilling the promise, which will
// cause the ProxyServer object to leak. This is not ideal,
// but fixing the leak will require nontrivial code changes
// because there is a lot of code assuming ProxyServer
// objects are destroyed before Connection objects.
return;
}
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
server.m_context.connection->m_loop.sync([&] {
auto fulfiller_dispose = kj::mv(fulfiller);
fulfiller_dispose->fulfill(kj::mv(call_context));
});
}))
{
server.m_context.connection->m_loop.sync([&]() {
auto fulfiller_dispose = kj::mv(fulfiller);
fulfiller_dispose->reject(kj::mv(*exception));
});
}
});
// Lookup Thread object specified by the client. The specified thread should
// be a local Thread::Server object, but it needs to be looked up
// asynchronously with getLocalServer().
auto thread_client = context_arg.getThread();
return server.m_context.connection->m_threads.getLocalServer(thread_client)
.then([&server, invoke, req](const kj::Maybe<Thread::Server&>& perhaps) {
// Assuming the thread object is found, pass it a pointer to the
// `invoke` lambda above which will invoke the function on that
// thread.
KJ_IF_MAYBE (thread_server, perhaps) {
const auto& thread = static_cast<ProxyServer<Thread>&>(*thread_server);
server.m_context.connection->m_loop.log()
<< "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}";
thread.m_thread_context.waiter->post(std::move(invoke));
} else {
server.m_context.connection->m_loop.log()
<< "IPC server error request #" << req << ", missing thread to execute request";
throw std::runtime_error("invalid thread handle");
}
})
// Wait for the invocation to finish before returning to the caller.
.then([invoke_wait = kj::mv(future.promise)]() mutable { return kj::mv(invoke_wait); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_CONTEXT_H

View file

@ -0,0 +1,46 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_DATA_H
#define MP_PROXY_TYPE_DATA_H
#include <mp/util.h>
namespace mp {
template <typename T, typename U>
concept IsSpanOf =
std::convertible_to<T, std::span<const U>> &&
std::constructible_from<T, const U*, const U*>;
template <typename T>
concept IsByteSpan =
IsSpanOf<T, std::byte> ||
IsSpanOf<T, char> ||
IsSpanOf<T, unsigned char> ||
IsSpanOf<T, signed char>;
//! Generic ::capnp::Data field builder for any C++ type that can be converted
//! to a span of bytes, like std::vector<char> or std::array<uint8_t>, or custom
//! blob types like uint256 or PKHash with data() and size() methods pointing to
//! bytes.
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output)
requires (std::is_same_v<decltype(output.get()), ::capnp::Data::Builder> && IsByteSpan<LocalType>)
{
auto data = std::span{value};
auto result = output.init(data.size());
memcpy(result.begin(), data.data(), data.size());
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
requires (std::is_same_v<decltype(input.get()), ::capnp::Data::Reader> && IsByteSpan<LocalType>)
{
using ByteType = decltype(std::span{std::declval<LocalType>().begin(), std::declval<LocalType>().end()})::element_type;
const kj::byte *begin{input.get().begin()}, *end{input.get().end()};
return read_dest.construct(reinterpret_cast<const ByteType*>(begin), reinterpret_cast<const ByteType*>(end));
}
} // namespace mp
#endif // MP_PROXY_TYPE_DATA_H

View file

@ -0,0 +1,38 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_DECAY_H
#define MP_PROXY_TYPE_DECAY_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<const LocalType>,
Priority<0>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType&>, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType&&>,
Priority<0>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
} // namespace mp
#endif // MP_PROXY_TYPE_DECAY_H

View file

@ -0,0 +1,22 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_EXCEPTION_H
#define MP_PROXY_TYPE_EXCEPTION_H
#include <mp/util.h>
namespace mp {
template <typename Output>
void CustomBuildField(TypeList<std::exception>,
Priority<1>,
InvokeContext& invoke_context,
const std::exception& value,
Output&& output)
{
BuildField(TypeList<std::string>(), invoke_context, output, std::string(value.what()));
}
} // namespace mp
#endif // MP_PROXY_TYPE_EXCEPTION_H

View file

@ -0,0 +1,67 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_FUNCTION_H
#define MP_PROXY_TYPE_FUNCTION_H
#include <mp/util.h>
namespace mp {
//! Adapter to convert ProxyCallback object call to function object call.
template <typename Result, typename... Args>
class ProxyCallbackImpl final : public ProxyCallback<std::function<Result(Args...)>>
{
using Fn = std::function<Result(Args...)>;
Fn m_fn;
public:
ProxyCallbackImpl(Fn fn) : m_fn(std::move(fn)) {}
Result call(Args&&... args) override { return m_fn(std::forward<Args>(args)...); }
};
template <typename Value, typename FnR, typename... FnParams, typename Output>
void CustomBuildField(TypeList<std::function<FnR(FnParams...)>>,
Priority<1>,
InvokeContext& invoke_context,
Value& value,
Output&& output)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
using Callback = ProxyCallbackImpl<FnR, FnParams...>;
output.set(kj::heap<ProxyServer<Interface>>(
std::make_shared<Callback>(std::forward<Value>(value)), invoke_context.connection));
}
}
// ProxyCallFn class is needed because c++11 doesn't support auto lambda parameters.
// It's equivalent c++14: [invoke_context](auto&& params) {
// invoke_context->call(std::forward<decltype(params)>(params)...)
template <typename InvokeContext>
struct ProxyCallFn
{
InvokeContext m_proxy;
template <typename... CallParams>
decltype(auto) operator()(CallParams&&... params) { return this->m_proxy->call(std::forward<CallParams>(params)...); }
};
template <typename FnR, typename... FnParams, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::function<FnR(FnParams...)>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
if (input.has()) {
using Interface = typename Decay<decltype(input.get())>::Calls;
auto client = std::make_shared<ProxyClient<Interface>>(
input.get(), &invoke_context.connection, /* destroy_connection= */ false);
return read_dest.construct(ProxyCallFn<decltype(client)>{std::move(client)});
}
return read_dest.construct();
};
} // namespace mp
#endif // MP_PROXY_TYPE_FUNCTION_H

View file

@ -0,0 +1,112 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_INTERFACE_H
#define MP_PROXY_TYPE_INTERFACE_H
#include <mp/util.h>
namespace mp {
template <typename Interface, typename Impl>
kj::Own<typename Interface::Server> MakeProxyServer(InvokeContext& context, std::shared_ptr<Impl> impl)
{
return kj::heap<ProxyServer<Interface>>(std::move(impl), context.connection);
}
template <typename Interface, typename Impl>
kj::Own<typename Interface::Server> CustomMakeProxyServer(InvokeContext& context, std::shared_ptr<Impl>&& impl)
{
return MakeProxyServer<Interface, Impl>(context, std::move(impl));
}
template <typename Impl, typename Value, typename Output>
void CustomBuildField(TypeList<std::unique_ptr<Impl>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename Decay<decltype(output.get())>::Calls* enable = nullptr)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::shared_ptr<Impl>(value.release())));
}
}
template <typename Impl, typename Value, typename Output>
void CustomBuildField(TypeList<std::shared_ptr<Impl>>,
Priority<2>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename Decay<decltype(output.get())>::Calls* enable = nullptr)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::move(value)));
}
}
template <typename Impl, typename Output>
void CustomBuildField(TypeList<Impl&>,
Priority<1>,
InvokeContext& invoke_context,
Impl& value,
Output&& output,
typename decltype(output.get())::Calls* enable = nullptr)
{
// Disable deleter so proxy server object doesn't attempt to delete the
// wrapped implementation when the proxy client is destroyed or
// disconnected.
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::shared_ptr<Impl>(&value, [](Impl*){})));
}
template <typename Interface, typename Impl>
std::unique_ptr<Impl> MakeProxyClient(InvokeContext& context, typename Interface::Client&& client)
{
return std::make_unique<ProxyClient<Interface>>(
std::move(client), &context.connection, /* destroy_connection= */ false);
}
template <typename Interface, typename Impl>
std::unique_ptr<Impl> CustomMakeProxyClient(InvokeContext& context, typename Interface::Client&& client)
{
return MakeProxyClient<Interface, Impl>(context, kj::mv(client));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::unique_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
{
using Interface = typename Decay<decltype(input.get())>::Calls;
if (input.has()) {
return read_dest.construct(
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
}
return read_dest.construct();
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
{
using Interface = typename Decay<decltype(input.get())>::Calls;
if (input.has()) {
return read_dest.construct(
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
}
return read_dest.construct();
}
} // namespace mp
#endif // MP_PROXY_TYPE_INTERFACE_H

View file

@ -0,0 +1,52 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_MAP_H
#define MP_PROXY_TYPE_MAP_H
#include <mp/proxy-types.h>
#include <mp/type-pair.h>
#include <mp/util.h>
namespace mp {
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dededup with vector handler above
auto list = output.init(value.size());
size_t i = 0;
for (const auto& elem : value) {
BuildField(TypeList<std::pair<KeyLocalType, ValueLocalType>>(), invoke_context,
ListOutput<typename decltype(list)::Builds>(list, i), elem);
++i;
}
}
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
for (auto item : data) {
ReadField(TypeList<std::pair<const KeyLocalType, ValueLocalType>>(), invoke_context,
Make<ValueField>(item),
ReadDestEmplace(
TypeList<std::pair<const KeyLocalType, ValueLocalType>>(), [&](auto&&... args) -> auto& {
return *value.emplace(std::forward<decltype(args)>(args)...).first;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_MAP_H

View file

@ -0,0 +1,64 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_MESSAGE_H
#define MP_PROXY_TYPE_MESSAGE_H
#include <mp/util.h>
namespace mp {
//! Overload CustomBuildField to serialize objects that have CustomBuildMessage
//! overloads. Defining a CustomBuildMessage overload is simpler than defining a
//! CustomBuildField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output,
decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr)
{
CustomBuildMessage(invoke_context, value, std::move(output.init()));
}
//! Overload CustomReadField to serialize objects that have CustomReadMessage
//! overloads. Defining a CustomReadMessage overload is simpler than defining a
//! CustomReadField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename LocalType, typename Reader, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Reader&& reader,
ReadDest&& read_dest,
decltype(CustomReadMessage(invoke_context, reader.get(),
std::declval<LocalType&>()))* enable = nullptr)
{
return read_dest.update([&](auto& value) { if (reader.has()) CustomReadMessage(invoke_context, reader.get(), value); });
}
//! Helper for CustomPassField below. Call Accessor::init method if it has one,
//! otherwise do nothing.
template <typename Accessor, typename Message>
decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
{
return Accessor::init(message);
}
template <typename Accessor>
::capnp::Void MaybeInit(...)
{
return {};
}
//! Overload CustomPassField to serialize objects that have CustomPassMessage
//! overloads. Defining a CustomPassMessage overload is simpler than defining a
//! CustomPassField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename Accessor, typename... LocalTypes, typename ServerContext, typename Fn, typename... Args>
auto CustomPassField(TypeList<LocalTypes...>, ServerContext& server_context, Fn&& fn, Args&&... args)
-> decltype(CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
MaybeGet<Accessor>(server_context.call_context.getResults()), nullptr))
{
CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
MaybeInit<Accessor>(server_context.call_context.getResults()),
[&](LocalTypes... param) { fn.invoke(server_context, std::forward<Args>(args)..., param...); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_MESSAGE_H

View file

@ -0,0 +1,87 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_NUMBER_H
#define MP_PROXY_TYPE_NUMBER_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_enum<Value>::value>::type* enable = nullptr)
{
using E = std::make_unsigned_t<std::underlying_type_t<Value>>;
using T = std::make_unsigned_t<LocalType>;
static_assert(std::numeric_limits<T>::max() >= std::numeric_limits<E>::max(), "mismatched integral/enum types");
return static_cast<LocalType>(value);
}
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_integral<Value>::value, int>::type* enable = nullptr)
{
static_assert(
std::numeric_limits<LocalType>::lowest() <= std::numeric_limits<Value>::lowest(), "mismatched integral types");
static_assert(
std::numeric_limits<LocalType>::max() >= std::numeric_limits<Value>::max(), "mismatched integral types");
return value;
}
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_floating_point<Value>::value>::type* enable = nullptr)
{
static_assert(std::is_same<Value, LocalType>::value,
"mismatched floating point types. please fix message.capnp type declaration to match wrapped interface");
return value;
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_enum<LocalType>::value>::type* enable = 0)
{
return read_dest.construct(static_cast<LocalType>(input.get()));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_integral<LocalType>::value>::type* enable = nullptr)
{
auto value = input.get();
if (value < std::numeric_limits<LocalType>::min() || value > std::numeric_limits<LocalType>::max()) {
throw std::range_error("out of bound int received");
}
return read_dest.construct(static_cast<LocalType>(value));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_floating_point<LocalType>::value>::type* enable = 0)
{
auto value = input.get();
static_assert(std::is_same<LocalType, decltype(value)>::value, "floating point type mismatch");
return read_dest.construct(value);
}
} // namespace mp
#endif // MP_PROXY_TYPE_NUMBER_H

View file

@ -0,0 +1,48 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_OPTIONAL_H
#define MP_PROXY_TYPE_OPTIONAL_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::optional<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
if (value) {
output.setHas();
// FIXME: should std::move value if destvalue is rref?
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::optional<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
} else if (value) {
ReadField(TypeList<LocalType>(), invoke_context, input, ReadDestUpdate(*value));
} else {
ReadField(TypeList<LocalType>(), invoke_context, input,
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value.emplace(std::forward<decltype(args)>(args)...);
return *value;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_OPTIONAL_H

View file

@ -0,0 +1,50 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_PAIR_H
#define MP_PROXY_TYPE_PAIR_H
#include <mp/util.h>
namespace mp {
// FIXME: Overload on output type instead of value type and switch to std::get and merge with next overload
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::pair<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto pair = output.init();
using Accessors = typename ProxyStruct<typename decltype(pair)::Builds>::Accessors;
BuildField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair), value.first);
BuildField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair), value.second);
}
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::pair<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
const auto& pair = input.get();
using Accessors = typename ProxyStruct<typename Decay<decltype(pair)>::Reads>::Accessors;
ReadField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair),
ReadDestEmplace(TypeList<KeyLocalType>(), [&](auto&&... key_args) -> auto& {
KeyLocalType* key = nullptr;
ReadField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair),
ReadDestEmplace(TypeList<ValueLocalType>(), [&](auto&&... value_args) -> auto& {
auto& ret = read_dest.construct(std::piecewise_construct, std::forward_as_tuple(key_args...),
std::forward_as_tuple(value_args...));
key = &ret.first;
return ret.second;
}));
return *key;
}));
}
} // namespace mp
#endif // MP_PROXY_TYPE_PAIR_H

View file

@ -0,0 +1,113 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_POINTER_H
#define MP_PROXY_TYPE_POINTER_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType*>, Priority<3>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
if (value) {
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::shared_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
if (value) {
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType*>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (value) {
ReadField(TypeList<LocalType>(), invoke_context, std::forward<Input>(input), ReadDestUpdate(*value));
}
});
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<LocalType>>,
Priority<0>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
} else if (value) {
ReadField(TypeList<LocalType>(), invoke_context, input, ReadDestUpdate(*value));
} else {
ReadField(TypeList<LocalType>(), invoke_context, input,
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value = std::make_shared<LocalType>(std::forward<decltype(args)>(args)...);
return *value;
}));
}
});
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<const LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
return;
}
ReadField(TypeList<LocalType>(), invoke_context, std::forward<Input>(input),
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value = std::make_shared<LocalType>(std::forward<decltype(args)>(args)...);
return *value;
}));
});
}
//! PassField override for C++ pointer arguments.
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
void PassField(Priority<1>, TypeList<LocalType*>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
const auto& params = server_context.call_context.getParams();
const auto& input = Make<StructField, Accessor>(params);
if (!input.want()) {
fn.invoke(server_context, std::forward<Args>(args)..., nullptr);
return;
}
InvokeContext& invoke_context = server_context;
Decay<LocalType> param;
MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<LocalType>(), invoke_context, input,
ReadDestUpdate(param));
fn.invoke(server_context, std::forward<Args>(args)..., &param);
auto&& results = server_context.call_context.getResults();
MaybeBuildField(std::integral_constant<bool, Accessor::out>(), TypeList<LocalType>(), invoke_context,
Make<StructField, Accessor>(results), param);
}
} // namespace mp
#endif // MP_PROXY_TYPE_POINTER_H

View file

@ -0,0 +1,48 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_SET_H
#define MP_PROXY_TYPE_SET_H
#include <mp/proxy-types.h>
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::set<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dededup with vector handler above
auto list = output.init(value.size());
size_t i = 0;
for (const auto& elem : value) {
BuildField(TypeList<LocalType>(), invoke_context, ListOutput<typename decltype(list)::Builds>(list, i), elem);
++i;
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::set<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
for (auto item : data) {
ReadField(TypeList<LocalType>(), invoke_context, Make<ValueField>(item),
ReadDestEmplace(TypeList<const LocalType>(), [&](auto&&... args) -> auto& {
return *value.emplace(std::forward<decltype(args)>(args)...).first;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_SET_H

View file

@ -0,0 +1,34 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_STRING_H
#define MP_PROXY_TYPE_STRING_H
#include <mp/util.h>
namespace mp {
template <typename Value, typename Output>
void CustomBuildField(TypeList<std::string>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto result = output.init(value.size());
memcpy(result.begin(), value.data(), value.size());
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::string>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
auto data = input.get();
return read_dest.construct(CharCast(data.begin()), data.size());
}
} // namespace mp
#endif // MP_PROXY_TYPE_STRING_H

View file

@ -0,0 +1,85 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_STRUCT_H
#define MP_PROXY_TYPE_STRUCT_H
#include <mp/util.h>
namespace mp {
template <size_t index, typename LocalType, typename Value, typename Output>
void BuildOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Output&& output,
Value&& value,
typename std::enable_if < index<ProxyType<LocalType>::fields>::type * enable = nullptr)
{
using Index = std::integral_constant<size_t, index>;
using Struct = typename ProxyType<LocalType>::Struct;
using Accessor = typename std::tuple_element<index, typename ProxyStruct<Struct>::Accessors>::type;
auto&& field_output = Make<StructField, Accessor>(output);
auto&& field_value = value.*ProxyType<LocalType>::get(Index());
BuildField(TypeList<Decay<decltype(field_value)>>(), invoke_context, field_output, field_value);
BuildOne<index + 1>(param, invoke_context, output, value);
}
template <size_t index, typename LocalType, typename Value, typename Output>
void BuildOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Output&& output,
Value&& value,
typename std::enable_if<index == ProxyType<LocalType>::fields>::type* enable = nullptr)
{
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType> local_type,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename ProxyType<LocalType>::Struct* enable = nullptr)
{
BuildOne<0>(local_type, invoke_context, output.init(), value);
}
template <size_t index, typename LocalType, typename Input, typename Value>
void ReadOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Input&& input,
Value&& value,
typename std::enable_if<index != ProxyType<LocalType>::fields>::type* enable = nullptr)
{
using Index = std::integral_constant<size_t, index>;
using Struct = typename ProxyType<LocalType>::Struct;
using Accessor = typename std::tuple_element<index, typename ProxyStruct<Struct>::Accessors>::type;
const auto& struc = input.get();
auto&& field_value = value.*ProxyType<LocalType>::get(Index());
ReadField(TypeList<RemoveCvRef<decltype(field_value)>>(), invoke_context, Make<StructField, Accessor>(struc),
ReadDestUpdate(field_value));
ReadOne<index + 1>(param, invoke_context, input, value);
}
template <size_t index, typename LocalType, typename Input, typename Value>
void ReadOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Input& input,
Value& value,
typename std::enable_if<index == ProxyType<LocalType>::fields>::type* enable = nullptr)
{
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType> param,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename ProxyType<LocalType>::Struct* enable = nullptr)
{
return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_STRUCT_H

View file

@ -0,0 +1,41 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_THREADMAP_H
#define MP_PROXY_TYPE_THREADMAP_H
#include <mp/util.h>
namespace mp {
template <>
struct ProxyServer<ThreadMap> final : public virtual ThreadMap::Server
{
public:
ProxyServer(Connection& connection);
kj::Promise<void> makeThread(MakeThreadContext context) override;
Connection& m_connection;
};
template <typename Output>
void CustomBuildField(TypeList<>,
Priority<1>,
InvokeContext& invoke_context,
Output&& output,
typename std::enable_if<std::is_same<decltype(output.get()), ThreadMap::Client>::value>::type* enable = nullptr)
{
output.set(kj::heap<ProxyServer<ThreadMap>>(invoke_context.connection));
}
template <typename Input>
decltype(auto) CustomReadField(TypeList<>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
typename std::enable_if<std::is_same<decltype(input.get()), ThreadMap::Client>::value>::type* enable = nullptr)
{
invoke_context.connection.m_thread_map = input.get();
}
} // namespace mp
#endif // MP_PROXY_TYPE_THREADMAP_H

View file

@ -0,0 +1,45 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_TUPLE_H
#define MP_PROXY_TYPE_TUPLE_H
#include <mp/util.h>
namespace mp {
// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples.
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::tuple<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto pair = output.init();
using Accessors = typename ProxyStruct<typename decltype(pair)::Builds>::Accessors;
BuildField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair), std::get<0>(value));
BuildField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair), std::get<1>(value));
}
// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples.
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::tuple<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
const auto& pair = input.get();
using Struct = ProxyStruct<typename Decay<decltype(pair)>::Reads>;
using Accessors = typename Struct::Accessors;
ReadField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair),
ReadDestUpdate(std::get<0>(value)));
ReadField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair),
ReadDestUpdate(std::get<1>(value)));
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_TUPLE_H

View file

@ -0,0 +1,71 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_VECTOR_H
#define MP_PROXY_TYPE_VECTOR_H
#include <mp/proxy-types.h>
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::vector<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dedup with set handler below
auto list = output.init(value.size());
size_t i = 0;
for (auto it = value.begin(); it != value.end(); ++it, ++i) {
BuildField(TypeList<LocalType>(), invoke_context, ListOutput<typename decltype(list)::Builds>(list, i), *it);
}
}
inline static bool BuildPrimitive(InvokeContext& invoke_context, std::vector<bool>::const_reference value, TypeList<bool>)
{
return value;
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::vector<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
value.reserve(data.size());
for (auto item : data) {
ReadField(TypeList<LocalType>(), invoke_context, Make<ValueField>(item),
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value.emplace_back(std::forward<decltype(args)>(args)...);
return value.back();
}));
}
});
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::vector<bool>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
value.reserve(data.size());
for (auto item : data) {
value.push_back(ReadField(TypeList<bool>(), invoke_context, Make<ValueField>(item), ReadDestTemp<bool>()));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_VECTOR_H

View file

@ -0,0 +1,23 @@
// Copyright (c) 2025 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 MP_PROXY_TYPE_VOID_H
#define MP_PROXY_TYPE_VOID_H
#include <mp/util.h>
namespace mp {
template <typename Value>
::capnp::Void BuildPrimitive(InvokeContext& invoke_context, Value&&, TypeList<::capnp::Void>)
{
return {};
}
template <typename LocalType, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, ::capnp::Void, Output&& output)
{
}
} // namespace mp
#endif // MP_PROXY_TYPE_VOID_H

View file

@ -0,0 +1,220 @@
// 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 MP_UTIL_H
#define MP_UTIL_H
#include <capnp/schema.h>
#include <cstddef>
#include <functional>
#include <future>
#include <kj/common.h>
#include <kj/exception.h>
#include <kj/string-tree.h>
#include <memory>
#include <string.h>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace mp {
//! Generic utility functions used by capnp code.
//! Type holding a list of types.
//!
//! Example:
//! TypeList<int, bool, void>
template <typename... Types>
struct TypeList
{
static constexpr size_t size = sizeof...(Types);
};
//! Construct a template class value by deducing template arguments from the
//! types of constructor arguments, so they don't need to be specified manually.
//!
//! Uses of this can go away with class template deduction in C++17
//! (https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)
//!
//! Example:
//! Make<std::pair>(5, true) // Constructs std::pair<int, bool>(5, true);
template <template <typename...> class Class, typename... Types, typename... Args>
Class<Types..., typename std::remove_reference<Args>::type...> Make(Args&&... args)
{
return Class<Types..., typename std::remove_reference<Args>::type...>{std::forward<Args>(args)...};
}
//! Type helper splitting a TypeList into two halves at position index.
//!
//! Example:
//! is_same<TypeList<int, double>, Split<2, TypeList<int, double, float, bool>>::First>
//! is_same<TypeList<float, bool>, Split<2, TypeList<int, double, float, bool>>::Second>
template <std::size_t index, typename List, typename _First = TypeList<>, bool done = index == 0>
struct Split;
//! Specialization of above (base case)
template <typename _Second, typename _First>
struct Split<0, _Second, _First, true>
{
using First = _First;
using Second = _Second;
};
//! Specialization of above (recursive case)
template <std::size_t index, typename Type, typename... _Second, typename... _First>
struct Split<index, TypeList<Type, _Second...>, TypeList<_First...>, false>
{
using _Next = Split<index - 1, TypeList<_Second...>, TypeList<_First..., Type>>;
using First = typename _Next::First;
using Second = typename _Next::Second;
};
//! Type helper giving return type of a callable type.
template <typename Callable>
using ResultOf = decltype(std::declval<Callable>()());
//! Substitutue for std::remove_cvref_t
template <typename T>
using RemoveCvRef = std::remove_cv_t<std::remove_reference_t<T>>;
//! Type helper abbreviating std::decay.
template <typename T>
using Decay = typename std::decay<T>::type;
//! SFINAE helper, see using Require below.
template <typename SfinaeExpr, typename Result_>
struct _Require
{
using Result = Result_;
};
//! SFINAE helper, basically the same as to C++17's void_t, but allowing types other than void to be returned.
template <typename SfinaeExpr, typename Result = void>
using Require = typename _Require<SfinaeExpr, Result>::Result;
//! Function parameter type for prioritizing overloaded function calls that
//! would otherwise be ambiguous.
//!
//! Example:
//! auto foo(Priority<1>) -> std::enable_if<>;
//! auto foo(Priority<0>) -> void;
//!
//! foo(Priority<1>()); // Calls higher priority overload if enabled.
template <int priority>
struct Priority : Priority<priority - 1>
{
};
//! Specialization of above (base case)
template <>
struct Priority<0>
{
};
//! Return capnp type name with filename prefix removed.
template <typename T>
const char* TypeName()
{
// DisplayName string looks like
// "interfaces/capnp/common.capnp:ChainNotifications.resendWalletTransactions$Results"
// This discards the part of the string before the first ':' character.
// Another alternative would be to use the displayNamePrefixLength field,
// but this discards everything before the last '.' character, throwing away
// the object name, which is useful.
const char* display_name = ::capnp::Schema::from<T>().getProto().getDisplayName().cStr();
const char* short_name = strchr(display_name, ':');
return short_name ? short_name + 1 : display_name;
}
//! Analog to std::lock_guard that unlocks instead of locks.
template <typename Lock>
struct UnlockGuard
{
UnlockGuard(Lock& lock) : m_lock(lock) { m_lock.unlock(); }
~UnlockGuard() { m_lock.lock(); }
Lock& m_lock;
};
template <typename Lock, typename Callback>
void Unlock(Lock& lock, Callback&& callback)
{
UnlockGuard<Lock> unlock(lock);
callback();
}
//! Needed for libc++/macOS compatibility. Lets code work with shared_ptr nothrow declaration
//! https://github.com/capnproto/capnproto/issues/553#issuecomment-328554603
template <typename T>
struct DestructorCatcher
{
T value;
template <typename... Params>
DestructorCatcher(Params&&... params) : value(kj::fwd<Params>(params)...)
{
}
~DestructorCatcher() noexcept try {
} catch (const kj::Exception& e) {
}
};
//! Wrapper around callback function for compatibility with std::async.
//!
//! std::async requires callbacks to be copyable and requires noexcept
//! destructors, but this doesn't work well with kj types which are generally
//! move-only and not noexcept.
template <typename Callable>
struct AsyncCallable
{
AsyncCallable(Callable&& callable) : m_callable(std::make_shared<DestructorCatcher<Callable>>(std::move(callable)))
{
}
AsyncCallable(const AsyncCallable&) = default;
AsyncCallable(AsyncCallable&&) = default;
~AsyncCallable() noexcept = default;
ResultOf<Callable> operator()() const { return (m_callable->value)(); }
mutable std::shared_ptr<DestructorCatcher<Callable>> m_callable;
};
//! Construct AsyncCallable object.
template <typename Callable>
AsyncCallable<typename std::remove_reference<Callable>::type> MakeAsyncCallable(Callable&& callable)
{
return std::move(callable);
}
//! Format current thread name as "{exe_name}-{$pid}/{thread_name}-{$tid}".
std::string ThreadName(const char* exe_name);
//! Escape binary string for use in log so it doesn't trigger unicode decode
//! errors in python unit tests.
std::string LogEscape(const kj::StringTree& string);
//! Callback type used by SpawnProcess below.
using FdToArgsFn = std::function<std::vector<std::string>(int fd)>;
//! Spawn a new process that communicates with the current process over a socket
//! pair. Returns pid through an output argument, and file descriptor for the
//! local side of the socket. Invokes fd_to_args callback with the remote file
//! descriptor number which returns the command line arguments that should be
//! used to execute the process, and which should have the remote file
//! descriptor embedded in whatever format the child process expects.
int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args);
//! Call execvp with vector args.
void ExecProcess(const std::vector<std::string>& args);
//! Wait for a process to exit and return its exit code.
int WaitProcess(int pid);
inline char* CharCast(char* c) { return c; }
inline char* CharCast(unsigned char* c) { return (char*)c; }
inline const char* CharCast(const char* c) { return c; }
inline const char* CharCast(const unsigned char* c) { return (const char*)c; }
} // namespace mp
#endif // MP_UTIL_H

View file

@ -0,0 +1,4 @@
%.capnp:
@:
%.capnp.c++ %.capnp.h %.capnp.proxy-client.c++ %.capnp.proxy-types.h %.capnp.proxy-server.c++ %.capnp.proxy-types.c++ %.capnp.proxy.h: %.capnp
$(AM_V_GEN) $(MPGEN_PREFIX)/bin/mpgen '$(srcdir)' '$(srcdir)' $<

View file

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
capnp_prefix=@capnp_PREFIX@
Name: libmultiprocess
Description: Multiprocess IPC library
Version: 0.0
Libs: -L${libdir} -lmultiprocess -L${capnp_prefix}/lib -lcapnp-rpc -lcapnp -lkj-async -lkj -pthread -lpthread
Cflags: -I${includedir} -I${capnp_prefix}/include -pthread

View file

@ -0,0 +1,650 @@
// 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 <mp/config.h>
#include <mp/util.h>
#include <algorithm>
#include <capnp/schema-parser.h>
#include <errno.h>
#include <fstream>
#include <map>
#include <set>
#include <sstream>
#include <system_error>
#include <unistd.h>
#include <vector>
#define PROXY_BIN "mpgen"
#define PROXY_DECL "mp/proxy.h"
#define PROXY_TYPES "mp/proxy-types.h"
constexpr uint64_t NAMESPACE_ANNOTATION_ID = 0xb9c6f99ebf805f2cull; // From c++.capnp
constexpr uint64_t INCLUDE_ANNOTATION_ID = 0xb899f3c154fdb458ull; // From proxy.capnp
constexpr uint64_t INCLUDE_TYPES_ANNOTATION_ID = 0xbcec15648e8a0cf1ull; // From proxy.capnp
constexpr uint64_t WRAP_ANNOTATION_ID = 0xe6f46079b7b1405eull; // From proxy.capnp
constexpr uint64_t COUNT_ANNOTATION_ID = 0xd02682b319f69b38ull; // From proxy.capnp
constexpr uint64_t EXCEPTION_ANNOTATION_ID = 0x996a183200992f88ull; // From proxy.capnp
constexpr uint64_t NAME_ANNOTATION_ID = 0xb594888f63f4dbb9ull; // From proxy.capnp
constexpr uint64_t SKIP_ANNOTATION_ID = 0x824c08b82695d8ddull; // From proxy.capnp
template <typename Reader>
static bool AnnotationExists(const Reader& reader, uint64_t id)
{
for (const auto annotation : reader.getAnnotations()) {
if (annotation.getId() == id) {
return true;
}
}
return false;
}
template <typename Reader>
static bool GetAnnotationText(const Reader& reader, uint64_t id, kj::StringPtr* result)
{
for (const auto annotation : reader.getAnnotations()) {
if (annotation.getId() == id) {
*result = annotation.getValue().getText();
return true;
}
}
return false;
}
template <typename Reader>
static bool GetAnnotationInt32(const Reader& reader, uint64_t id, int32_t* result)
{
for (const auto annotation : reader.getAnnotations()) {
if (annotation.getId() == id) {
*result = annotation.getValue().getInt32();
return true;
}
}
return false;
}
void ForEachMethod(const capnp::InterfaceSchema& interface, const std::function<void(const capnp::InterfaceSchema& interface, const capnp::InterfaceSchema::Method)>& callback)
{
for (const auto super : interface.getSuperclasses()) {
ForEachMethod(super, callback);
}
for (const auto method : interface.getMethods()) {
callback(interface, method);
}
}
using CharSlice = kj::ArrayPtr<const char>;
// Overload for any type with a string .begin(), like kj::StringPtr and kj::ArrayPtr<char>.
template <class OutputStream, class Array, const char* Enable = decltype(std::declval<Array>().begin())()>
OutputStream& operator<<(OutputStream& os, const Array& array)
{
os.write(array.begin(), array.size());
return os;
}
struct Format
{
template <typename Value>
Format& operator<<(Value&& value)
{
m_os << value;
return *this;
}
operator std::string() const { return m_os.str(); }
std::ostringstream m_os;
};
std::string Cap(kj::StringPtr str)
{
std::string result = str;
if (!result.empty() && 'a' <= result[0] && result[0] <= 'z') result[0] -= 'a' - 'A';
return result;
}
bool BoxedType(const ::capnp::Type& type)
{
return !(type.isVoid() || type.isBool() || type.isInt8() || type.isInt16() || type.isInt32() || type.isInt64() ||
type.isUInt8() || type.isUInt16() || type.isUInt32() || type.isUInt64() || type.isFloat32() ||
type.isFloat64() || type.isEnum());
}
// src_file is path to .capnp file to generate stub code from.
//
// src_prefix can be used to generate outputs in a different directory than the
// source directory. For example if src_file is "/a/b/c/d/file.canp", and
// src_prefix is "/a/b", then output files will be "c/d/file.capnp.h"
// "c/d/file.capnp.cxx" "c/d/file.capnp.proxy.h", etc. This is equivalent to
// the capnp "--src-prefix" option (see "capnp help compile").
//
// include_prefix can be used to control relative include paths used in
// generated files. For example if src_file is "/a/b/c/d/file.canp" and
// include_prefix is "/a/b/c" include lines like
// "#include <d/file.capnp.proxy.h>" "#include <d/file.capnp.proxy-types.h>"i
// will be generated.
void Generate(kj::StringPtr src_prefix,
kj::StringPtr include_prefix,
kj::StringPtr src_file,
const std::vector<kj::StringPtr>& import_paths,
const kj::ReadableDirectory& src_dir,
const std::vector<kj::Own<const kj::ReadableDirectory>>& import_dirs)
{
std::string output_path;
if (src_prefix == ".") {
output_path = src_file;
} else if (!src_file.startsWith(src_prefix) || src_file.size() <= src_prefix.size() ||
src_file[src_prefix.size()] != '/') {
throw std::runtime_error("src_prefix is not src_file prefix");
} else {
output_path = src_file.slice(src_prefix.size() + 1);
}
std::string include_path;
if (include_prefix == ".") {
include_path = src_file;
} else if (!src_file.startsWith(include_prefix) || src_file.size() <= include_prefix.size() ||
src_file[include_prefix.size()] != '/') {
throw std::runtime_error("include_prefix is not src_file prefix");
} else {
include_path = src_file.slice(include_prefix.size() + 1);
}
std::string include_base = include_path;
std::string::size_type p = include_base.rfind('.');
if (p != std::string::npos) include_base.erase(p);
std::vector<std::string> args;
args.emplace_back(capnp_PREFIX "/bin/capnp");
args.emplace_back("compile");
args.emplace_back("--src-prefix=");
args.back().append(src_prefix.cStr(), src_prefix.size());
for (const auto& import_path : import_paths) {
args.emplace_back("--import-path=");
args.back().append(import_path.cStr(), import_path.size());
}
args.emplace_back("--output=" capnp_PREFIX "/bin/capnpc-c++");
args.emplace_back(src_file);
int pid = fork();
if (pid == -1) {
throw std::system_error(errno, std::system_category(), "fork");
}
if (!pid) {
mp::ExecProcess(args);
}
int status = mp::WaitProcess(pid);
if (status) {
throw std::runtime_error("Invoking " capnp_PREFIX "/bin/capnp failed");
}
capnp::SchemaParser parser;
auto directory_pointers = kj::heapArray<const kj::ReadableDirectory*>(import_dirs.size());
for (size_t i = 0; i < import_dirs.size(); ++i) {
directory_pointers[i] = import_dirs[i].get();
}
auto file_schema = parser.parseFromDirectory(src_dir, kj::Path::parse(output_path), directory_pointers);
std::ofstream cpp_server(output_path + ".proxy-server.c++");
cpp_server << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
cpp_server << "#include <" << include_path << ".proxy-types.h>\n";
cpp_server << "#include <" << PROXY_TYPES << ">\n\n";
cpp_server << "namespace mp {\n";
std::ofstream cpp_client(output_path + ".proxy-client.c++");
cpp_client << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
cpp_client << "#include <" << include_path << ".proxy-types.h>\n";
cpp_client << "#include <" << PROXY_TYPES << ">\n\n";
cpp_client << "namespace mp {\n";
std::ofstream cpp_types(output_path + ".proxy-types.c++");
cpp_types << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
cpp_types << "#include <" << include_path << ".proxy-types.h>\n";
cpp_types << "#include <" << PROXY_TYPES << ">\n\n";
cpp_types << "namespace mp {\n";
std::string guard = output_path;
std::transform(guard.begin(), guard.end(), guard.begin(), [](unsigned char c) {
return ('0' <= c && c <= '9') ? c : ('A' <= c && c <= 'Z') ? c : ('a' <= c && c <= 'z') ? c - 'a' + 'A' : '_';
});
std::ofstream inl(output_path + ".proxy-types.h");
inl << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
inl << "#ifndef " << guard << "_PROXY_TYPES_H\n";
inl << "#define " << guard << "_PROXY_TYPES_H\n\n";
inl << "#include <" << include_path << ".proxy.h>\n";
for (const auto annotation : file_schema.getProto().getAnnotations()) {
if (annotation.getId() == INCLUDE_TYPES_ANNOTATION_ID) {
inl << "#include <" << annotation.getValue().getText() << ">\n";
}
}
inl << "namespace mp {\n";
std::ofstream h(output_path + ".proxy.h");
h << "// Generated by " PROXY_BIN " from " << src_file << "\n\n";
h << "#ifndef " << guard << "_PROXY_H\n";
h << "#define " << guard << "_PROXY_H\n\n";
h << "#include <" << include_path << ".h>\n";
for (const auto annotation : file_schema.getProto().getAnnotations()) {
if (annotation.getId() == INCLUDE_ANNOTATION_ID) {
h << "#include <" << annotation.getValue().getText() << ">\n";
}
}
h << "#include <" << PROXY_DECL << ">\n\n";
h << "#if defined(__GNUC__)\n";
h << "#pragma GCC diagnostic push\n";
h << "#if !defined(__has_warning)\n";
h << "#pragma GCC diagnostic ignored \"-Wsuggest-override\"\n";
h << "#elif __has_warning(\"-Wsuggest-override\")\n";
h << "#pragma GCC diagnostic ignored \"-Wsuggest-override\"\n";
h << "#endif\n";
h << "#endif\n";
h << "namespace mp {\n";
kj::StringPtr message_namespace;
GetAnnotationText(file_schema.getProto(), NAMESPACE_ANNOTATION_ID, &message_namespace);
std::string base_name = include_base;
size_t output_slash = base_name.rfind('/');
if (output_slash != std::string::npos) {
base_name.erase(0, output_slash + 1);
}
std::ostringstream methods;
std::set<kj::StringPtr> accessors_done;
std::ostringstream accessors;
std::ostringstream dec;
std::ostringstream def_server;
std::ostringstream def_client;
std::ostringstream int_client;
std::ostringstream def_types;
auto add_accessor = [&](kj::StringPtr name) {
if (!accessors_done.insert(name).second) return;
std::string cap = Cap(name);
accessors << "struct " << cap << "\n";
accessors << "{\n";
accessors << " template<typename S> static auto get(S&& s) -> decltype(s.get" << cap << "()) { return s.get" << cap << "(); }\n";
accessors << " template<typename S> static bool has(S&& s) { return s.has" << cap << "(); }\n";
accessors << " template<typename S, typename A> static void set(S&& s, A&& a) { s.set" << cap
<< "(std::forward<A>(a)); }\n";
accessors << " template<typename S, typename... A> static decltype(auto) init(S&& s, A&&... a) { return s.init"
<< cap << "(std::forward<A>(a)...); }\n";
accessors << " template<typename S> static bool getWant(S&& s) { return s.getWant" << cap << "(); }\n";
accessors << " template<typename S> static void setWant(S&& s) { s.setWant" << cap << "(true); }\n";
accessors << " template<typename S> static bool getHas(S&& s) { return s.getHas" << cap << "(); }\n";
accessors << " template<typename S> static void setHas(S&& s) { s.setHas" << cap << "(true); }\n";
accessors << "};\n";
};
for (const auto node_nested : file_schema.getProto().getNestedNodes()) {
kj::StringPtr node_name = node_nested.getName();
const auto& node = file_schema.getNested(node_name);
kj::StringPtr proxied_class_type;
GetAnnotationText(node.getProto(), WRAP_ANNOTATION_ID, &proxied_class_type);
if (node.getProto().isStruct()) {
const auto& struc = node.asStruct();
std::ostringstream generic_name;
generic_name << node_name;
dec << "template<";
bool first_param = true;
for (const auto param : node.getProto().getParameters()) {
if (first_param) {
first_param = false;
generic_name << "<";
} else {
dec << ", ";
generic_name << ", ";
}
dec << "typename " << param.getName();
generic_name << "" << param.getName();
}
if (!first_param) generic_name << ">";
dec << ">\n";
dec << "struct ProxyStruct<" << message_namespace << "::" << generic_name.str() << ">\n";
dec << "{\n";
dec << " using Struct = " << message_namespace << "::" << generic_name.str() << ";\n";
for (const auto field : struc.getFields()) {
auto field_name = field.getProto().getName();
add_accessor(field_name);
dec << " using " << Cap(field_name) << "Accessor = Accessor<" << base_name
<< "_fields::" << Cap(field_name) << ", FIELD_IN | FIELD_OUT";
if (BoxedType(field.getType())) dec << " | FIELD_BOXED";
dec << ">;\n";
}
dec << " using Accessors = std::tuple<";
size_t i = 0;
for (const auto field : struc.getFields()) {
if (AnnotationExists(field.getProto(), SKIP_ANNOTATION_ID)) {
continue;
}
if (i) dec << ", ";
dec << Cap(field.getProto().getName()) << "Accessor";
++i;
}
dec << ">;\n";
dec << " static constexpr size_t fields = " << i << ";\n";
dec << "};\n";
if (proxied_class_type.size()) {
inl << "template<>\n";
inl << "struct ProxyType<" << proxied_class_type << ">\n";
inl << "{\n";
inl << "public:\n";
inl << " using Struct = " << message_namespace << "::" << node_name << ";\n";
size_t i = 0;
for (const auto field : struc.getFields()) {
if (AnnotationExists(field.getProto(), SKIP_ANNOTATION_ID)) {
continue;
}
auto field_name = field.getProto().getName();
auto member_name = field_name;
GetAnnotationText(field.getProto(), NAME_ANNOTATION_ID, &member_name);
inl << " static decltype(auto) get(std::integral_constant<size_t, " << i << ">) { return "
<< "&" << proxied_class_type << "::" << member_name << "; }\n";
++i;
}
inl << " static constexpr size_t fields = " << i << ";\n";
inl << "};\n";
}
}
if (proxied_class_type.size() && node.getProto().isInterface()) {
const auto& interface = node.asInterface();
std::ostringstream client;
client << "template<>\nstruct ProxyClient<" << message_namespace << "::" << node_name << "> final : ";
client << "public ProxyClientCustom<" << message_namespace << "::" << node_name << ", "
<< proxied_class_type << ">\n{\n";
client << "public:\n";
client << " using ProxyClientCustom::ProxyClientCustom;\n";
client << " ~ProxyClient();\n";
std::ostringstream server;
server << "template<>\nstruct ProxyServer<" << message_namespace << "::" << node_name << "> : public "
<< "ProxyServerCustom<" << message_namespace << "::" << node_name << ", " << proxied_class_type
<< ">\n{\n";
server << "public:\n";
server << " using ProxyServerCustom::ProxyServerCustom;\n";
server << " ~ProxyServer();\n";
std::ostringstream client_construct;
std::ostringstream client_destroy;
int method_ordinal = 0;
ForEachMethod(interface, [&] (const capnp::InterfaceSchema& method_interface, const capnp::InterfaceSchema::Method& method) {
kj::StringPtr method_name = method.getProto().getName();
kj::StringPtr proxied_method_name = method_name;
GetAnnotationText(method.getProto(), NAME_ANNOTATION_ID, &proxied_method_name);
const std::string method_prefix = Format() << message_namespace << "::" << method_interface.getShortDisplayName()
<< "::" << Cap(method_name);
bool is_construct = method_name == "construct";
bool is_destroy = method_name == "destroy";
struct Field
{
::capnp::StructSchema::Field param;
bool param_is_set = false;
::capnp::StructSchema::Field result;
bool result_is_set = false;
int args = 0;
bool retval = false;
bool optional = false;
bool requested = false;
bool skip = false;
kj::StringPtr exception;
};
std::vector<Field> fields;
std::map<kj::StringPtr, int> field_idx; // name -> args index
bool has_result = false;
auto add_field = [&](const ::capnp::StructSchema::Field& schema_field, bool param) {
if (AnnotationExists(schema_field.getProto(), SKIP_ANNOTATION_ID)) {
return;
}
auto field_name = schema_field.getProto().getName();
auto inserted = field_idx.emplace(field_name, fields.size());
if (inserted.second) {
fields.emplace_back();
}
auto& field = fields[inserted.first->second];
if (param) {
field.param = schema_field;
field.param_is_set = true;
} else {
field.result = schema_field;
field.result_is_set = true;
}
if (!param && field_name == "result") {
field.retval = true;
has_result = true;
}
GetAnnotationText(schema_field.getProto(), EXCEPTION_ANNOTATION_ID, &field.exception);
int32_t count = 1;
if (!GetAnnotationInt32(schema_field.getProto(), COUNT_ANNOTATION_ID, &count)) {
if (schema_field.getType().isStruct()) {
GetAnnotationInt32(schema_field.getType().asStruct().getProto(),
COUNT_ANNOTATION_ID, &count);
} else if (schema_field.getType().isInterface()) {
GetAnnotationInt32(schema_field.getType().asInterface().getProto(),
COUNT_ANNOTATION_ID, &count);
}
}
if (inserted.second && !field.retval && !field.exception.size()) {
field.args = count;
}
};
for (const auto schema_field : method.getParamType().getFields()) {
add_field(schema_field, true);
}
for (const auto schema_field : method.getResultType().getFields()) {
add_field(schema_field, false);
}
for (auto& field : field_idx) {
auto has_field = field_idx.find("has" + Cap(field.first));
if (has_field != field_idx.end()) {
fields[has_field->second].skip = true;
fields[field.second].optional = true;
}
auto want_field = field_idx.find("want" + Cap(field.first));
if (want_field != field_idx.end() && fields[want_field->second].param_is_set) {
fields[want_field->second].skip = true;
fields[field.second].requested = true;
}
}
if (!is_construct && !is_destroy && (&method_interface == &interface)) {
methods << "template<>\n";
methods << "struct ProxyMethod<" << method_prefix << "Params>\n";
methods << "{\n";
methods << " static constexpr auto impl = &" << proxied_class_type
<< "::" << proxied_method_name << ";\n";
methods << "};\n\n";
}
std::ostringstream client_args;
std::ostringstream client_invoke;
std::ostringstream server_invoke_start;
std::ostringstream server_invoke_end;
int argc = 0;
for (const auto& field : fields) {
if (field.skip) continue;
const auto& f = field.param_is_set ? field.param : field.result;
auto field_name = f.getProto().getName();
auto field_type = f.getType();
std::ostringstream field_flags;
field_flags << (!field.param_is_set ? "FIELD_OUT" : field.result_is_set ? "FIELD_IN | FIELD_OUT" : "FIELD_IN");
if (field.optional) field_flags << " | FIELD_OPTIONAL";
if (field.requested) field_flags << " | FIELD_REQUESTED";
if (BoxedType(field_type)) field_flags << " | FIELD_BOXED";
add_accessor(field_name);
for (int i = 0; i < field.args; ++i) {
if (argc > 0) client_args << ",";
client_args << "M" << method_ordinal << "::Param<" << argc << "> " << field_name;
if (field.args > 1) client_args << i;
++argc;
}
client_invoke << ", ";
if (field.exception.size()) {
client_invoke << "ClientException<" << field.exception << ", ";
} else {
client_invoke << "MakeClientParam<";
}
client_invoke << "Accessor<" << base_name << "_fields::" << Cap(field_name) << ", "
<< field_flags.str() << ">>(";
if (field.retval || field.args == 1) {
client_invoke << field_name;
} else {
for (int i = 0; i < field.args; ++i) {
if (i > 0) client_invoke << ", ";
client_invoke << field_name << i;
}
}
client_invoke << ")";
if (field.exception.size()) {
server_invoke_start << "Make<ServerExcept, " << field.exception;
} else if (field.retval) {
server_invoke_start << "Make<ServerRet";
} else {
server_invoke_start << "MakeServerField<" << field.args;
}
server_invoke_start << ", Accessor<" << base_name << "_fields::" << Cap(field_name) << ", "
<< field_flags.str() << ">>(";
server_invoke_end << ")";
}
std::string static_str{is_construct || is_destroy ? "static " : ""};
std::string super_str{is_construct || is_destroy ? "Super& super" : ""};
std::string self_str{is_construct || is_destroy ? "super" : "*this"};
client << " using M" << method_ordinal << " = ProxyClientMethodTraits<" << method_prefix
<< "Params>;\n";
client << " " << static_str << "typename M" << method_ordinal << "::Result " << method_name << "("
<< super_str << client_args.str() << ")";
client << ";\n";
def_client << "ProxyClient<" << message_namespace << "::" << node_name << ">::M" << method_ordinal
<< "::Result ProxyClient<" << message_namespace << "::" << node_name << ">::" << method_name
<< "(" << super_str << client_args.str() << ") {\n";
if (has_result) {
def_client << " typename M" << method_ordinal << "::Result result;\n";
}
def_client << " clientInvoke(" << self_str << ", &" << message_namespace << "::" << node_name
<< "::Client::" << method_name << "Request" << client_invoke.str() << ");\n";
if (has_result) def_client << " return result;\n";
def_client << "}\n";
server << " kj::Promise<void> " << method_name << "(" << Cap(method_name)
<< "Context call_context) override;\n";
def_server << "kj::Promise<void> ProxyServer<" << message_namespace << "::" << node_name
<< ">::" << method_name << "(" << Cap(method_name)
<< "Context call_context) {\n"
" return serverInvoke(*this, call_context, "
<< server_invoke_start.str();
if (is_destroy) {
def_server << "ServerDestroy()";
} else {
def_server << "ServerCall()";
}
def_server << server_invoke_end.str() << ");\n}\n";
++method_ordinal;
});
client << "};\n";
server << "};\n";
dec << "\n" << client.str() << "\n" << server.str() << "\n";
KJ_IF_MAYBE(bracket, proxied_class_type.findFirst('<')) {
// Skip ProxyType definition for complex type expressions which
// could lead to duplicate definitions. They can be defined
// manually if actually needed.
} else {
dec << "template<>\nstruct ProxyType<" << proxied_class_type << ">\n{\n";
dec << " using Type = " << proxied_class_type << ";\n";
dec << " using Message = " << message_namespace << "::" << node_name << ";\n";
dec << " using Client = ProxyClient<Message>;\n";
dec << " using Server = ProxyServer<Message>;\n";
dec << "};\n";
int_client << "ProxyTypeRegister t" << node_nested.getId() << "{TypeList<" << proxied_class_type << ">{}};\n";
}
def_types << "ProxyClient<" << message_namespace << "::" << node_name
<< ">::~ProxyClient() { clientDestroy(*this); " << client_destroy.str() << " }\n";
def_types << "ProxyServer<" << message_namespace << "::" << node_name
<< ">::~ProxyServer() { serverDestroy(*this); }\n";
}
}
h << methods.str() << "namespace " << base_name << "_fields {\n"
<< accessors.str() << "} // namespace " << base_name << "_fields\n"
<< dec.str();
cpp_server << def_server.str();
cpp_server << "} // namespace mp\n";
cpp_client << def_client.str();
cpp_client << "namespace {\n" << int_client.str() << "} // namespace\n";
cpp_client << "} // namespace mp\n";
cpp_types << def_types.str();
cpp_types << "} // namespace mp\n";
inl << "} // namespace mp\n";
inl << "#endif\n";
h << "} // namespace mp\n";
h << "#if defined(__GNUC__)\n";
h << "#pragma GCC diagnostic pop\n";
h << "#endif\n";
h << "#endif\n";
}
int main(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "Usage: " PROXY_BIN " SRC_PREFIX INCLUDE_PREFIX SRC_FILE [IMPORT_PATH...]\n");
exit(1);
}
std::vector<kj::StringPtr> import_paths;
std::vector<kj::Own<const kj::ReadableDirectory>> import_dirs;
auto fs = kj::newDiskFilesystem();
auto cwd = fs->getCurrentPath();
kj::Own<const kj::ReadableDirectory> src_dir;
KJ_IF_MAYBE(dir, fs->getRoot().tryOpenSubdir(cwd.evalNative(argv[1]))) {
src_dir = kj::mv(*dir);
} else {
throw std::runtime_error(std::string("Failed to open src_prefix prefix directory: ") + argv[1]);
}
for (size_t i = 4; i < argc; ++i) {
KJ_IF_MAYBE(dir, fs->getRoot().tryOpenSubdir(cwd.evalNative(argv[i]))) {
import_paths.emplace_back(argv[i]);
import_dirs.emplace_back(kj::mv(*dir));
} else {
throw std::runtime_error(std::string("Failed to open import directory: ") + argv[i]);
}
}
for (const char* path : {CMAKE_INSTALL_PREFIX "/include", capnp_PREFIX "/include"}) {
KJ_IF_MAYBE(dir, fs->getRoot().tryOpenSubdir(cwd.evalNative(path))) {
import_paths.emplace_back(path);
import_dirs.emplace_back(kj::mv(*dir));
}
// No exception thrown if _PREFIX directories do not exist
}
Generate(argv[1], argv[2], argv[3], import_paths, *src_dir, import_dirs);
return 0;
}

View file

@ -0,0 +1,394 @@
// 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 <mp/proxy.h>
#include <mp/proxy-io.h>
#include <mp/proxy-types.h>
#include <mp/proxy.capnp.h>
#include <mp/type-threadmap.h>
#include <mp/util.h>
#include <assert.h>
#include <atomic>
#include <capnp/blob.h>
#include <capnp/capability.h>
#include <condition_variable>
#include <functional>
#include <future>
#include <kj/async-io.h>
#include <kj/async.h>
#include <kj/common.h>
#include <kj/debug.h>
#include <kj/exception.h>
#include <kj/memory.h>
#include <map>
#include <memory>
#include <mutex>
#include <stddef.h>
#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <utility>
namespace mp {
template <typename Interface>
struct ProxyServer;
thread_local ThreadContext g_thread_context;
void LoggingErrorHandler::taskFailed(kj::Exception&& exception)
{
KJ_LOG(ERROR, "Uncaught exception in daemonized task.", exception);
m_loop.log() << "Uncaught exception in daemonized task.";
}
Connection::~Connection()
{
// Shut down RPC system first, since this will garbage collect Server
// objects that were not freed before the connection was closed, some of
// which may call addAsyncCleanup and add more cleanup callbacks which can
// run below.
m_rpc_system.reset();
// ProxyClient cleanup handlers are in sync list, and ProxyServer cleanup
// handlers are in the async list.
//
// The ProxyClient cleanup handlers are synchronous because they are fast
// and don't do anything besides release capnp resources and reset state so
// future calls to client methods immediately throw exceptions instead of
// trying to communicating across the socket. The synchronous callbacks set
// ProxyClient capability pointers to null, so new method calls on client
// objects fail without triggering i/o or relying on event loop which may go
// out of scope or trigger obscure capnp i/o errors.
//
// The ProxySever cleanup handlers call user defined destructors on server
// object, which can run arbitrary blocking bitcoin code so they have to run
// asynchronously in a different thread. The asynchronous cleanup functions
// intentionally aren't started until after the synchronous cleanup
// functions run, so client objects are fully disconnected before bitcoin
// code in the destructors are run. This way if the bitcoin code tries to
// make client requests the requests will just fail immediately instead of
// sending i/o or accessing the event loop.
//
// The context where Connection objects are destroyed and this destructor is invoked
// is different depending on whether this is an outgoing connection being used
// to make an Init.makeX call() (e.g. Init.makeNode or Init.makeWalletClient) or an incoming
// connection implementing the Init interface and handling the Init.makeX() calls.
//
// Either way when a connection is closed, capnp behavior is to call all
// ProxyServer object destructors first, and then trigger an onDisconnect
// callback.
//
// On incoming side of the connection, the onDisconnect callback is written
// to delete the Connection object from the m_incoming_connections and call
// this destructor which calls Connection::disconnect.
//
// On the outgoing side, the Connection object is owned by top level client
// object client, which onDisconnect handler doesn't have ready access to,
// so onDisconnect handler just calls Connection::disconnect directly
// instead.
//
// Either way disconnect code runs in the event loop thread and called both
// on clean and unclean shutdowns. In unclean shutdown case when the
// connection is broken, sync and async cleanup lists will filled with
// callbacks. In the clean shutdown case both lists will be empty.
while (!m_sync_cleanup_fns.empty()) {
m_sync_cleanup_fns.front()();
m_sync_cleanup_fns.pop_front();
}
while (!m_async_cleanup_fns.empty()) {
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.m_async_fns.emplace_back(std::move(m_async_cleanup_fns.front()));
m_async_cleanup_fns.pop_front();
}
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.startAsyncThread(lock);
m_loop.removeClient(lock);
}
CleanupIt Connection::addSyncCleanup(std::function<void()> fn)
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
// Add cleanup callbacks to the front of list, so sync cleanup functions run
// in LIFO order. This is a good approach because sync cleanup functions are
// added as client objects are created, and it is natural to clean up
// objects in the reverse order they were created. In practice, however,
// order should not be significant because the cleanup callbacks run
// synchronously in a single batch when the connection is broken, and they
// only reset the connection pointers in the client objects without actually
// deleting the client objects.
return m_sync_cleanup_fns.emplace(m_sync_cleanup_fns.begin(), std::move(fn));
}
void Connection::removeSyncCleanup(CleanupIt it)
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_sync_cleanup_fns.erase(it);
}
void Connection::addAsyncCleanup(std::function<void()> fn)
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
// Add async cleanup callbacks to the back of the list. Unlike the sync
// cleanup list, this list order is more significant because it determines
// the order server objects are destroyed when there is a sudden disconnect,
// and it is possible objects may need to be destroyed in a certain order.
// This function is called in ProxyServerBase destructors, and since capnp
// destroys ProxyServer objects in LIFO order, we should preserve this
// order, and add cleanup callbacks to the end of the list so they can be
// run starting from the beginning of the list.
//
// In bitcoin core, running these callbacks in the right order is
// particularly important for the wallet process, because it uses blocking
// shared_ptrs and requires Chain::Notification pointers owned by the node
// process to be destroyed before the WalletLoader objects owned by the node
// process, otherwise shared pointer counts of the CWallet objects (which
// inherit from Chain::Notification) will not be 1 when WalletLoader
// destructor runs and it will wait forever for them to be released.
m_async_cleanup_fns.emplace(m_async_cleanup_fns.end(), std::move(fn));
}
EventLoop::EventLoop(const char* exe_name, LogFn log_fn, void* context)
: m_exe_name(exe_name),
m_io_context(kj::setupAsyncIo()),
m_log_fn(std::move(log_fn)),
m_context(context),
m_task_set(new kj::TaskSet(m_error_handler))
{
int fds[2];
KJ_SYSCALL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
m_wait_fd = fds[0];
m_post_fd = fds[1];
}
EventLoop::~EventLoop()
{
if (m_async_thread.joinable()) m_async_thread.join();
std::lock_guard<std::mutex> lock(m_mutex);
KJ_ASSERT(m_post_fn == nullptr);
KJ_ASSERT(m_async_fns.empty());
KJ_ASSERT(m_wait_fd == -1);
KJ_ASSERT(m_post_fd == -1);
KJ_ASSERT(m_num_clients == 0);
// Spin event loop. wait for any promises triggered by RPC shutdown.
// auto cleanup = kj::evalLater([]{});
// cleanup.wait(m_io_context.waitScope);
}
void EventLoop::loop()
{
assert(!g_thread_context.loop_thread);
g_thread_context.loop_thread = true;
KJ_DEFER(g_thread_context.loop_thread = false);
kj::Own<kj::AsyncIoStream> wait_stream{
m_io_context.lowLevelProvider->wrapSocketFd(m_wait_fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP)};
int post_fd{m_post_fd};
char buffer = 0;
for (;;) {
size_t read_bytes = wait_stream->read(&buffer, 0, 1).wait(m_io_context.waitScope);
if (read_bytes != 1) throw std::logic_error("EventLoop wait_stream closed unexpectedly");
std::unique_lock<std::mutex> lock(m_mutex);
if (m_post_fn) {
Unlock(lock, *m_post_fn);
m_post_fn = nullptr;
m_cv.notify_all();
} else if (done(lock)) {
// Intentionally do not break if m_post_fn was set, even if done()
// would return true, to ensure that the removeClient write(post_fd)
// call always succeeds and the loop does not exit between the time
// that the done condition is set and the write call is made.
break;
}
}
log() << "EventLoop::loop done, cancelling event listeners.";
m_task_set.reset();
log() << "EventLoop::loop bye.";
wait_stream = nullptr;
KJ_SYSCALL(::close(post_fd));
std::unique_lock<std::mutex> lock(m_mutex);
m_wait_fd = -1;
m_post_fd = -1;
}
void EventLoop::post(const std::function<void()>& fn)
{
if (std::this_thread::get_id() == m_thread_id) {
return fn();
}
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [this] { return m_post_fn == nullptr; });
m_post_fn = &fn;
int post_fd{m_post_fd};
Unlock(lock, [&] {
char buffer = 0;
KJ_SYSCALL(write(post_fd, &buffer, 1));
});
m_cv.wait(lock, [this, &fn] { return m_post_fn != &fn; });
}
void EventLoop::addClient(std::unique_lock<std::mutex>& lock) { m_num_clients += 1; }
void EventLoop::removeClient(std::unique_lock<std::mutex>& lock)
{
m_num_clients -= 1;
if (done(lock)) {
m_cv.notify_all();
int post_fd{m_post_fd};
Unlock(lock, [&] {
char buffer = 0;
KJ_SYSCALL(write(post_fd, &buffer, 1)); // NOLINT(bugprone-suspicious-semicolon)
});
}
}
void EventLoop::startAsyncThread(std::unique_lock<std::mutex>& lock)
{
if (m_async_thread.joinable()) {
m_cv.notify_all();
} else if (!m_async_fns.empty()) {
m_async_thread = std::thread([this] {
std::unique_lock<std::mutex> lock(m_mutex);
while (true) {
if (!m_async_fns.empty()) {
addClient(lock);
std::function<void()> fn = std::move(m_async_fns.front());
m_async_fns.pop_front();
Unlock(lock, fn);
removeClient(lock);
continue;
} else if (m_num_clients == 0) {
break;
}
m_cv.wait(lock);
}
});
}
}
bool EventLoop::done(std::unique_lock<std::mutex>& lock)
{
assert(m_num_clients >= 0);
assert(lock.owns_lock());
assert(lock.mutex() == &m_mutex);
return m_num_clients == 0 && m_async_fns.empty();
}
std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex, Connection* connection, std::function<Thread::Client()> make_thread)
{
std::unique_lock<std::mutex> lock(mutex);
auto thread = threads.find(connection);
if (thread != threads.end()) return {thread, false};
thread = threads.emplace(
std::piecewise_construct, std::forward_as_tuple(connection),
std::forward_as_tuple(make_thread(), connection, /* destroy_connection= */ false)).first;
thread->second.setCleanup([&threads, &mutex, thread] {
// Note: it is safe to use the `thread` iterator in this cleanup
// function, because the iterator would only be invalid if the map entry
// was removed, and if the map entry is removed the ProxyClient<Thread>
// destructor unregisters the cleanup.
// Connection is being destroyed before thread client is, so reset
// thread client m_cleanup_it member so thread client destructor does not
// try unregister this callback after connection is destroyed.
thread->second.m_cleanup_it.reset();
// Remove connection pointer about to be destroyed from the map
std::unique_lock<std::mutex> lock(mutex);
threads.erase(thread);
});
return {thread, true};
}
ProxyClient<Thread>::~ProxyClient()
{
// If thread is being destroyed before connection is destroyed, remove the
// cleanup callback that was registered to handle the connection being
// destroyed before the thread being destroyed.
if (m_cleanup_it) {
m_context.connection->removeSyncCleanup(*m_cleanup_it);
}
}
void ProxyClient<Thread>::setCleanup(std::function<void()> fn)
{
assert(fn);
assert(!m_cleanup_it);
m_cleanup_it = m_context.connection->addSyncCleanup(fn);
}
ProxyServer<Thread>::ProxyServer(ThreadContext& thread_context, std::thread&& thread)
: m_thread_context(thread_context), m_thread(std::move(thread))
{
assert(m_thread_context.waiter.get() != nullptr);
}
ProxyServer<Thread>::~ProxyServer()
{
if (!m_thread.joinable()) return;
// Stop async thread and wait for it to exit. Need to wait because the
// m_thread handle needs to outlive the thread to avoid "terminate called
// without an active exception" error. An alternative to waiting would be
// detach the thread, but this would introduce nondeterminism which could
// make code harder to debug or extend.
assert(m_thread_context.waiter.get());
std::unique_ptr<Waiter> waiter;
{
std::unique_lock<std::mutex> lock(m_thread_context.waiter->m_mutex);
//! Reset thread context waiter pointer, as shutdown signal for done
//! lambda passed as waiter->wait() argument in makeThread code below.
waiter = std::move(m_thread_context.waiter);
//! Assert waiter is idle. This destructor shouldn't be getting called if it is busy.
assert(!waiter->m_fn);
// Clear client maps now to avoid deadlock in m_thread.join() call
// below. The maps contain Thread::Client objects that need to be
// destroyed from the event loop thread (this thread), which can't
// happen if this thread is busy calling join.
m_thread_context.request_threads.clear();
m_thread_context.callback_threads.clear();
//! Ping waiter.
waiter->m_cv.notify_all();
}
m_thread.join();
}
kj::Promise<void> ProxyServer<Thread>::getName(GetNameContext context)
{
context.getResults().setResult(m_thread_context.thread_name);
return kj::READY_NOW;
}
ProxyServer<ThreadMap>::ProxyServer(Connection& connection) : m_connection(connection) {}
kj::Promise<void> ProxyServer<ThreadMap>::makeThread(MakeThreadContext context)
{
std::string from = context.getParams().getName();
std::promise<ThreadContext*> thread_context;
std::thread thread([&thread_context, from, this]() {
g_thread_context.thread_name = ThreadName(m_connection.m_loop.m_exe_name) + " (from " + from + ")";
g_thread_context.waiter = std::make_unique<Waiter>();
thread_context.set_value(&g_thread_context);
std::unique_lock<std::mutex> lock(g_thread_context.waiter->m_mutex);
// Wait for shutdown signal from ProxyServer<Thread> destructor (signal
// is just waiter getting set to null.)
g_thread_context.waiter->wait(lock, [] { return !g_thread_context.waiter; });
});
auto thread_server = kj::heap<ProxyServer<Thread>>(*thread_context.get_future().get(), std::move(thread));
auto thread_client = m_connection.m_threads.add(kj::mv(thread_server));
context.getResults().setResult(kj::mv(thread_client));
return kj::READY_NOW;
}
std::atomic<int> server_reqs{0};
std::string LongThreadName(const char* exe_name)
{
return g_thread_context.thread_name.empty() ? ThreadName(exe_name) : g_thread_context.thread_name;
}
} // namespace mp

View file

@ -0,0 +1,153 @@
// Copyright (c) 2018-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 <mp/config.h>
#include <mp/util.h>
#include <errno.h>
#include <kj/array.h>
#include <pthread.h>
#include <sstream>
#include <stdio.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <system_error>
#include <thread>
#include <unistd.h>
#if __linux__
#include <syscall.h>
#endif
#ifdef HAVE_PTHREAD_GETTHREADID_NP
#include <pthread_np.h>
#endif // HAVE_PTHREAD_GETTHREADID_NP
namespace mp {
namespace {
//! Return highest possible file descriptor.
size_t MaxFd()
{
struct rlimit nofile;
if (getrlimit(RLIMIT_NOFILE, &nofile) == 0) {
return nofile.rlim_cur - 1;
} else {
return 1023;
}
}
} // namespace
std::string ThreadName(const char* exe_name)
{
char thread_name[16] = {0};
#ifdef HAVE_PTHREAD_GETNAME_NP
pthread_getname_np(pthread_self(), thread_name, sizeof(thread_name));
#endif // HAVE_PTHREAD_GETNAME_NP
std::ostringstream buffer;
buffer << (exe_name ? exe_name : "") << "-" << getpid() << "/";
if (thread_name[0] != '\0') {
buffer << thread_name << "-";
}
// Prefer platform specific thread ids over the standard C++11 ones because
// the former are shorter and are the same as what gdb prints "LWP ...".
#if __linux__
buffer << syscall(SYS_gettid);
#elif defined(HAVE_PTHREAD_THREADID_NP)
uint64_t tid = 0;
pthread_threadid_np(NULL, &tid);
buffer << tid;
#elif defined(HAVE_PTHREAD_GETTHREADID_NP)
buffer << pthread_getthreadid_np();
#else
buffer << std::this_thread::get_id();
#endif
return std::move(buffer.str());
}
std::string LogEscape(const kj::StringTree& string)
{
const int MAX_SIZE = 1000;
std::string result;
string.visit([&](const kj::ArrayPtr<const char>& piece) {
if (result.size() > MAX_SIZE) return;
for (char c : piece) {
if (c == '\\') {
result.append("\\\\");
} else if (c < 0x20 || c > 0x7e) {
char escape[4];
snprintf(escape, 4, "\\%02x", c);
result.append(escape);
} else {
result.push_back(c);
}
if (result.size() > MAX_SIZE) {
result += "...";
break;
}
}
});
return result;
}
int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args)
{
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
throw std::system_error(errno, std::system_category(), "socketpair");
}
pid = fork();
if (pid == -1) {
throw std::system_error(errno, std::system_category(), "fork");
}
// Parent process closes the descriptor for socket 0, child closes the descriptor for socket 1.
if (close(fds[pid ? 0 : 1]) != 0) {
throw std::system_error(errno, std::system_category(), "close");
}
if (!pid) {
// Child process must close all potentially open descriptors, except socket 0.
int maxFd = MaxFd();
for (int fd = 3; fd < maxFd; ++fd) {
if (fd != fds[0]) {
close(fd);
}
}
ExecProcess(fd_to_args(fds[0]));
}
return fds[1];
}
void ExecProcess(const std::vector<std::string>& args)
{
std::vector<char*> argv;
argv.reserve(args.size());
for (const auto& arg : args) {
argv.push_back(const_cast<char*>(arg.c_str()));
}
argv.push_back(nullptr);
if (execvp(argv[0], argv.data()) != 0) {
perror("execlp failed");
_exit(1);
}
}
int WaitProcess(int pid)
{
int status;
if (::waitpid(pid, &status, 0 /* options */) != pid) {
throw std::system_error(errno, std::system_category(), "waitpid");
}
return status;
}
} // namespace mp

View file

@ -0,0 +1,41 @@
# Copyright (c) 2020 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(CTest)
# Custom test targets for convenience, based on
# https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/EmulateMakeCheck.
#
# CTest already provides a "make test" target, but it just runs existing tests
# that were previously built, without building anything itself. Define "make
# tests" here as a custom target to build all available tests and "make check"
# as a custom target to build and run them.
add_custom_target(mptests)
add_custom_target(mpcheck COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS mptests)
# Only add more convenient tests and check targets if project is being built
# standlone, to prevent clashes with external projects.
if (MP_STANDALONE)
add_custom_target(tests DEPENDS mptests)
add_custom_target(check DEPENDS mpcheck)
endif()
if(BUILD_TESTING AND TARGET CapnProto::kj-test)
set_property(SOURCE ${MP_PROXY_HDRS} PROPERTY GENERATED 1)
add_executable(mptest
${MP_PROXY_HDRS}
mp/test/foo-types.h
mp/test/foo.h
mp/test/test.cpp
)
include(${PROJECT_SOURCE_DIR}/cmake/TargetCapnpSources.cmake)
target_capnp_sources(mptest ${CMAKE_CURRENT_SOURCE_DIR} mp/test/foo.capnp)
target_include_directories(mptest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mptest PRIVATE CapnProto::kj-test)
target_link_libraries(mptest PRIVATE Threads::Threads)
add_dependencies(mptests mptest)
add_test(NAME mptest COMMAND mptest)
endif()

View file

@ -0,0 +1,83 @@
// 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 MP_TEST_FOO_TYPES_H
#define MP_TEST_FOO_TYPES_H
#include <mp/proxy-types.h>
#include <mp/type-context.h>
#include <mp/type-decay.h>
#include <mp/type-interface.h>
#include <mp/type-map.h>
#include <mp/type-message.h>
#include <mp/type-number.h>
#include <mp/type-set.h>
#include <mp/type-string.h>
#include <mp/type-struct.h>
#include <mp/type-threadmap.h>
#include <mp/type-vector.h>
namespace mp {
namespace test {
template <typename Output>
void CustomBuildField(TypeList<FooCustom>, Priority<1>, InvokeContext& invoke_context, const FooCustom& value, Output&& output)
{
BuildField(TypeList<std::string>(), invoke_context, output, value.v1);
output.setV2(value.v2);
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<FooCustom>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
{
messages::FooCustom::Reader custom = input.get();
return read_dest.update([&](FooCustom& value) {
value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
value.v2 = custom.getV2();
});
}
} // namespace test
inline void CustomBuildMessage(InvokeContext& invoke_context,
const test::FooMessage& src,
test::messages::FooMessage::Builder&& builder)
{
builder.setMessage(src.message + " build");
}
inline void CustomReadMessage(InvokeContext& invoke_context,
const test::messages::FooMessage::Reader& reader,
test::FooMessage& dest)
{
dest.message = std::string{reader.getMessage()} + " read";
}
inline void CustomBuildMessage(InvokeContext& invoke_context,
const test::FooMutable& src,
test::messages::FooMutable::Builder&& builder)
{
builder.setMessage(src.message + " build");
}
inline void CustomReadMessage(InvokeContext& invoke_context,
const test::messages::FooMutable::Reader& reader,
test::FooMutable& dest)
{
dest.message = std::string{reader.getMessage()} + " read";
}
inline void CustomPassMessage(InvokeContext& invoke_context,
const test::messages::FooMutable::Reader& reader,
test::messages::FooMutable::Builder builder,
std::function<void(test::FooMutable&)>&& fn)
{
test::FooMutable mut;
mut.message = std::string{reader.getMessage()} + " pass";
fn(mut);
builder.setMessage(mut.message + " return");
}
} // namespace mp
#endif // MP_TEST_FOO_TYPES_H

View file

@ -0,0 +1,67 @@
# 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.
@0xe102a54b33a43a20;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("mp::test::messages");
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("mp/test/foo.h");
$Proxy.includeTypes("mp/test/foo-types.h");
interface FooInterface $Proxy.wrap("mp::test::FooImplementation") {
add @0 (a :Int32, b :Int32) -> (result :Int32);
mapSize @1 (map :List(Pair(Text, Text))) -> (result :Int32);
pass @2 (arg :FooStruct) -> (result :FooStruct);
raise @3 (arg :FooStruct) -> (error :FooStruct $Proxy.exception("mp::test::FooStruct"));
initThreadMap @4 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
callback @5 (context :Proxy.Context, callback :FooCallback, arg: Int32) -> (result :Int32);
callbackUnique @6 (context :Proxy.Context, callback :FooCallback, arg: Int32) -> (result :Int32);
callbackShared @7 (context :Proxy.Context, callback :FooCallback, arg: Int32) -> (result :Int32);
saveCallback @8 (context :Proxy.Context, callback :FooCallback) -> ();
callbackSaved @9 (context :Proxy.Context, arg: Int32) -> (result :Int32);
callbackExtended @10 (context :Proxy.Context, callback :ExtendedCallback, arg: Int32) -> (result :Int32);
passCustom @11 (arg :FooCustom) -> (result :FooCustom);
passEmpty @12 (arg :FooEmpty) -> (result :FooEmpty);
passMessage @13 (arg :FooMessage) -> (result :FooMessage);
passMutable @14 (arg :FooMutable) -> (arg :FooMutable);
passEnum @15 (arg :Int32) -> (result :Int32);
}
interface FooCallback $Proxy.wrap("mp::test::FooCallback") {
destroy @0 (context :Proxy.Context) -> ();
call @1 (context :Proxy.Context, arg :Int32) -> (result :Int32);
}
interface ExtendedCallback extends(FooCallback) $Proxy.wrap("mp::test::ExtendedCallback") {
callExtended @0 (context :Proxy.Context, arg :Int32) -> (result :Int32);
}
struct FooStruct $Proxy.wrap("mp::test::FooStruct") {
name @0 :Text;
setint @1 :List(Int32);
vbool @2 :List(Bool);
}
struct FooCustom $Proxy.wrap("mp::test::FooCustom") {
v1 @0 :Text;
v2 @1 :Int32;
}
struct FooEmpty $Proxy.wrap("mp::test::FooEmpty") {
}
struct FooMessage {
message @0 :Text;
}
struct FooMutable {
message @0 :Text;
}
struct Pair(T1, T2) {
first @0 :T1;
second @1 :T2;
}

View file

@ -0,0 +1,84 @@
// 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 MP_TEST_FOO_H
#define MP_TEST_FOO_H
#include <map>
#include <memory>
#include <string>
#include <set>
#include <vector>
namespace mp {
namespace test {
struct FooStruct
{
std::string name;
std::set<int> setint;
std::vector<bool> vbool;
};
enum class FooEnum : int { ONE = 1, TWO = 2, };
struct FooCustom
{
std::string v1;
int v2;
};
struct FooEmpty
{
};
struct FooMessage
{
std::string message;
};
struct FooMutable
{
std::string message;
};
class FooCallback
{
public:
virtual ~FooCallback() = default;
virtual int call(int arg) = 0;
};
class ExtendedCallback : public FooCallback
{
public:
virtual int callExtended(int arg) = 0;
};
class FooImplementation
{
public:
int add(int a, int b) { return a + b; }
int mapSize(const std::map<std::string, std::string>& map) { return map.size(); }
FooStruct pass(FooStruct foo) { return foo; }
void raise(FooStruct foo) { throw foo; }
void initThreadMap() {}
int callback(FooCallback& callback, int arg) { return callback.call(arg); }
int callbackUnique(std::unique_ptr<FooCallback> callback, int arg) { return callback->call(arg); }
int callbackShared(std::shared_ptr<FooCallback> callback, int arg) { return callback->call(arg); } // NOLINT(performance-unnecessary-value-param)
void saveCallback(std::shared_ptr<FooCallback> callback) { m_callback = std::move(callback); }
int callbackSaved(int arg) { return m_callback->call(arg); }
int callbackExtended(ExtendedCallback& callback, int arg) { return callback.callExtended(arg); }
FooCustom passCustom(FooCustom foo) { return foo; }
FooEmpty passEmpty(FooEmpty foo) { return foo; }
FooMessage passMessage(FooMessage foo) { foo.message += " call"; return foo; }
void passMutable(FooMutable& foo) { foo.message += " call"; }
FooEnum passEnum(FooEnum foo) { return foo; }
std::shared_ptr<FooCallback> m_callback;
};
} // namespace test
} // namespace mp
#endif // MP_TEST_FOO_H

View file

@ -0,0 +1,131 @@
// 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 <mp/proxy-types.h>
#include <mp/test/foo.capnp.h>
#include <mp/test/foo.capnp.proxy.h>
#include <mp/test/foo.h>
#include <future>
#include <kj/common.h>
#include <kj/memory.h>
#include <kj/test.h>
namespace mp {
namespace test {
KJ_TEST("Call FooInterface methods")
{
std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> foo_promise;
std::function<void()> disconnect_client;
std::thread thread([&]() {
EventLoop loop("mptest", [](bool raise, const std::string& log) { printf("LOG%i: %s\n", raise, log.c_str()); });
auto pipe = loop.m_io_context.provider->newTwoWayPipe();
auto connection_client = std::make_unique<Connection>(loop, kj::mv(pipe.ends[0]));
auto foo_client = std::make_unique<ProxyClient<messages::FooInterface>>(
connection_client->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<messages::FooInterface>(),
connection_client.get(), /* destroy_connection= */ false);
foo_promise.set_value(std::move(foo_client));
disconnect_client = [&] { loop.sync([&] { connection_client.reset(); }); };
auto connection_server = std::make_unique<Connection>(loop, kj::mv(pipe.ends[1]), [&](Connection& connection) {
auto foo_server = kj::heap<ProxyServer<messages::FooInterface>>(std::make_shared<FooImplementation>(), connection);
return capnp::Capability::Client(kj::mv(foo_server));
});
connection_server->onDisconnect([&] { connection_server.reset(); });
loop.loop();
});
auto foo = foo_promise.get_future().get();
KJ_EXPECT(foo->add(1, 2) == 3);
FooStruct in;
in.name = "name";
in.setint.insert(2);
in.setint.insert(1);
in.vbool.push_back(false);
in.vbool.push_back(true);
in.vbool.push_back(false);
FooStruct out = foo->pass(in);
KJ_EXPECT(in.name == out.name);
KJ_EXPECT(in.setint.size() == out.setint.size());
for (auto init{in.setint.begin()}, outit{out.setint.begin()}; init != in.setint.end() && outit != out.setint.end(); ++init, ++outit) {
KJ_EXPECT(*init == *outit);
}
KJ_EXPECT(in.vbool.size() == out.vbool.size());
for (size_t i = 0; i < in.vbool.size(); ++i) {
KJ_EXPECT(in.vbool[i] == out.vbool[i]);
}
FooStruct err;
try {
foo->raise(in);
} catch (const FooStruct& e) {
err = e;
}
KJ_EXPECT(in.name == err.name);
class Callback : public ExtendedCallback
{
public:
Callback(int expect, int ret) : m_expect(expect), m_ret(ret) {}
int call(int arg) override
{
KJ_EXPECT(arg == m_expect);
return m_ret;
}
int callExtended(int arg) override
{
KJ_EXPECT(arg == m_expect + 10);
return m_ret + 10;
}
int m_expect, m_ret;
};
foo->initThreadMap();
Callback callback(1, 2);
KJ_EXPECT(foo->callback(callback, 1) == 2);
KJ_EXPECT(foo->callbackUnique(std::make_unique<Callback>(3, 4), 3) == 4);
KJ_EXPECT(foo->callbackShared(std::make_shared<Callback>(5, 6), 5) == 6);
auto saved = std::make_shared<Callback>(7, 8);
KJ_EXPECT(saved.use_count() == 1);
foo->saveCallback(saved);
KJ_EXPECT(saved.use_count() == 2);
foo->callbackSaved(7);
KJ_EXPECT(foo->callbackSaved(7) == 8);
foo->saveCallback(nullptr);
KJ_EXPECT(saved.use_count() == 1);
KJ_EXPECT(foo->callbackExtended(callback, 11) == 12);
FooCustom custom_in;
custom_in.v1 = "v1";
custom_in.v2 = 5;
FooCustom custom_out = foo->passCustom(custom_in);
KJ_EXPECT(custom_in.v1 == custom_out.v1);
KJ_EXPECT(custom_in.v2 == custom_out.v2);
foo->passEmpty(FooEmpty{});
FooMessage message1;
message1.message = "init";
FooMessage message2{foo->passMessage(message1)};
KJ_EXPECT(message2.message == "init build read call build read");
FooMutable mut;
mut.message = "init";
foo->passMutable(mut);
KJ_EXPECT(mut.message == "init build pass call return read");
disconnect_client();
thread.join();
bool destroyed = false;
foo->m_context.cleanup_fns.emplace_front([&destroyed]{ destroyed = true; });
foo.reset();
KJ_EXPECT(destroyed);
}
} // namespace test
} // namespace mp

View file

@ -241,7 +241,7 @@ if(WIN32)
set_target_properties(bitcoin-qt PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
if(WITH_MULTIPROCESS)
if(BUILD_MULTIPROCESS)
add_executable(bitcoin-gui
main.cpp
../init/bitcoin-gui.cpp

View file

@ -161,9 +161,10 @@ if(ENABLE_WALLET)
add_subdirectory(${PROJECT_SOURCE_DIR}/src/wallet/test wallet)
endif()
if(WITH_MULTIPROCESS)
if(BUILD_MULTIPROCESS)
target_link_libraries(bitcoin_ipc_test
PRIVATE
Boost::headers
core_interface
univalue
)

View file

@ -51,6 +51,7 @@ KNOWN_VIOLATIONS = [
REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [
"src/crypto/ctaes/",
"src/ipc/libmultiprocess/",
"src/leveldb/",
"src/secp256k1/",
"src/minisketch/",

View file

@ -2,4 +2,5 @@ SHARED_EXCLUDED_SUBTREES = ["src/leveldb/",
"src/crc32c/",
"src/secp256k1/",
"src/minisketch/",
"src/ipc/libmultiprocess/",
]

View file

@ -287,6 +287,7 @@ fn lint_std_filesystem() -> LintResult {
"std::filesystem",
"--",
"./src/",
":(exclude)src/ipc/libmultiprocess/",
":(exclude)src/util/fs.h",
])
.status()