diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index 9a2afcc5..00000000 --- a/.clang-tidy +++ /dev/null @@ -1,40 +0,0 @@ -Checks: ' --*, -bugprone-argument-comment, -bugprone-move-forwarding-reference, -bugprone-string-constructor, -bugprone-use-after-move, -bugprone-lambda-function-name, -bugprone-unhandled-self-assignment, -misc-unused-using-decls, -misc-no-recursion, -modernize-deprecated-headers, -modernize-use-default-member-init, -modernize-use-emplace, -modernize-use-equals-default, -modernize-use-noexcept, -modernize-use-nullptr, -modernize-use-starts-ends-with, -performance-*, --performance-avoid-endl, --performance-enum-size, --performance-inefficient-string-concatenation, --performance-no-int-to-ptr, --performance-noexcept-move-constructor, --performance-unnecessary-value-param, -readability-const-return-type, -readability-redundant-declaration, -readability-redundant-string-init, -clang-analyzer-core.*, --clang-analyzer-core.UndefinedBinaryOperatorResult, -clang-analyzer-optin.core.*, -' -HeaderFilterRegex: '.' -WarningsAsErrors: '*' -CheckOptions: - - key: modernize-deprecated-headers.CheckHeaderFile - value: false - - key: performance-move-const-arg.CheckTriviallyCopyableMove - value: false - - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField - value: false diff --git a/.github/workflows/bitcoin-core-ci.yml b/.github/workflows/bitcoin-core-ci.yml index e6ac83f0..380c0e1b 100644 --- a/.github/workflows/bitcoin-core-ci.yml +++ b/.github/workflows/bitcoin-core-ci.yml @@ -9,15 +9,22 @@ name: Bitcoin Core CI on: - push: - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + workflow_call: + inputs: + source_ref: + description: 'Ref of libmultiprocess source to test. Defaults to the caller ref (github.sha).' + required: false + type: string + support_ref: + description: 'Ref of support branch to use for CI scripts. Defaults to support HEAD.' + required: false + type: string + default: 'support' env: BITCOIN_REPO: bitcoin/bitcoin + # Temporary: use PR #35454 until it merges; revert to refs/heads/master after + BITCOIN_CORE_REF: refs/pull/35454/merge LLVM_VERSION: 22 LIBCXX_DIR: /tmp/libcxx-build/ @@ -79,38 +86,48 @@ jobs: uses: actions/checkout@v4 with: repository: ${{ env.BITCOIN_REPO }} + ref: ${{ env.BITCOIN_CORE_REF }} fetch-depth: 1 - name: Checkout libmultiprocess uses: actions/checkout@v4 with: path: _libmultiprocess + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v4 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Replace libmultiprocess subtree - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh replace_subtree + run: support/ci/scripts/bitcoin_core_ci.sh replace_subtree - name: Add LLVM apt repository if: matrix.apt-llvm - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh add_llvm_apt_repository + run: support/ci/scripts/bitcoin_core_ci.sh add_llvm_apt_repository - name: Install APT packages if: matrix.packages - run: _libmultiprocess/ci/scripts/ci_helpers.sh install_apt_packages ${{ matrix.packages }} + run: support/ci/scripts/ci_helpers.sh install_apt_packages ${{ matrix.packages }} - name: Configure LLVM alternatives if: matrix.packages - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh install_llvm_alternatives + run: support/ci/scripts/bitcoin_core_ci.sh install_llvm_alternatives - name: Install Homebrew packages if: matrix.brew-packages - run: _libmultiprocess/ci/scripts/ci_helpers.sh install_homebrew_packages ${{ matrix.brew-packages }} + run: support/ci/scripts/ci_helpers.sh install_homebrew_packages ${{ matrix.brew-packages }} - name: Install pip packages if: matrix.pip-packages - run: _libmultiprocess/ci/scripts/ci_helpers.sh install_pip_packages ${{ matrix.pip-packages }} + run: support/ci/scripts/ci_helpers.sh install_pip_packages ${{ matrix.pip-packages }} - name: Determine parallelism - run: _libmultiprocess/ci/scripts/ci_helpers.sh determine_parallelism "${{ matrix.nproc_multiplier }}" + run: support/ci/scripts/ci_helpers.sh determine_parallelism "${{ matrix.nproc_multiplier }}" - name: Restore ccache id: ccache-restore @@ -124,26 +141,26 @@ jobs: - name: Reset ccache stats if: matrix.packages || matrix.brew-packages - run: _libmultiprocess/ci/scripts/ci_helpers.sh reset_ccache_stats + run: support/ci/scripts/ci_helpers.sh reset_ccache_stats - name: CMake configure env: BITCOIN_CORE_CMAKE_ARGS: ${{ matrix.cmake-args }} - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh configure_bitcoin_core + run: support/ci/scripts/bitcoin_core_ci.sh configure_bitcoin_core - name: Build - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh build_bitcoin_core + run: support/ci/scripts/bitcoin_core_ci.sh build_bitcoin_core - name: Show ccache stats if: matrix.packages || matrix.brew-packages - run: _libmultiprocess/ci/scripts/ci_helpers.sh show_ccache_stats + run: support/ci/scripts/ci_helpers.sh show_ccache_stats - name: Run IPC unit tests env: ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 LSAN_OPTIONS: suppressions=${{ github.workspace }}/test/sanitizer_suppressions/lsan UBSAN_OPTIONS: suppressions=${{ github.workspace }}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1 - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh run_ipc_unit_tests "${{ matrix.unit_test_runs }}" + run: support/ci/scripts/bitcoin_core_ci.sh run_ipc_unit_tests "${{ matrix.unit_test_runs }}" - name: Run IPC functional tests env: @@ -151,7 +168,7 @@ jobs: LSAN_OPTIONS: suppressions=${{ github.workspace }}/test/sanitizer_suppressions/lsan UBSAN_OPTIONS: suppressions=${{ github.workspace }}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1 CI_FAILFAST_TEST_LEAVE_DANGLING: 1 - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh run_ipc_functional_tests "${{ matrix.functional_test_runs }}" "${{ matrix.functional_timeout_factor }}" + run: support/ci/scripts/bitcoin_core_ci.sh run_ipc_functional_tests "${{ matrix.functional_test_runs }}" "${{ matrix.functional_timeout_factor }}" - name: Save ccache uses: actions/cache/save@v4 @@ -195,21 +212,31 @@ jobs: uses: actions/checkout@v4 with: repository: ${{ env.BITCOIN_REPO }} + ref: ${{ env.BITCOIN_CORE_REF }} fetch-depth: 1 - name: Checkout libmultiprocess uses: actions/checkout@v4 with: path: _libmultiprocess + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v4 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Add LLVM apt repository - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh add_llvm_apt_repository + run: support/ci/scripts/bitcoin_core_ci.sh add_llvm_apt_repository - name: Install packages - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh install_tsan_packages + run: support/ci/scripts/bitcoin_core_ci.sh install_tsan_packages - name: Determine parallelism - run: _libmultiprocess/ci/scripts/ci_helpers.sh determine_parallelism "${{ matrix.nproc_multiplier }}" + run: support/ci/scripts/ci_helpers.sh determine_parallelism "${{ matrix.nproc_multiplier }}" - name: Restore instrumented libc++ cache id: libcxx-cache @@ -220,11 +247,11 @@ jobs: - name: Build instrumented libc++ if: steps.libcxx-cache.outputs.cache-hit != 'true' - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh build_instrumented_libcxx + run: support/ci/scripts/bitcoin_core_ci.sh build_instrumented_libcxx - name: Determine host id: host - run: _libmultiprocess/ci/scripts/ci_helpers.sh determine_host + run: support/ci/scripts/ci_helpers.sh determine_host - name: Restore depends cache id: depends-cache @@ -237,7 +264,7 @@ jobs: - name: Build depends (stage 1, without IPC) if: steps.depends-cache.outputs.cache-hit != 'true' - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh build_depends_without_ipc + run: support/ci/scripts/bitcoin_core_ci.sh build_depends_without_ipc - name: Save depends cache uses: actions/cache/save@v4 @@ -249,10 +276,10 @@ jobs: key: depends-tsan-${{ hashFiles('depends/packages/*.mk') }}-${{ env.LLVM_VERSION }} - name: Replace libmultiprocess subtree - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh replace_subtree + run: support/ci/scripts/bitcoin_core_ci.sh replace_subtree - name: Build depends (stage 2, IPC packages including libmultiprocess) - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh build_depends_with_ipc + run: support/ci/scripts/bitcoin_core_ci.sh build_depends_with_ipc - name: Restore ccache id: ccache-restore @@ -265,7 +292,7 @@ jobs: ccache-TSan- - name: Reset ccache stats - run: _libmultiprocess/ci/scripts/ci_helpers.sh reset_ccache_stats + run: support/ci/scripts/ci_helpers.sh reset_ccache_stats - name: CMake configure env: @@ -273,24 +300,24 @@ jobs: -DSANITIZERS=thread -DAPPEND_CPPFLAGS=-DARENA_DEBUG -DDEBUG_LOCKCONTENTION -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES -DCMAKE_TOOLCHAIN_FILE=depends/${{ steps.host.outputs.host }}/toolchain.cmake - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh configure_bitcoin_core + run: support/ci/scripts/bitcoin_core_ci.sh configure_bitcoin_core - name: Build - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh build_bitcoin_core + run: support/ci/scripts/bitcoin_core_ci.sh build_bitcoin_core - name: Show ccache stats - run: _libmultiprocess/ci/scripts/ci_helpers.sh show_ccache_stats + run: support/ci/scripts/ci_helpers.sh show_ccache_stats - name: Run IPC unit tests env: LD_LIBRARY_PATH: depends/${{ steps.host.outputs.host }}/lib - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh run_ipc_unit_tests "${{ matrix.unit_test_runs }}" + run: support/ci/scripts/bitcoin_core_ci.sh run_ipc_unit_tests "${{ matrix.unit_test_runs }}" - name: Run IPC functional tests env: LD_LIBRARY_PATH: depends/${{ steps.host.outputs.host }}/lib CI_FAILFAST_TEST_LEAVE_DANGLING: 1 - run: _libmultiprocess/ci/scripts/bitcoin_core_ci.sh run_ipc_functional_tests "${{ matrix.functional_test_runs }}" "${{ matrix.functional_timeout_factor }}" + run: support/ci/scripts/bitcoin_core_ci.sh run_ipc_functional_tests "${{ matrix.functional_test_runs }}" "${{ matrix.functional_timeout_factor }}" - name: Save ccache uses: actions/cache/save@v4 diff --git a/.github/workflows/ci-support-trigger.yml b/.github/workflows/ci-support-trigger.yml new file mode 100644 index 00000000..b4188d59 --- /dev/null +++ b/.github/workflows/ci-support-trigger.yml @@ -0,0 +1,24 @@ +name: CI (support branch) + +# Trigger CI for pushes and PRs against the support branch. +# Builds the C++ source from master and tests it using the CI scripts from +# this branch (or PR branch), so that support branch changes can be validated. + +on: + push: + pull_request: + +jobs: + ci: + uses: ./.github/workflows/ci.yml + with: + source_ref: refs/heads/master + support_ref: ${{ github.sha }} + secrets: inherit + + bitcoin-core-ci: + uses: ./.github/workflows/bitcoin-core-ci.yml + with: + source_ref: refs/heads/master + support_ref: ${{ github.sha }} + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffaca1ca..10abac93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,17 @@ name: CI on: - push: - pull_request: + workflow_call: + inputs: + source_ref: + description: 'Ref of C++ source to build. Defaults to the caller ref (github.sha).' + required: false + type: string + support_ref: + description: 'Ref of support branch to use for CI scripts. Defaults to support HEAD.' + required: false + type: string + default: 'support' jobs: build-netbsd: @@ -23,6 +32,16 @@ jobs: capnproto-cppflags: 'CPPFLAGS="-DKJ_NO_EXCEPTIONS=0"' steps: - uses: actions/checkout@v6 + with: + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v6 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Start NetBSD VM uses: vmactions/netbsd-vm@v1 @@ -62,8 +81,8 @@ jobs: - name: Run CI script run: | - cd ${{ github.workspace }} - CI_CONFIG="ci/configs/netbsd.bash" bash ci/scripts/ci.sh + cd "${{ github.workspace }}/support" + CI_CONFIG="netbsd" bash ci/scripts/ci.sh build-openbsd: runs-on: ubuntu-latest @@ -73,6 +92,16 @@ jobs: shell: openbsd {0} steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v5 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Start OpenBSD VM uses: vmactions/openbsd-vm@v1 @@ -84,8 +113,8 @@ jobs: - name: Run CI script run: | - cd ${{ github.workspace }} - CI_CONFIG="ci/configs/openbsd.bash" bash ci/scripts/ci.sh + cd "${{ github.workspace }}/support" + CI_CONFIG="openbsd" bash ci/scripts/ci.sh build-freebsd: runs-on: ubuntu-latest @@ -95,6 +124,16 @@ jobs: shell: freebsd {0} steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v5 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Start FreeBSD VM uses: vmactions/freebsd-vm@v1 @@ -106,8 +145,8 @@ jobs: - name: Run CI script run: | - cd ${{ github.workspace }} - CI_CONFIG="ci/configs/freebsd.bash" bash ci/scripts/ci.sh + cd "${{ github.workspace }}/support" + CI_CONFIG="freebsd" bash ci/scripts/ci.sh build-macos: runs-on: macos-latest @@ -115,6 +154,16 @@ jobs: steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v5 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Install dependencies env: @@ -125,7 +174,8 @@ jobs: - name: Run CI script run: | export PATH="$(brew --prefix llvm)/bin:$PATH" - CI_CONFIG="ci/configs/macos.bash" bash ci/scripts/ci.sh + cd "${{ github.workspace }}/support" + CI_CONFIG="macos" bash ci/scripts/ci.sh build: runs-on: ubuntu-latest @@ -148,12 +198,22 @@ jobs: steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_ref || github.sha }} + repository: ${{ github.repository }} + + - name: Checkout support branch + uses: actions/checkout@v5 + with: + ref: ${{ inputs.support_ref }} + path: support + repository: ${{ github.repository }} - name: Determine CI configuration id: config env: - CI_CONFIG: ci/configs/${{ matrix.config }}.bash - run: ci/scripts/config.sh + CI_CONFIG: ${{ matrix.config }} + run: support/ci/scripts/config.sh - name: Install Nix uses: cachix/install-nix-action@v31 # 2025-05-27, from https://github.com/cachix/install-nix-action/tags @@ -170,7 +230,7 @@ jobs: if: steps.config.outputs.cache_nix_store == 'true' uses: nix-community/cache-nix-action@v7 with: - primary-key: nix-${{ runner.os }}-${{ matrix.config }}-${{ steps.config.outputs.nixpkgs_rev }}-${{ hashFiles('shell.nix', 'ci/patches/*.patch', format('ci/configs/{0}.bash', matrix.config)) }} + primary-key: nix-${{ runner.os }}-${{ matrix.config }}-${{ steps.config.outputs.nixpkgs_rev }}-${{ hashFiles('support/shell.nix', 'support/ci/patches/*.patch', format('support/ci/configs/{0}.bash', matrix.config)) }} restore-prefixes-first-match: | nix-${{ runner.os }}-${{ matrix.config }}-${{ steps.config.outputs.nixpkgs_rev }}- nix-${{ runner.os }}-${{ matrix.config }}- @@ -179,5 +239,5 @@ jobs: - name: Run CI script env: - CI_CONFIG: ci/configs/${{ matrix.config }}.bash - run: ci/scripts/run.sh + CI_CONFIG: ${{ matrix.config }} + run: support/ci/scripts/run.sh diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 30b4b786..00000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# CMake artifacts -/*build* - -# Git artifacts -*.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index ea91f98f..4e30b12b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,269 +2,16 @@ # 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.12) +cmake_minimum_required(VERSION 3.12..4.1) -project("Libmultiprocess" CXX) -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED YES) -endif() +project(LibmultiprocessSupport CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED YES) -include("cmake/compat_find.cmake") +set(MP_SOURCE_DIR ".." CACHE PATH "Path to the libmultiprocess source directory.") +get_filename_component(MP_SOURCE_DIR "${MP_SOURCE_DIR}" ABSOLUTE) +include(CTest) +add_subdirectory("${MP_SOURCE_DIR}" libmultiprocess) find_package(Threads REQUIRED) -find_package(CapnProto 0.7 QUIET NO_MODULE) - if(NOT CapnProto_FOUND) - message(FATAL_ERROR - "Cap'n Proto is required but was not found.\n" - "To resolve, choose one of the following:\n" - " - Install Cap'n Proto (version 1.0+ recommended)\n" - " - For Bitcoin Core compilation build with -DENABLE_IPC=OFF to disable multiprocess support\n" - ) - endif() - -# Cap'n Proto compatibility checks -set(CAPNPROTO_ISSUES "") -set(CAPNPROTO_CVE_AFFECTED FALSE) -set(CAPNPROTO_CLANG_INCOMPATIBLE FALSE) - -# Check for list-of-pointers memory access bug from Nov 2022 -# https://nvd.nist.gov/vuln/detail/CVE-2022-46149 -# https://github.com/advisories/GHSA-qqff-4vw4-f6hx -# https://github.com/capnproto/capnproto/security/advisories/GHSA-qqff-4vw4-f6hx -# https://github.com/capnproto/capnproto/blob/master/security-advisories/2022-11-30-0-pointer-list-bounds.md -# https://capnproto.org/news/2022-11-30-CVE-2022-46149-security-advisory.html -# https://dwrensha.github.io/capnproto-rust/2022/11/30/out_of_bounds_memory_access_bug.html -if(CapnProto_VERSION STREQUAL "0.7.0" - OR CapnProto_VERSION STREQUAL "0.8.0" - OR CapnProto_VERSION STREQUAL "0.9.0" - OR CapnProto_VERSION STREQUAL "0.9.1" - OR CapnProto_VERSION STREQUAL "0.10.0" - OR CapnProto_VERSION STREQUAL "0.10.1" - OR CapnProto_VERSION STREQUAL "0.10.2") - set(CAPNPROTO_CVE_AFFECTED TRUE) - string(APPEND CAPNPROTO_ISSUES "- CVE-2022-46149 security vulnerability (details: https://github.com/advisories/GHSA-qqff-4vw4-f6hx)\n") -endif() - -# Check for Cap'n Proto / Clang / C++20 incompatibility -# Cap'n Proto 0.9.x and 0.10.x are incompatible with Clang 16+ when using C++20 -# due to P2468R2 implementation. This was fixed in Cap'n Proto 1.0+. -# See: https://github.com/bitcoin-core/libmultiprocess/issues/199 -if((CapnProto_VERSION VERSION_GREATER_EQUAL "0.9.0") AND - (CapnProto_VERSION VERSION_LESS "1.0.0") AND - (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND - (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "16") AND - (CMAKE_CXX_STANDARD EQUAL 20)) - set(CAPNPROTO_CLANG_INCOMPATIBLE TRUE) - string(APPEND CAPNPROTO_ISSUES "- Incompatible with Clang ${CMAKE_CXX_COMPILER_VERSION} when using C++20\n") -endif() - -if(CAPNPROTO_CVE_AFFECTED OR CAPNPROTO_CLANG_INCOMPATIBLE) - set(RESOLUTION_OPTIONS "") - - # Fixes both issues - string(APPEND RESOLUTION_OPTIONS " - Upgrade to Cap'n Proto version 1.0 or newer (recommended)\n") - - if(CAPNPROTO_CVE_AFFECTED AND NOT CAPNPROTO_CLANG_INCOMPATIBLE) - string(APPEND RESOLUTION_OPTIONS " - Upgrade to a patched minor version (0.7.1, 0.8.1, 0.9.2, 0.10.3, or later)\n") - elseif(CAPNPROTO_CLANG_INCOMPATIBLE AND NOT CAPNPROTO_CVE_AFFECTED) - string(APPEND RESOLUTION_OPTIONS " - Use GCC instead of Clang\n") - endif() - - string(APPEND RESOLUTION_OPTIONS " - For Bitcoin Core compilation build with -DENABLE_IPC=OFF to disable multiprocess support\n") - - message(FATAL_ERROR - "The version of Cap'n Proto detected: ${CapnProto_VERSION} has known compatibility issues:\n" - "${CAPNPROTO_ISSUES}" - "To resolve, choose one of the following:\n" - "${RESOLUTION_OPTIONS}" - ) -endif() - -set(MPGEN_EXECUTABLE "" CACHE FILEPATH "If specified, should be full path to an external mpgen binary to use rather than the one built internally.") - -option(MP_ENABLE_CLANG_TIDY "Run clang-tidy with the compiler." OFF) -if(MP_ENABLE_CLANG_TIDY) - find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy) - if(NOT CLANG_TIDY_EXECUTABLE) - message(FATAL_ERROR "MP_ENABLE_CLANG_TIDY is ON but clang-tidy is not found.") - endif() - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE}") -endif() - -option(MP_ENABLE_IWYU "Run include-what-you-use with the compiler." OFF) -if(MP_ENABLE_IWYU) - find_program(IWYU_EXECUTABLE NAMES include-what-you-use iwyu) - if(NOT IWYU_EXECUTABLE) - message(FATAL_ERROR "MP_ENABLE_IWYU is ON but include-what-you-use was not found.") - endif() - set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IWYU_EXECUTABLE};-Xiwyu;--error") - if(DEFINED ENV{IWYU_MAPPING_FILE}) - list(APPEND CMAKE_CXX_INCLUDE_WHAT_YOU_USE "-Xiwyu" "--mapping_file=$ENV{IWYU_MAPPING_FILE}") - endif() -endif() - -if(MP_ENABLE_CLANG_TIDY OR MP_ENABLE_IWYU) - # Workaround for nix from https://gitlab.kitware.com/cmake/cmake/-/issues/20912#note_793338 - # Nix injects header paths via $NIX_CFLAGS_COMPILE; CMake tags these as - # CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES and omits them from the compile - # database, so clang-tidy, which ignores $NIX_CFLAGS_COMPILE, can't find capnp - # headers. Setting them as standard passes them to clang-tidy. - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) -endif() - -include("cmake/compat_config.cmake") -include("cmake/pthread_checks.cmake") -include(GNUInstallDirs) - -# Set MP_INCLUDE_DIR as a global property so target_capnp_sources function can -# use it, and its callers don't need to specify the include directory manually -# to avoid "error: Import failed: /mp/proxy.capnp" failures from capnproto. -set_property(GLOBAL PROPERTY MP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") - -# Set a convenience variable for subdirectories. -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(MP_STANDALONE TRUE) - include(CTest) -else() - 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) -set_source_files_properties("${MP_PROXY_SRCS}" PROPERTIES SKIP_LINTING TRUE) # Ignored before cmake 3.27 -# Build-graph node for generated headers. This lets targets that include -# the headers order themselves after generation without depending on the -# library target that also uses them. -add_custom_target(mp_headers DEPENDS ${MP_PROXY_HDRS}) - -# util library -add_library(mputil OBJECT src/mp/util.cpp) -target_include_directories(mputil PRIVATE - $ - $) -target_link_libraries(mputil PUBLIC CapnProto::kj) - -# 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 - $) -add_library(Libmultiprocess::multiprocess ALIAS multiprocess) -target_include_directories(multiprocess PUBLIC - $ - $ - $) -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 $) -add_executable(Libmultiprocess::mpgen ALIAS mpgen) -target_include_directories(mpgen PRIVATE $) -target_include_directories(mpgen PUBLIC $ $) -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) +add_subdirectory(example) diff --git a/README.md b/README.md index 9a6e6579..824eb503 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ -# libmultiprocess +# libmultiprocess (support branch) -`libmultiprocess` is a C++ library and code generator making it easy to call functions and reference objects in different processes. +This branch contains CI scripts, documentation, and examples supporting the +libmultiprocess library. -For more information see the [usage instructions](doc/usage.md), [installation instructions](doc/install.md), or [design documentation](doc/design.md). +Contents: -If you have any questions, comments, or feedback, please submit an [issue](https://github.com/bitcoin-core/libmultiprocess/issues/new). -Duplicate issues are perfectly fine and all discussion about the project is welcome, since there isn't another discussion forum currently. +- [`ci/`](ci/) — CI scripts, configs, and patches +- [`doc/`](doc/) — design, usage, and installation documentation +- [`example/`](example/) — example C++ code +- [`CMakeLists.txt`](CMakeLists.txt) — CMake project for building example code +- [`shell.nix`](shell.nix) — Nix development environment + +The `CMakeLists.txt` file assumes it is checked out to a subdirectory of the +libmultiprocess library source code (this can be controlled with the +`MP_SOURCE_DIR` option), and the library can be found one level up. + +See [ci/README.md](ci/README.md) for instructions on running CI jobs locally. diff --git a/ci/README.md b/ci/README.md index fef1c022..6f0db8ab 100644 --- a/ci/README.md +++ b/ci/README.md @@ -16,11 +16,11 @@ All CI is just bash and nix. To run jobs locally: ```bash -CI_CONFIG=ci/configs/default.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/llvm.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/gnu32.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/sanitize.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/olddeps.bash ci/scripts/run.sh +CI_CONFIG=default ci/scripts/run.sh +CI_CONFIG=llvm ci/scripts/run.sh +CI_CONFIG=gnu32 ci/scripts/run.sh +CI_CONFIG=sanitize ci/scripts/run.sh +CI_CONFIG=olddeps ci/scripts/run.sh ``` By default CI jobs will reuse their build directories. `CI_CLEAN=1` can be specified to delete them before running instead. @@ -48,5 +48,3 @@ macOS or Linux: -j build \ --matrix config:sanitize ``` - - diff --git a/ci/scripts/ci.sh b/ci/scripts/ci.sh index d989e9f4..3e5067e8 100755 --- a/ci/scripts/ci.sh +++ b/ci/scripts/ci.sh @@ -8,11 +8,11 @@ export LC_ALL=C.UTF-8 set -o errexit -o nounset -o pipefail -o xtrace -[ "${CI_CONFIG+x}" ] && source "$CI_CONFIG" +[ "${CI_CONFIG+x}" ] && source "ci/configs/${CI_CONFIG}.bash" : "${CI_DIR:=build}" if ! [ -v BUILD_TARGETS ]; then - BUILD_TARGETS=(all tests mpexamples) + BUILD_TARGETS=(all mptests mpexamples) fi [ -n "${CI_CLEAN-}" ] && rm -rf "${CI_DIR}" diff --git a/ci/scripts/config.sh b/ci/scripts/config.sh index 7fe8118d..553094d7 100755 --- a/ci/scripts/config.sh +++ b/ci/scripts/config.sh @@ -14,7 +14,7 @@ readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/ci_helpers.sh" -[ "${CI_CONFIG+x}" ] && source "$CI_CONFIG" +[ "${CI_CONFIG+x}" ] && source "${SCRIPT_DIR}/../configs/${CI_CONFIG}.bash" # Resolve the nixpkgs channel to a specific revision for use in cache keys. if [[ -n "${NIXPKGS_CHANNEL:-}" ]]; then diff --git a/ci/scripts/run.sh b/ci/scripts/run.sh index 04ac8596..d2935b5b 100755 --- a/ci/scripts/run.sh +++ b/ci/scripts/run.sh @@ -8,7 +8,11 @@ export LC_ALL=C.UTF-8 set -o errexit -o nounset -o pipefail -o xtrace -[ "${CI_CONFIG+x}" ] && source "$CI_CONFIG" +# Change to the support branch root (two levels up from this script) so that +# shell.nix and CMakeLists.txt can be referenced without a path prefix. +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." + +[ "${CI_CONFIG+x}" ] && source "ci/configs/${CI_CONFIG}.bash" nix develop --ignore-environment --keep CI_CONFIG --keep CI_CLEAN "${NIX_ARGS[@]+"${NIX_ARGS[@]}"}" -f shell.nix --command ci/scripts/ci.sh diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in deleted file mode 100644 index 60187f08..00000000 --- a/cmake/Config.cmake.in +++ /dev/null @@ -1,33 +0,0 @@ -@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() diff --git a/cmake/TargetCapnpSources.cmake b/cmake/TargetCapnpSources.cmake deleted file mode 100644 index f04a9657..00000000 --- a/cmake/TargetCapnpSources.cmake +++ /dev/null @@ -1,125 +0,0 @@ -# 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 - - And if include_prefix is /home/src/lib, generated includes look like: - - #include - - Pass ${CMAKE_CURRENT_SOURCE_DIR} or a subdirectory of it to include files - relative to the current source directory (the typical usage). - -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. - - ONLY_CAPNP: If specified, only the Cap'n Proto serialization files - (`.capnp.h`, `.capnp.c++`) are generated and compiled. The mpgen proxy - files (`.capnp.proxy-client.c++`, `.capnp.proxy-server.c++`, - `.capnp.proxy-types.c++`, etc.) are skipped. Useful when you need - Cap'n Proto serialization without the multiprocess RPC proxy - infrastructure. - -Example: - # Assuming `my_library` is a target and `lib/` contains `.capnp` schema - # files with imports from `include/`. - target_capnp_sources(my_library "${CMAKE_CURRENT_SOURCE_DIR}" - lib/schema1.capnp lib/schema2.capnp - IMPORT_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/include) - -#]=] - -function(target_capnp_sources target include_prefix) - cmake_parse_arguments(PARSE_ARGV 2 - "TCS" # prefix - "ONLY_CAPNP" # options - "" # one_value_keywords - "IMPORT_PATHS" # multi_value_keywords - ) - - set(MPGEN_BINARY "") - if(MPGEN_EXECUTABLE) - set(MPGEN_BINARY "${MPGEN_EXECUTABLE}") - if(NOT EXISTS "${MPGEN_BINARY}") - message(FATAL_ERROR "MPGEN_EXECUTABLE: \"${MPGEN_BINARY}\" does not exist.") - endif() - elseif(TARGET Libmultiprocess::multiprocess) - set(MPGEN_BINARY Libmultiprocess::mpgen) - else() - message(FATAL_ERROR "No usable mpgen. Set MPGEN_EXECUTABLE or enable the internal target.") - endif() - - get_property(mp_include_dir GLOBAL PROPERTY MP_INCLUDE_DIR) - 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 ${MPGEN_BINARY} ${CMAKE_CURRENT_SOURCE_DIR} ${include_prefix} ${CMAKE_CURRENT_SOURCE_DIR}/${capnp_file} ${TCS_IMPORT_PATHS} ${mp_include_dir} - DEPENDS ${capnp_file} - VERBATIM - ) - # Skip linting for capnp-generated files but keep it for mpgen-generated ones - set_source_files_properties(${capnp_file}.c++ PROPERTIES SKIP_LINTING TRUE) # Ignored before cmake 3.27 - target_sources(${target} PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}/${capnp_file}.c++ - ) - if(NOT TCS_ONLY_CAPNP) - target_sources(${target} PRIVATE - ${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++ - ) - endif() - 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_CURRENT_BINARY_DIR}) - file(RELATIVE_PATH relative_path ${CMAKE_CURRENT_SOURCE_DIR} ${include_prefix}) - if(relative_path) - string(APPEND build_include_prefix "/" "${relative_path}") - endif() - target_include_directories(${target} PUBLIC $ ${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. - if(NOT TARGET "${target}_headers") - add_custom_target("${target}_headers" DEPENDS ${generated_headers}) - endif() -endfunction() diff --git a/cmake/compat_config.cmake b/cmake/compat_config.cmake deleted file mode 100644 index f9d3004f..00000000 --- a/cmake/compat_config.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 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/bitcoin-core/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/bitcoin-core/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() diff --git a/cmake/compat_find.cmake b/cmake/compat_find.cmake deleted file mode 100644 index d3d7bc6d..00000000 --- a/cmake/compat_find.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 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 happen 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 intrinsic as described -# https://github.com/bitcoin-core/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) diff --git a/cmake/pthread_checks.cmake b/cmake/pthread_checks.cmake deleted file mode 100644 index 87711416..00000000 --- a/cmake/pthread_checks.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 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 - 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 - #include - int main(int argc, char** argv) - { - uint64_t tid; - pthread_threadid_np(nullptr, &tid); - return 0; - }" - HAVE_PTHREAD_THREADID_NP) - -check_cxx_source_compiles(" - #include - #include - int main(int argc, char** argv) - { - return pthread_getthreadid_np(); - }" - HAVE_PTHREAD_GETTHREADID_NP) -cmake_pop_check_state() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 7da049c3..6e0541d3 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -2,8 +2,6 @@ # 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 ) diff --git a/include/mp/config.h.in b/include/mp/config.h.in deleted file mode 100644 index 9d3c6240..00000000 --- a/include/mp/config.h.in +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 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 diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h deleted file mode 100644 index 092ea42e..00000000 --- a/include/mp/proxy-io.h +++ /dev/null @@ -1,893 +0,0 @@ -// Copyright (c) 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 -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 -struct ServerInvokeContext : InvokeContext -{ - using CallContext = CallContext_; - - ProxyServer& proxy_server; - CallContext& call_context; - int req; - //! For IPC methods that execute asynchronously, not on the event-loop - //! thread: lock preventing the event-loop thread from freeing the params or - //! results structs if the request is canceled while the worker thread is - //! reading params (`call_context.getParams()`) or writing results - //! (`call_context.getResults()`). - Lock* cancel_lock{nullptr}; - //! For IPC methods that execute asynchronously, not on the event-loop - //! thread, this is set to true if the IPC call was canceled by the client - //! or canceled by a disconnection. If the call runs on the event-loop - //! thread, it can't be canceled. This should be accessed with cancel_lock - //! held if it is not null, since in the asynchronous case it is accessed - //! from multiple threads. - bool request_canceled{false}; - - 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 -using ServerContext = ServerInvokeContext, ::capnp::CallContext>; - -template <> -struct ProxyClient : public ProxyClientBase -{ - 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(); - - //! Reference to callback function that is run if there is a sudden - //! disconnect and the Connection object is destroyed before this - //! ProxyClient object. The callback will destroy this object and - //! remove its entry from the thread's request_threads or callback_threads - //! map. It will also reset m_disconnect_cb so the destructor does not - //! access it. In the normal case where there is no sudden disconnect, the - //! destructor will unregister m_disconnect_cb so the callback is never run. - //! Since this variable is accessed from multiple threads, accesses should - //! be guarded with the associated Waiter::m_mutex. - std::optional m_disconnect_cb; -}; - -template <> -struct ProxyServer final : public Thread::Server -{ -public: - ProxyServer(Connection& connection, ThreadContext& thread_context, std::thread&& thread); - ~ProxyServer(); - kj::Promise getName(GetNameContext context) override; - - //! Run a callback function fn returning T on this thread. The function will - //! be queued and executed as soon as the thread is idle, and when fn - //! returns, the promise returned by this method will be fulfilled with the - //! value fn returned. - template - kj::Promise post(Fn&& fn); - - EventLoopRef m_loop; - ThreadContext& m_thread_context; - std::thread m_thread; - //! Promise signaled when m_thread_context.waiter is ready and there is no - //! post() callback function waiting to execute. - kj::Promise m_thread_ready{kj::READY_NOW}; -}; - -//! 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; -}; - -//! Log flags. Update stringify function if changed! -enum class Log { - Trace = 0, - Debug, - Info, - Warning, - Error, - Raise, -}; - -kj::StringPtr KJ_STRINGIFY(Log flags); - -struct LogMessage { - - //! Message to be logged - std::string message; - - //! The severity level of this message - Log level; -}; - -using LogFn = std::function; - -struct LogOptions { - - //! External logging callback. - LogFn log_fn; - - //! Maximum number of characters to use when representing - //! request and response structs as strings. - size_t max_chars{200}; - - //! Messages with a severity level less than log_level will not be - //! reported. - Log log_level{Log::Trace}; -}; - -class Logger -{ -public: - Logger(const LogOptions& options, Log log_level) : m_options(options), m_log_level(log_level) {} - - Logger(Logger&&) = delete; - Logger& operator=(Logger&&) = delete; - Logger(const Logger&) = delete; - Logger& operator=(const Logger&) = delete; - - ~Logger() noexcept(false) - { - if (enabled()) m_options.log_fn({std::move(m_buffer).str(), m_log_level}); - } - - template - friend Logger& operator<<(Logger& logger, T&& value) - { - if (logger.enabled()) logger.m_buffer << std::forward(value); - return logger; - } - - template - friend Logger& operator<<(Logger&& logger, T&& value) - { - return logger << std::forward(value); - } - - explicit operator bool() const - { - return enabled(); - } - -private: - bool enabled() const - { - return m_options.log_fn && m_log_level >= m_options.log_level; - } - - const LogOptions& m_options; - Log m_log_level; - std::ostringstream m_buffer; -}; - -#define MP_LOGPLAIN(loop, ...) if (mp::Logger logger{(loop).m_log_opts, __VA_ARGS__}; logger) logger - -#define MP_LOG(loop, ...) MP_LOGPLAIN(loop, __VA_ARGS__) << "{" << LongThreadName((loop).m_exe_name) << "} " - -std::string LongThreadName(const char* exe_name); - -//! Event loop implementation. -//! -//! Cap'n Proto threading model is very simple: all I/O operations are -//! asynchronous and must be performed on a single thread. This includes: -//! -//! - Code starting an asynchronous operation (calling a function that returns a -//! promise object) -//! - Code notifying that an asynchronous operation is complete (code using a -//! fulfiller object) -//! - Code handling a completed operation (code chaining or waiting for a promise) -//! -//! All of this code needs to access shared state, and there is no mutex that -//! can be acquired to lock this state because Cap'n Proto -//! assumes it will only be accessed from one thread. So all this code needs to -//! actually run on one thread, and the EventLoop::loop() method is the entry point for -//! this thread. ProxyClient and ProxyServer objects that use other threads and -//! need to perform I/O operations post to this thread using EventLoop::post() -//! and EventLoop::sync() methods. -//! -//! Specifically, because ProxyClient methods can be called from arbitrary -//! threads, and ProxyServer methods can run on arbitrary threads, ProxyClient -//! methods use the EventLoop thread to send requests, and ProxyServer methods -//! use the thread to return results. -//! -//! Based on https://groups.google.com/d/msg/capnproto/TuQFF1eH2-M/g81sHaTAAQAJ -class EventLoop -{ -public: - //! Construct event loop object with default logging options. - EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr) - : EventLoop(exe_name, LogOptions{std::move(log_fn)}, context){} - - //! Construct event loop object with specified logging options. - EventLoop(const char* exe_name, LogOptions log_opts, void* context = nullptr); - - //! Backwards-compatible constructor for previous (deprecated) logging callback signature - EventLoop(const char* exe_name, std::function old_callback, void* context = nullptr) - : EventLoop(exe_name, - LogFn{[old_callback = std::move(old_callback)](LogMessage log_data) {old_callback(log_data.level == Log::Raise, std::move(log_data.message));}}, - context){} - - ~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(kj::Function 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 - void sync(Callable&& callable) - { - post(std::forward(callable)); - } - - //! Register cleanup function to run on asynchronous worker thread without - //! blocking the event loop thread. - void addAsyncCleanup(std::function fn); - - //! 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() MP_REQUIRES(m_mutex); - - //! Check if loop should exit. - bool done() const MP_REQUIRES(m_mutex); - - //! 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. - kj::Function* m_post_fn MP_GUARDED_BY(m_mutex) = nullptr; - - //! Callback functions to run on async thread. - std::optional m_async_fns MP_GUARDED_BY(m_mutex); - - //! 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 MP_GUARDED_BY(m_mutex) = 0; - - //! Mutex and condition variable used to post tasks to event loop and async - //! thread. - 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 m_task_set; - - //! List of connections. - std::list m_incoming_connections; - - //! Logging options - LogOptions m_log_opts; - - //! External context pointer. - void* m_context; - - //! Hook called when ProxyServer::makeThread() is called. - std::function testing_hook_makethread; - - //! Hook called on the worker thread inside makeThread(), after the thread - //! context is set up and thread_context promise is fulfilled, but before it - //! starts waiting for requests. - std::function testing_hook_makethread_created; - - //! Hook called on the worker thread when it starts to execute an async - //! request. Used by tests to control timing or inject behavior at this - //! point in execution. - std::function testing_hook_async_request_start; - - //! Hook called on the worker thread just before returning results. - std::function testing_hook_async_request_done; -}; - -//! Single element task queue used to handle recursive capnp calls. (If the -//! server makes a callback into the client in the middle of a request, while the 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 a -//! single process, with the callback sharing the same thread stack as the original -//! call.) To support this, the clientInvoke function calls Waiter::wait() to -//! block the client IPC thread while initial request is in progress. Then if -//! there is a callback, it is executed with Waiter::post(). -//! -//! The Waiter class is also used server-side by `ProxyServer::post()` -//! to execute IPC calls on worker threads. -struct Waiter -{ - Waiter() = default; - - template - bool post(Fn&& fn) - { - const Lock lock(m_mutex); - if (m_fn) return false; - m_fn = std::forward(fn); - m_cv.notify_all(); - return true; - } - - template - void wait(Lock& lock, Predicate pred) MP_REQUIRES(m_mutex) - { - m_cv.wait(lock.m_lock, [&]() MP_REQUIRES(m_mutex) { - // 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.reset(); - Unlock(lock, fn); - } - const bool done = pred(); - return done; - }); - } - - //! Mutex mainly used internally by waiter class, but also used externally - //! to guard access to related state. Specifically, since the thread_local - //! ThreadContext struct owns a Waiter, the Waiter::m_mutex is used to guard - //! access to other parts of the struct to avoid needing to deal with more - //! mutexes than necessary. This mutex can be held at the same time as - //! EventLoop::m_mutex as long as Waiter::mutex is locked first and - //! EventLoop::m_mutex is locked second. - Mutex m_mutex; - std::condition_variable m_cv MP_GUARDED_BY(m_mutex); - std::optional> m_fn MP_GUARDED_BY(m_mutex); -}; - -//! 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&& 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)) {} - Connection(EventLoop& loop, - kj::Own&& 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))) {} - - //! 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 fn); - void removeSyncCleanup(CleanupIt it); - - //! Add disconnect handler. - template - void onDisconnect(F&& f) - { - // Add disconnect handler to local TaskSet to ensure it is canceled 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 the typical case where f deletes this Connection object. - m_on_disconnect.add(m_network.onDisconnect().then( - [f = std::forward(f), this]() mutable { m_loop->m_task_set->add(kj::evalLater(kj::mv(f))); })); - } - - EventLoopRef m_loop; - kj::Own m_stream; - LoggingErrorHandler m_error_handler{*m_loop}; - //! TaskSet used to cancel the m_network.onDisconnect() handler for remote - //! disconnections, if the connection is closed locally first by deleting - //! this Connection object. - 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 objects previously returned by - //! ThreadMap.makeThread) used to service requests to clients. - ::capnp::CapabilityServerSet m_threads; - - //! Canceler for canceling promises that we want to discard when the - //! connection is destroyed. This is used to interrupt method calls that are - //! still executing at time of disconnection. - kj::Canceler m_canceler; - - //! Cleanup functions to run if connection is broken unexpectedly. List - //! will be empty if all ProxyClient are destroyed cleanly before the - //! connection is destroyed. - CleanupList m_sync_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 -ProxyClientBase::ProxyClientBase(typename Interface::Client client, - Connection* connection, - bool destroy_connection) - : m_client(std::move(client)), m_context(connection) - -{ - MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this; - // Handler for the connection getting destroyed before this client object. - auto disconnect_cb = m_context.connection->addSyncCleanup([this]() { - // Release client capability by move-assigning to temporary. - { - typename Interface::Client(std::move(m_client)); - } - Lock lock{m_context.loop->m_mutex}; - 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 disconnect_cb function, which sets - // m_context.connection to null so nothing happens here. - m_context.cleanup_fns.emplace_front([this, destroy_connection, disconnect_cb]{ - { - // 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. - // Exceptions are caught and logged rather than propagated because - // ~ProxyClientBase is noexcept and the peer may be gone by the time - // this runs. - if (kj::runCatchingExceptions([&]{ Sub::destroy(*this); }) != nullptr) { - MP_LOG(*m_context.loop, Log::Warning) << "Remote destroy call failed during cleanup. Continuing."; - } - - // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code - m_context.loop->sync([&]() { - // Remove disconnect callback on cleanup so it doesn't run and try - // to access this object after it's destroyed. This call needs to - // run inside loop->sync() on the event loop thread because - // otherwise, if there were an ill-timed disconnect, the - // onDisconnect handler could fire and delete the Connection object - // before the removeSyncCleanup call. - if (m_context.connection) m_context.connection->removeSyncCleanup(disconnect_cb); - - // Release client capability by move-assigning to temporary. - { - typename Interface::Client(std::move(m_client)); - } - if (destroy_connection) { - delete m_context.connection; - m_context.connection = nullptr; - } - }); - } - }); - Sub::construct(*this); -} - -template -ProxyClientBase::~ProxyClientBase() noexcept -{ - MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this; - CleanupRun(m_context.cleanup_fns); - MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this; -} - -template -ProxyServerBase::ProxyServerBase(std::shared_ptr impl, Connection& connection) - : m_impl(std::move(impl)), m_context(&connection) -{ - MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this; - assert(m_impl); -} - -//! ProxyServer destructor, called from the EventLoop thread by Cap'n Proto -//! garbage collection code after there are no more references to this object. -//! This will typically happen when the corresponding ProxyClient object on the -//! other side of the connection is destroyed. It can also happen earlier if the -//! connection is broken or destroyed. In the latter case this destructor will -//! typically be called inside m_rpc_system.reset() call in the ~Connection -//! destructor while the Connection object still exists. However, because -//! ProxyServer objects are refcounted, and the Connection object could be -//! destroyed while asynchronous IPC calls are still in-flight, it's possible -//! for this destructor to be called after the Connection object no longer -//! exists, so it is NOT valid to dereference the m_context.connection pointer -//! from this function. -template -ProxyServerBase::~ProxyServerBase() -{ - MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this; - 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.loop->addAsyncCleanup([impl=std::move(m_impl), fns=std::move(m_context.cleanup_fns)]() mutable { - impl.reset(); - CleanupRun(fns); - }); - } - assert(m_context.cleanup_fns.empty()); - MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this; -} - -//! 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 -void ProxyServerBase::invokeDestroy() -{ - m_impl.reset(); - CleanupRun(m_context.cleanup_fns); -} - -//! Map from Connection to local or remote thread handle which will be used over -//! that connection. This map will typically only contain one entry, but can -//! contain multiple if a single thread makes IPC calls over multiple -//! connections. A std::optional value type is used to avoid the map needing to -//! be locked while ProxyClient objects are constructed, see -//! ThreadContext "Synchronization note" below. -using ConnThreads = std::map>>; -using ConnThread = ConnThreads::iterator; - -// Retrieve ProxyClient 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 SetThread(GuardedRef threads, Connection* connection, const std::function& make_thread); - -//! The thread_local ThreadContext g_thread_context struct provides information -//! about individual threads and a way of communicating between them. Because -//! it's a thread local struct, each ThreadContext instance is initialized by -//! the thread that owns it. -//! -//! ThreadContext is used for any client threads created externally which make -//! IPC calls, and for server threads created by -//! ProxyServer::makeThread() which execute IPC calls for clients. -//! -//! In both cases, the struct holds information like the thread name, and a -//! Waiter object where the EventLoop can post incoming IPC requests to execute -//! on the thread. The struct also holds ConnThread maps associating the thread -//! with local and remote ProxyClient objects. -struct ThreadContext -{ - //! Identifying string for debug. - std::string thread_name; - - //! Waiter object used to allow remote clients to execute code on this - //! thread. For server threads created by - //! ProxyServer::makeThread(), this is initialized in that - //! function. Otherwise, for client threads created externally, this is - //! initialized the first time the thread tries to make an IPC call. Having - //! a waiter is necessary for threads making IPC calls in case a server they - //! are calling expects them to execute a callback during the call, before - //! it sends a response. - //! - //! For IPC client threads, the Waiter pointer is never cleared and the Waiter - //! just gets destroyed when the thread does. For server threads created by - //! makeThread(), this pointer is set to null in the ~ProxyServer as - //! a signal for the thread to exit and destroy itself. In both cases, the - //! same Waiter object is used across different calls and only created and - //! destroyed once for the lifetime of the thread. - std::unique_ptr 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. - //! - //! Synchronization note: The callback_thread and request_thread maps are - //! only ever accessed internally by this thread's destructor and externally - //! by Cap'n Proto event loop threads. Since it's possible for IPC client - //! threads to make calls over different connections that could have - //! different event loops, these maps are guarded by Waiter::m_mutex in case - //! different event loop threads add or remove map entries simultaneously. - //! However, individual ProxyClient objects in the maps will only be - //! associated with one event loop and guarded by EventLoop::m_mutex. So - //! Waiter::m_mutex does not need to be held while accessing individual - //! ProxyClient instances, and may even need to be released to - //! respect lock order and avoid locking Waiter::m_mutex before - //! EventLoop::m_mutex. - ConnThreads callback_threads MP_GUARDED_BY(waiter->m_mutex); - - //! 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. - //! - //! Synchronization note: \ref callback_threads note applies here as well. - ConnThreads request_threads MP_GUARDED_BY(waiter->m_mutex); - - //! 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; -}; - -template -kj::Promise ProxyServer::post(Fn&& fn) -{ - auto ready = kj::newPromiseAndFulfiller(); // Signaled when waiter is ready to post again. - auto cancel_monitor_ptr = kj::heap(); - CancelMonitor& cancel_monitor = *cancel_monitor_ptr; - // Keep a reference to the ProxyServer instance by assigning it to - // the self variable. ProxyServer instances are reference-counted and if the - // client drops its reference, this variable keeps the instance alive until - // the thread finishes executing. The self variable needs to be destroyed on - // the event loop thread so it is freed in a sync() call below. - auto self = thisCap(); - auto ret = m_thread_ready.then([this, self = std::move(self), fn = std::forward(fn), ready_fulfiller = kj::mv(ready.fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable { - auto result = kj::newPromiseAndFulfiller(); // Signaled when fn() is called, with its return value. - bool posted = m_thread_context.waiter->post([this, self = std::move(self), fn = std::forward(fn), ready_fulfiller = kj::mv(ready_fulfiller), result_fulfiller = kj::mv(result.fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable { - // Fulfill ready.promise now, as soon as the Waiter starts executing - // this lambda, so the next ProxyServer::post() call can - // immediately call waiter->post(). It is important to do this - // before calling fn() because fn() can make an IPC call back to the - // client, which can make another IPC call to this server thread. - // (This typically happens when IPC methods take std::function - // parameters.) When this happens the second call to the server - // thread should not be blocked waiting for the first call. - m_loop->sync([ready_fulfiller = kj::mv(ready_fulfiller)]() mutable { - ready_fulfiller->fulfill(); - ready_fulfiller = nullptr; - }); - std::optional result_value; - kj::Maybe exception{kj::runCatchingExceptions([&]{ result_value.emplace(fn(*cancel_monitor_ptr)); })}; - m_loop->sync([this, &result_value, &exception, self = kj::mv(self), result_fulfiller = kj::mv(result_fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable { - // Destroy CancelMonitor here before fulfilling or rejecting the - // promise so it doesn't get triggered when the promise is - // destroyed. - cancel_monitor_ptr = nullptr; - // Send results to the fulfiller. Technically it would be ok to - // skip this if promise was canceled, but it's simpler to just - // do it unconditionally. - KJ_IF_MAYBE(e, exception) { - assert(!result_value); - result_fulfiller->reject(kj::mv(*e)); - } else { - assert(result_value); - result_fulfiller->fulfill(kj::mv(*result_value)); - result_value.reset(); - } - result_fulfiller = nullptr; - // Use evalLater to destroy the ProxyServer self - // reference, if it is the last reference, because the - // ProxyServer destructor needs to join the thread, - // which can't happen until this sync() block has exited. - m_loop->m_task_set->add(kj::evalLater([self = kj::mv(self)] {})); - }); - }); - // Assert that calling Waiter::post did not fail. It could only return - // false if a new function was posted before the previous one finished - // executing, but new functions are only posted when m_thread_ready is - // signaled, so this should never happen. - assert(posted); - return kj::mv(result.promise); - }).attach(kj::heap(cancel_monitor)); - m_thread_ready = kj::mv(ready.promise); - return ret; -} - -//! 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 -std::unique_ptr> ConnectStream(EventLoop& loop, int fd) -{ - typename InitInterface::Client init_client(nullptr); - std::unique_ptr connection; - loop.sync([&] { - auto stream = - loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP); - connection = std::make_unique(loop, kj::mv(stream)); - init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs(); - Connection* connection_ptr = connection.get(); - connection->onDisconnect([&loop, connection_ptr] { - MP_LOG(loop, Log::Warning) << "IPC client: unexpected network disconnect."; - delete connection_ptr; - }); - }); - return std::make_unique>( - 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 -void _Serve(EventLoop& loop, kj::Own&& 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>(std::shared_ptr(&init, [](InitImpl*){}), connection); - }); - auto it = loop.m_incoming_connections.begin(); - MP_LOG(loop, Log::Info) << "IPC server: socket connected."; - it->onDisconnect([&loop, it] { - MP_LOG(loop, Log::Info) << "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 -void _Listen(EventLoop& loop, kj::Own&& listener, InitImpl& init) -{ - auto* ptr = listener.get(); - loop.m_task_set->add(ptr->accept().then( - [&loop, &init, listener = kj::mv(listener)](kj::Own&& stream) mutable { - _Serve(loop, kj::mv(stream), init); - _Listen(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 -void ServeStream(EventLoop& loop, int fd, InitImpl& init) -{ - _Serve( - 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 -void ListenConnections(EventLoop& loop, int fd, InitImpl& init) -{ - loop.sync([&]() { - _Listen(loop, - loop.m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), - init); - }); -} - -extern thread_local ThreadContext g_thread_context; // NOLINT(bitcoin-nontrivial-threadlocal) -// Silence nonstandard bitcoin tidy error "Variable with non-trivial destructor -// cannot be thread_local" which should not be a problem on modern platforms, and -// could lead to a small memory leak at worst on older ones. - -} // namespace mp - -#endif // MP_PROXY_IO_H diff --git a/include/mp/proxy-types.h b/include/mp/proxy-types.h deleted file mode 100644 index 1ccf423f..00000000 --- a/include/mp/proxy-types.h +++ /dev/null @@ -1,844 +0,0 @@ -// Copyright (c) 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 - -#include -#include -#include -#include -#include - -namespace mp { - -template -class ValueField -{ -public: - ValueField(Value& value) : m_value(value) {} - ValueField(Value&& value) : m_value(value) {} - Value& m_value; - - const Value& get() const { return m_value; } - Value& get() { return m_value; } - Value& init() { return m_value; } - bool has() const { return true; } -}; - -template -struct StructField -{ - template - StructField(S& struct_) : m_struct(struct_) - { - } - Struct& m_struct; - - decltype(auto) get() const { return Accessor::get(this->m_struct); } - - bool has() const { - if constexpr (Accessor::optional) { - return Accessor::getHas(m_struct); - } else if constexpr (Accessor::boxed) { - return Accessor::has(m_struct); - } else { - return true; - } - } - - bool want() const { - if constexpr (Accessor::requested) { - return Accessor::getWant(m_struct); - } else { - return true; - } - } - - template decltype(auto) set(Args &&...args) const { - return Accessor::set(this->m_struct, std::forward(args)...); - } - - template decltype(auto) init(Args &&...args) const { - return Accessor::init(this->m_struct, std::forward(args)...); - } - - void setHas() const { - if constexpr (Accessor::optional) { - Accessor::setHas(m_struct); - } - } - - void setWant() const { - if constexpr (Accessor::requested) { - Accessor::setWant(m_struct); - } - } -}; - - - -// 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, make_shared, emplace or just return a -// temporary string that is moved from. -template -struct ReadDestEmplace -{ - ReadDestEmplace(TypeList, EmplaceFn emplace_fn) : m_emplace_fn(std::move(emplace_fn)) {} - - //! Simple case. If ReadField implementation calls this construct() method - //! with constructor arguments, just pass them on to the emplace function. - template - decltype(auto) construct(Args&&... args) - { - return m_emplace_fn(std::forward(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 - decltype(auto) update(UpdateFn&& update_fn) - { - if constexpr (std::is_const_v>>) { - // 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 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 -auto ReadDestTemp() -{ - return ReadDestEmplace{TypeList(), [](auto&&... args) -> decltype(auto) { - return LocalType{std::forward(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 -struct ReadDestUpdate -{ - ReadDestUpdate(Value& value) : m_value(value) {} - - //! Simple case. If ReadField works by calling update() just forward arguments to update_fn. - template - 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 - Value& construct(Args&&... args) - { - m_value.~Value(); - new (&m_value) Value(std::forward(args)...); - return m_value; - } - - Value& m_value; -}; - -//! Return whether to read a C++ value from a Cap'n Proto field. Returning -//! false can be useful to interpret certain Cap'n Proto field values as null -//! C++ values when initializing nullable C++ std::optional / std::unique_ptr / -//! std::shared_ptr types. -//! -//! For example, when reading from a `List(Data)` field into a -//! `std::vector>` value, it's useful to be -//! able to interpret empty `Data` values as null pointers. This is useful -//! because the Cap'n Proto C++ API does not currently provide a way to -//! distinguish between null and empty Data values in a List[*], so we need to -//! choose some Data value to represent null if we want to allow passing null -//! pointers. Since no CTransaction is ever serialized as empty Data, it's safe -//! to use empty Data values to represent null pointers. -//! -//! [*] The Cap'n Proto wire format actually does distinguish between null and -//! empty Data values inside Lists, and the C++ API does allow distinguishing -//! between null and empty Data values in other contexts, just not the List -//! context, so this limitation could be removed in the future. -//! -//! Design note: CustomHasField() and CustomHasValue() are inverses of each -//! other. CustomHasField() allows leaving Cap'n Proto fields unset when C++ -//! types have certain values, and CustomHasValue() allows leaving C++ values -//! unset when Cap'n Proto fields have certain values. But internally the -//! functions get called in different ways. This is because in C++, unlike in -//! Cap'n Proto not every C++ type is default constructible, and it may be -//! impossible to leave certain C++ values unset. For example if a C++ method -//! requires function parameters, there's no way to call the function without -//! constructing values for each of the parameters. Similarly there's no way to -//! add values to C++ vectors or maps without initializing those values. This -//! is not the case in Cap'n Proto where all values are optional and it's -//! possible to skip initializing parameters and list elements. -//! -//! Because of this difference, CustomHasValue() works universally and can be -//! used to disable BuildField() calls in every context, while CustomHasField() -//! can only be used to disable ReadField() calls in certain contexts like -//! std::optional and pointer contexts. -template -bool CustomHasField(TypeList, InvokeContext& invoke_context, const Input& input) -{ - return input.has(); -} - -template -decltype(auto) ReadField(TypeList, InvokeContext& invoke_context, Input&& input, Args&&... args) -{ - return CustomReadField(TypeList...>(), Priority<2>(), invoke_context, std::forward(input), std::forward(args)...); -} - -template -void ThrowField(TypeList, InvokeContext& invoke_context, Input&& input) -{ - ReadField( - TypeList(), invoke_context, input, ReadDestEmplace(TypeList(), - [](auto&& ...args) -> const LocalType& { throw LocalType{std::forward(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 -void ThrowField(TypeList, InvokeContext& invoke_context, Input&& input) -{ - auto data = input.get(); - throw std::runtime_error(std::string(CharCast(data.begin()), data.size())); -} - -//! Return whether to write a C++ value into a Cap'n Proto field. Returning -//! false can be useful to map certain C++ values to unset Cap'n Proto fields. -//! -//! For example the bitcoin `Coin` class asserts false when a spent coin is -//! serialized. But some C++ methods return these coins, so there needs to be a -//! way to represent them in Cap'n Proto and a null Data field is a convenient -//! representation. -template -bool CustomHasValue(InvokeContext& invoke_context, const Values&... value) -{ - return true; -} - -template -void BuildField(TypeList, Context& context, Output&& output, Values&&... values) -{ - if (CustomHasValue(context, values...)) { - CustomBuildField(TypeList(), Priority<3>(), context, std::forward(values)..., - std::forward(output)); - } -} - -// Adapter that allows BuildField overloads to work with, set, and initialize 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 maybe this could -// go away or be simplified, because there would no longer be a need to return -// ListOutput method pointers emulating capnp struct method pointers. -template -struct ListOutput; - -template -struct ListOutput<::capnp::List> -{ - using Builder = typename ::capnp::List::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 decltype(auto) set(Arg&& arg) const { return static_cast(this->m_builder).set(m_index, std::forward(arg)); } - template decltype(auto) init(Arg&& arg) const { return static_cast(this->m_builder).init(m_index, std::forward(arg)); } - // clang-format on -}; - -template -void BuildList(TypeList, InvokeContext& invoke_context, Output&& output, Value&& value) -{ - auto list = output.init(value.size()); - size_t i = 0; - for (const auto& elem : value) { - BuildField(TypeList(), invoke_context, ListOutput(list, i), elem); - ++i; - } -} - -template -void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) -{ - output.set(BuildPrimitive(invoke_context, std::forward(value), TypeList())); -} - -//! PassField override for callable interface reference arguments. -template -auto PassField(Priority<1>, TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) - -> Require -{ - // 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(params); - using Interface = typename Decay::Calls; - auto param = std::make_unique>(input.get(), server_context.proxy_server.m_context.connection, false); - fn.invoke(server_context, std::forward(args)..., *param); -} - -template -void MaybeBuildField(std::true_type, Args&&... args) -{ - BuildField(std::forward(args)...); -} -template -void MaybeBuildField(std::false_type, Args&&...) -{ -} -template -void MaybeReadField(std::true_type, Args&&... args) -{ - ReadField(std::forward(args)...); -} -template -void MaybeReadField(std::false_type, Args&&...) -{ -} - -template -void MaybeSetWant(TypeList, Priority<1>, const Value& value, Output&& output) -{ - if (value) { - output.setWant(); - } -} - -template -void MaybeSetWant(LocalTypes, Priority<0>, const Args&...) -{ -} - -//! Default PassField implementation calling MaybeReadField/MaybeBuildField. -template -void PassField(Priority<0>, TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) -{ - InvokeContext& invoke_context = server_context; - using ArgType = RemoveCvRef; - std::optional param; - const auto& params = server_context.call_context.getParams(); - MaybeReadField(std::integral_constant(), TypeList(), invoke_context, - Make(params), ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - param.emplace(std::forward(args)...); - return *param; - })); - if constexpr (Accessor::in) { - assert(param); - } else { - if (!param) param.emplace(); - } - fn.invoke(server_context, std::forward(args)..., static_cast(*param)); - auto&& results = server_context.call_context.getResults(); - MaybeBuildField(std::integral_constant(), TypeList(), invoke_context, - Make(results), *param); -} - -//! Default PassField implementation for count(0) arguments, calling ReadField/BuildField -template -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(params); - ReadField(TypeList<>(), server_context, input); - fn.invoke(server_context, std::forward(args)...); - auto&& results = server_context.call_context.getResults(); - BuildField(TypeList<>(), server_context, Make(results)); -} - -template -struct IterateFieldsHelper -{ - template - void handleChain(Arg1& arg1, Arg2& arg2, ParamList, NextFn&& next_fn, NextFnArgs&&... next_fn_args) - { - using S = Split; - handleChain(arg1, arg2, typename S::First()); - next_fn.handleChain(arg1, arg2, typename S::Second(), - std::forward(next_fn_args)...); - } - - template - void handleChain(Arg1& arg1, Arg2& arg2, ParamList) - { - static_cast(this)->handleField(arg1, arg2, ParamList()); - } -private: - IterateFieldsHelper() = default; - friend Derived; -}; - -struct IterateFields : IterateFieldsHelper -{ - template - void handleField(Arg1&&, Arg2&&, ParamList) - { - } -}; - -template -struct ClientException -{ - struct BuildParams : IterateFieldsHelper - { - template - void handleField(InvokeContext& invoke_context, Params& params, ParamList) - { - } - - BuildParams(ClientException* client_exception) : m_client_exception(client_exception) {} - ClientException* m_client_exception; - }; - - struct ReadResults : IterateFieldsHelper - { - template - void handleField(InvokeContext& invoke_context, Results& results, ParamList) - { - StructField input(results); - if (CustomHasField(TypeList(), invoke_context, input)) { - ThrowField(TypeList(), invoke_context, input); - } - } - - ReadResults(ClientException* client_exception) : m_client_exception(client_exception) {} - ClientException* m_client_exception; - }; -}; - -template -struct ClientParam -{ - ClientParam(Types&&... values) : m_values{std::forward(values)...} {} - - struct BuildParams : IterateFieldsHelper - { - template - void handleField(ClientInvokeContext& invoke_context, Params& params, ParamList) - { - auto const fun = [&](Values&&... values) { - MaybeSetWant( - ParamList(), Priority<1>(), values..., Make(params)); - MaybeBuildField(std::integral_constant(), ParamList(), invoke_context, - Make(params), std::forward(values)...); - }; - - // Note: The m_values tuple just consists of lvalue and rvalue - // references, so calling std::move doesn't change the tuple, it - // just causes std::apply to call the std::get overload that returns - // && instead of &, so rvalue references are preserved and not - // turned into lvalue references. This allows the BuildField call to - // move from the argument if it is an rvalue reference or was passed - // by value. - std::apply(fun, std::move(m_client_param->m_values)); - } - - BuildParams(ClientParam* client_param) : m_client_param(client_param) {} - ClientParam* m_client_param; - }; - - struct ReadResults : IterateFieldsHelper - { - template - void handleField(ClientInvokeContext& invoke_context, Results& results, TypeList) - { - auto const fun = [&](Values&&... values) { - MaybeReadField(std::integral_constant(), TypeList...>(), invoke_context, - Make(results), ReadDestUpdate(values)...); - }; - - std::apply(fun, m_client_param->m_values); - } - - ReadResults(ClientParam* client_param) : m_client_param(client_param) {} - ClientParam* m_client_param; - }; - - std::tuple m_values; -}; - -template -ClientParam MakeClientParam(Types&&... values) -{ - return {std::forward(values)...}; -} - -struct ServerCall -{ - // FIXME: maybe call call_context.releaseParams() - template - decltype(auto) invoke(ServerContext& server_context, TypeList<>, Args&&... args) const - { - // If cancel_lock is set, release it while executing the method, and - // reacquire it afterwards. The lock is needed to prevent params and - // response structs from being deleted by the event loop thread if the - // request is canceled, so it is only needed before and after method - // execution. It is important to release the lock during execution - // because the method can take arbitrarily long to return and the event - // loop will need the lock itself in on_cancel if the call is canceled. - if (server_context.cancel_lock) server_context.cancel_lock->m_lock.unlock(); - return TryFinally( - [&]() -> decltype(auto) { - return ProxyServerMethodTraits< - typename decltype(server_context.call_context.getParams())::Reads - >::invoke(server_context, std::forward(args)...); - }, - [&] { - if (server_context.cancel_lock) server_context.cancel_lock->m_lock.lock(); - // If the IPC request was canceled, throw InterruptException - // because there is no point continuing and trying to fill the - // call_context.getResults() struct. It's also important to stop - // executing because the connection may have been destroyed as - // described in https://github.com/bitcoin/bitcoin/issues/34250 - // and there could be invalid references to the destroyed - // Connection object if this continued. - // If the IPC method itself threw an exception, the - // InterruptException thrown below will take precedence over it. - // Since the call has been canceled that exception can't be - // returned to the caller, so it needs to be discarded like - // other result values. - if (server_context.request_canceled) throw InterruptException{"canceled"}; - }); - } -}; - -struct ServerDestroy -{ - template - void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const - { - server_context.proxy_server.invokeDestroy(std::forward(args)...); - } -}; - -template -struct ServerRet : Parent -{ - ServerRet(Parent parent) : Parent(parent) {} - - template - void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const - { - auto&& result = Parent::invoke(server_context, TypeList<>(), std::forward(args)...); - auto&& results = server_context.call_context.getResults(); - InvokeContext& invoke_context = server_context; - BuildField(TypeList(), invoke_context, Make(results), - std::forward(result)); - } -}; - -template -struct ServerExcept : Parent -{ - ServerExcept(Parent parent) : Parent(parent) {} - - template - void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const - { - try { - return Parent::invoke(server_context, TypeList<>(), std::forward(args)...); - } catch (const Exception& exception) { - auto&& results = server_context.call_context.getResults(); - BuildField(TypeList(), server_context, Make(results), exception); - } - } -}; - -//! Helper for CustomPassField below. Call Accessor::get method if it has one, -//! otherwise return capnp::Void. -template -decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr) -{ - return Accessor::get(message); -} - -template -::capnp::Void MaybeGet(...) -{ - return {}; -} - -template -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 -auto PassField(Priority<2>, Args&&... args) -> decltype(CustomPassField(std::forward(args)...)) -{ - return CustomPassField(std::forward(args)...); -}; - -template -struct ServerField : Parent -{ - ServerField(Parent parent) : Parent(parent) {} - - const Parent& parent() const { return *this; } - - template - decltype(auto) invoke(ServerContext& server_context, ArgTypes, Args&&... args) const - { - return PassField(Priority<2>(), - typename Split::First(), - server_context, - this->parent(), - typename Split::Second(), - std::forward(args)...); - } -}; - -template -ServerField MakeServerField(Parent parent) -{ - return {parent}; -} - -template -struct CapRequestTraits; - -template -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 -void clientDestroy(Client& client) -{ - MP_LOG(*client.m_context.loop, Log::Debug) << "IPC client destroy " << CxxTypeName(client); -} - -template -void serverDestroy(Server& server) -{ - MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server destroy " << CxxTypeName(server); -} - -//! Entry point called by generated client code that looks like: -//! -//! ProxyClient::M0::Result ProxyClient::methodName(M0::Param<0> arg0, M0::Param<1> arg1) { -//! typename M0::Result result; -//! clientInvoke(*this, &InterfaceName::Client::methodNameRequest, MakeClientParam<...>(M0::Fwd<0>(arg0)), MakeClientParam<...>(M0::Fwd<1>(arg1)), MakeClientParam<...>(result)); -//! return result; -//! } -//! -//! Ellipses above are where generated Accessor<> type declarations are inserted. -template -void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields) -{ - if (!g_thread_context.waiter) { - assert(g_thread_context.thread_name.empty()); - g_thread_context.thread_name = ThreadName(proxy_client.m_context.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(); - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Info) - << "{" << g_thread_context.thread_name - << "} IPC client first request from current thread, constructing waiter"; - } - ThreadContext& thread_context{g_thread_context}; - std::optional invoke_context; // Must outlive waiter->wait() call below - std::exception_ptr exception; - std::string kj_exception; - bool done = false; - const char* disconnected = nullptr; - proxy_client.m_context.loop->sync([&]() { - if (!proxy_client.m_context.connection) { - const Lock lock(thread_context.waiter->m_mutex); - done = true; - disconnected = "IPC client method called after disconnect."; - thread_context.waiter->m_cv.notify_all(); - return; - } - - auto request = (proxy_client.m_client.*get_request)(nullptr); - using Request = CapRequestTraits; - using FieldList = typename ProxyClientMethodTraits::Fields; - invoke_context.emplace(*proxy_client.m_context.connection, thread_context); - IterateFields().handleChain(*invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...); - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Debug) - << "{" << thread_context.thread_name << "} IPC client send " - << TypeName(); - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Trace) - << "send data: " << LogEscape(request.toString(), proxy_client.m_context.loop->m_log_opts.max_chars); - - proxy_client.m_context.loop->m_task_set->add(request.send().then( - [&](::capnp::Response&& response) { - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Debug) - << "{" << thread_context.thread_name << "} IPC client recv " - << TypeName(); - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Trace) - << "recv data: " << LogEscape(response.toString(), proxy_client.m_context.loop->m_log_opts.max_chars); - try { - IterateFields().handleChain( - *invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...); - } catch (...) { - exception = std::current_exception(); - } - const Lock lock(thread_context.waiter->m_mutex); - done = true; - thread_context.waiter->m_cv.notify_all(); - }, - [&](const ::kj::Exception& e) { - if (e.getType() == ::kj::Exception::Type::DISCONNECTED) { - disconnected = "IPC client method call interrupted by disconnect."; - } else { - kj_exception = kj::str("kj::Exception: ", e).cStr(); - MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Info) - << "{" << thread_context.thread_name << "} IPC client exception " << kj_exception; - } - const Lock lock(thread_context.waiter->m_mutex); - done = true; - thread_context.waiter->m_cv.notify_all(); - })); - }); - - Lock lock(thread_context.waiter->m_mutex); - thread_context.waiter->wait(lock, [&done]() { return done; }); - if (exception) std::rethrow_exception(exception); - if (!kj_exception.empty()) MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Raise) << kj_exception; - if (disconnected) MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Raise) << disconnected; -} - -//! 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 -auto ReplaceVoid(Fn&& fn, Ret&& ret) -{ - if constexpr (std::is_same_v) { - fn(); - return ret(); - } else { - return fn(); - } -} - -extern std::atomic server_reqs; - -//! Entry point called by generated server code that looks like: -//! -//! kj::Promise ProxyServer::methodName(CallContext call_context) { -//! return serverInvoke(*this, call_context, MakeServerField<0, ...>(MakeServerField<1, ...>(Make(ServerCall())))); -//! } -//! -//! Ellipses above are where generated Accessor<> type declarations are inserted. -template -kj::Promise 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; - - EventLoop& loop = *server.m_context.loop; - int req = ++server_reqs; - MP_LOG(loop, Log::Debug) << "IPC server recv request #" << req << " " - << TypeName(); - MP_LOG(loop, Log::Trace) << "request data: " - << LogEscape(params.toString(), server.m_context.loop->m_log_opts.max_chars); - - try { - using ServerContext = ServerInvokeContext; - using ArgList = typename ProxyClientMethodTraits::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(kj::mv(call_context)); }) - .then([&loop, req](CallContext call_context) { - MP_LOG(loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName(); - MP_LOG(loop, Log::Trace) << "response data: " - << LogEscape(call_context.getResults().toString(), loop.m_log_opts.max_chars); - }).catch_([&loop, req](::kj::Exception&& e) -> kj::Promise { - // Call failed for some reason. Cap'n Proto will try to send - // this error to the client as well, but it is good to log the - // failure early here and include the request number. - MP_LOG(loop, Log::Error) << "IPC server error request #" << req << " " << TypeName() - << " " << kj::str("kj::Exception: ", e.getDescription()).cStr(); - return kj::mv(e); - }); - } catch (const std::exception& e) { - MP_LOG(loop, Log::Error) << "IPC server unhandled exception: " << e.what(); - throw; - } catch (...) { - MP_LOG(loop, Log::Error) << "IPC server unhandled exception"; - throw; - } -} - -//! Map to convert client interface pointers to ProxyContext struct references -//! at runtime using typeids. -struct ProxyTypeRegister { - template - ProxyTypeRegister(TypeList) { - types().emplace(typeid(Interface), [](void* iface) -> ProxyContext& { return static_cast::Client&>(*static_cast(iface)).m_context; }); - } - using Types = std::map; - static Types& types() { static Types types; return types; } -}; - -} // namespace mp - -#endif // MP_PROXY_TYPES_H diff --git a/include/mp/proxy.capnp b/include/mp/proxy.capnp deleted file mode 100644 index 386f8f7a..00000000 --- a/include/mp/proxy.capnp +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 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. -} diff --git a/include/mp/proxy.h b/include/mp/proxy.h deleted file mode 100644 index 4458ee9e..00000000 --- a/include/mp/proxy.h +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright (c) 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 - -#include -#include -#include -#include -#include -#include -#include -#include -#include // IWYU pragma: keep - -namespace mp { -class Connection; -class EventLoop; -//! Mapping from capnp interface type to proxy client implementation (specializations are generated by -//! proxy-codegen.cpp). -template struct ProxyClient; // IWYU pragma: export -//! Mapping from capnp interface type to proxy server implementation (specializations are generated by -//! proxy-codegen.cpp). -template struct ProxyServer; // IWYU pragma: export -//! Mapping from capnp method params type to method traits (specializations are generated by proxy-codegen.cpp). -template struct ProxyMethod; // IWYU pragma: export -//! Mapping from capnp struct type to struct traits (specializations are generated by proxy-codegen.cpp). -template struct ProxyStruct; // IWYU pragma: export -//! Mapping from local c++ type to capnp type and traits (specializations are generated by proxy-codegen.cpp). -template struct ProxyType; // IWYU pragma: export - -using CleanupList = std::list>; -using CleanupIt = typename CleanupList::iterator; - -inline void CleanupRun(CleanupList& fns) { - while (!fns.empty()) { - auto fn = std::move(fns.front()); - fns.pop_front(); - fn(); - } -} - -//! Event loop smart pointer automatically managing m_num_clients. -//! If a lock pointer argument is passed, the specified lock will be used, -//! otherwise EventLoop::m_mutex will be locked when needed. -class EventLoopRef -{ -public: - explicit EventLoopRef(EventLoop& loop, Lock* lock = nullptr); - EventLoopRef(EventLoopRef&& other) noexcept : m_loop(other.m_loop) { other.m_loop = nullptr; } - EventLoopRef(const EventLoopRef&) = delete; - EventLoopRef& operator=(const EventLoopRef&) = delete; - EventLoopRef& operator=(EventLoopRef&&) = delete; - ~EventLoopRef() { reset(); } - EventLoop& operator*() const { assert(m_loop); return *m_loop; } - EventLoop* operator->() const { assert(m_loop); return m_loop; } - void reset(bool relock=false); - - EventLoop* m_loop{nullptr}; - Lock* m_lock{nullptr}; -}; - -//! Context data associated with proxy client and server classes. -struct ProxyContext -{ - Connection* connection; - EventLoopRef loop; - CleanupList cleanup_fns; - - ProxyContext(Connection* connection); -}; - -//! Base class for generated ProxyClient classes that implement a C++ interface -//! and forward calls to a capnp interface. -template -class ProxyClientBase : public Impl_ -{ -public: - using Interface = Interface_; - using Impl = Impl_; - using Sub = ProxyClient; - using Super = ProxyClientBase; - - //! Construct libmultiprocess client object wrapping Cap'n Proto client - //! object with a reference to the associated mp::Connection object. - //! - //! The destroy_connection option determines whether destroying this client - //! object closes the connection. It is set to true for the - //! ProxyClient object returned by ConnectStream, to let IPC - //! clients close the connection by freeing the object. It is false for - //! other client objects so they can be destroyed without affecting the - //! connection. - 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 -class ProxyClientCustom : public ProxyClientBase -{ - using ProxyClientBase::ProxyClientBase; -}; - -//! Base class for generated ProxyServer classes that implement capnp server -//! methods and forward calls to a wrapped c++ implementation class. -template -struct ProxyServerBase : public virtual Interface_::Server -{ -public: - using Interface = Interface_; - using Impl = Impl_; - - ProxyServerBase(std::shared_ptr impl, Connection& connection); - virtual ~ProxyServerBase(); - void invokeDestroy(); - using Interface_::Server::thisCap; - - /** - * 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 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 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 -struct ProxyServerCustom : public ProxyServerBase -{ - using ProxyServerBase::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 - helper to access individual parameter types by index number. -//! Fwd - helper to forward arguments by index number. -//! Fields - helper alias that appends Result type to the Params typelist if -//! it not void. -template -struct FunctionTraits; - -//! Specialization of above extracting result and params types assuming the -//! template argument is a pointer-to-method type, -//! decltype(&ClassName::methodName) -template -struct FunctionTraits<_Result (_Class::*const)(_Params...)> -{ - using Params = TypeList<_Params...>; - using Result = _Result; - template - using Param = typename std::tuple_element>::type; - using Fields = - std::conditional_t, Params, TypeList<_Params..., _Result>>; - - //! Enable perfect forwarding for clientInvoke calls. If parameter is a - //! value type or rvalue reference type, pass it as an rvalue-reference to - //! MakeClientParam and BuildField calls so it can be moved from, and if it - //! is an lvalue reference, pass it an lvalue reference so it won't be moved - //! from. This method does the same thing as std::forward except it takes a - //! parameter number instead of a type as a template argument, so generated - //! code calling this can be less repetitive and verbose. - template - static decltype(auto) Fwd(Param& arg) { return static_cast&&>(arg); } -}; - -//! 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 -struct ProxyMethodTraits -{ - using Params = TypeList<>; - using Result = void; - using Fields = Params; - - template - static void invoke(ServerContext&) - { - } -}; - -//! Specialization of above for proxy methods that have a -//! ProxyMethod::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 -//! { -//! static constexpr auto impl = &ClassName::methodName; -//! }; -template -struct ProxyMethodTraits::impl)>> - : public FunctionTraits::impl)> -{ - template - static decltype(auto) invoke(ServerContext& server_context, Args&&... args) - { - return (server_context.proxy_server.m_impl.get()->*ProxyMethod::impl)(std::forward(args)...); - } -}; - -//! Customizable (through template specialization) traits class used in generated ProxyClient implementations from -//! proxy-codegen.cpp. -template -struct ProxyClientMethodTraits : public ProxyMethodTraits -{ -}; - -//! Customizable (through template specialization) traits class used in generated ProxyServer implementations from -//! proxy-codegen.cpp. -template -struct ProxyServerMethodTraits : public ProxyMethodTraits -{ -}; - -static constexpr int FIELD_IN = 1; //!< See Accessor::in. -static constexpr int FIELD_OUT = 2; //!< See Accessor::out. -static constexpr int FIELD_OPTIONAL = 4; //!< See Accessor::optional. -static constexpr int FIELD_REQUESTED = 8; //!< See Accessor::requested. -static constexpr int FIELD_BOXED = 16; //!< See Accessor::boxed. - -//! Accessor type holding flags that determine how to access a message field. -template -struct Accessor : public Field -{ - //! Field is present from the Cap'n Proto Params struct (client -> server). - static const bool in = flags & FIELD_IN; - //! Field is present from the Cap'n Proto Results struct (server -> client). - static const bool out = flags & FIELD_OUT; - //! Field has a companion has{Name} boolean field in the Cap'n Proto struct. - //! This is used to represent optional primitive values (e.g. C++ - //! std::optional) because Cap'n Proto doesn't allow primitive fields to - //! be unset. - static const bool optional = flags & FIELD_OPTIONAL; - //! Results field has a companion want{Name} boolean field in the Params - //! struct. Used for optional output parameters (e.g. C++ int*) and set to - //! true if the caller passed a non-null pointer and wants the result. - static const bool requested = flags & FIELD_REQUESTED; - //! Field is a Cap'n Proto pointer type (struct, list, text, data, - //! interface) as opposed to a primitive type (bool, int, float, enum). - static const bool boxed = flags & FIELD_BOXED; -}; - -//! Wrapper around std::function for passing std::function objects between client and servers. -template -class ProxyCallback; - -//! Specialization of above to separate Result and Arg types. -template -class ProxyCallback> -{ -public: - virtual Result call(Args&&... args) = 0; -}; - -} // namespace mp - -#endif // MP_PROXY_H diff --git a/include/mp/type-char.h b/include/mp/type-char.h deleted file mode 100644 index b51ffeb1..00000000 --- a/include/mp/type-char.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList, - Priority<3>, - InvokeContext& invoke_context, - const unsigned char (&value)[size], - Output&& output) -{ - auto result = output.init(size); - memcpy(result.begin(), value, size); -} - -template -decltype(auto) CustomReadField(TypeList, - 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 diff --git a/include/mp/type-chrono.h b/include/mp/type-chrono.h deleted file mode 100644 index d7154986..00000000 --- a/include/mp/type-chrono.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 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 - -#include - -namespace mp { -//! Overload CustomBuildField and CustomReadField to serialize std::chrono -//! parameters and return values as numbers. -template -void CustomBuildField(TypeList>, Priority<1>, InvokeContext& invoke_context, Value&& value, - Output&& output) -{ - static_assert(std::numeric_limits::lowest() <= std::numeric_limits::lowest(), - "capnp type does not have enough range to hold lowest std::chrono::duration value"); - static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), - "capnp type does not have enough range to hold highest std::chrono::duration value"); - output.set(value.count()); -} - -template -decltype(auto) CustomReadField(TypeList>, Priority<1>, InvokeContext& invoke_context, - Input&& input, ReadDest&& read_dest) -{ - return read_dest.construct(input.get()); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_CHRONO_H diff --git a/include/mp/type-context.h b/include/mp/type-context.h deleted file mode 100644 index 3ab3d4b0..00000000 --- a/include/mp/type-context.h +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) 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 -#include - -#include - -namespace mp { -template -void CustomBuildField(TypeList<>, - Priority<1>, - ClientInvokeContext& invoke_context, - Output&& output, - typename std::enable_if::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( - GuardedRef{thread_context.waiter->m_mutex, thread_context.callback_threads}, &connection, - [&] { return connection.m_threads.add(kj::heap>(connection, 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( - GuardedRef{thread_context.waiter->m_mutex, thread_context.request_threads}, - &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 -auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) -> - typename std::enable_if< - std::is_same::value, - kj::Promise>::type -{ - auto& server = server_context.proxy_server; - EventLoop& loop = *server.m_context.loop; - int req = server_context.req; - // Keep a reference to the ProxyServer instance by assigning it to the self - // variable. ProxyServer instances are reference-counted and if the client - // drops its reference and the IPC call is canceled, this variable keeps the - // instance alive until the method finishes executing. The self variable - // needs to be destroyed on the event loop thread so it is freed in a sync() - // call below. - auto self = server.thisCap(); - auto invoke = [self = kj::mv(self), call_context = kj::mv(server_context.call_context), &server, &loop, req, fn, args...](CancelMonitor& cancel_monitor) mutable { - MP_LOG(loop, Log::Debug) << "IPC server executing request #" << req; - if (loop.testing_hook_async_request_start) loop.testing_hook_async_request_start(); - KJ_DEFER(if (loop.testing_hook_async_request_done) loop.testing_hook_async_request_done()); - ServerContext server_context{server, call_context, req}; - // 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; - ConnThread request_thread; - bool inserted{false}; - Mutex cancel_mutex; - Lock cancel_lock{cancel_mutex}; - server_context.cancel_lock = &cancel_lock; - loop.sync([&] { - // Detect request being canceled before it executes. - if (cancel_monitor.m_canceled) { - server_context.request_canceled = true; - return; - } - // Detect request being canceled while it executes. - assert(!cancel_monitor.m_on_cancel); - cancel_monitor.m_on_cancel = [&loop, &server_context, &cancel_mutex, req]() { - MP_LOG(loop, Log::Info) << "IPC server request #" << req << " canceled while executing."; - // Lock cancel_mutex here to block the event loop - // thread and prevent it from deleting the request's - // params and response structs while the execution - // thread is accessing them. Because this lock is - // released before the event loop thread does delete - // the structs, the mutex does not provide any - // protection from the event loop deleting the - // structs _before_ the execution thread acquires - // it. So in addition to locking the mutex, the - // execution thread always checks request_canceled - // as well before accessing the structs. - Lock cancel_lock{cancel_mutex}; - server_context.request_canceled = true; - }; - // Update requests_threads map if not canceled. We know - // the request is not canceled currently because - // cancel_monitor.m_canceled was checked above and this - // code is running on the event loop thread. - std::tie(request_thread, inserted) = SetThread( - GuardedRef{thread_context.waiter->m_mutex, request_threads}, server.m_context.connection, - [&] { return Accessor::get(call_context.getParams()).getCallbackThread(); }); - }); - - // If an entry was inserted into the request_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( - // Release the cancel lock before calling loop->sync and - // waiting for the event loop thread, because if a - // cancellation happened, it needs to run the on_cancel - // callback above. It's safe to release cancel_lock at - // this point because the fn.invoke() call below will be - // finished and no longer accessing the params or - // results structs. - cancel_lock.m_lock.unlock(); - // Erase the request_threads entry on the event loop - // thread with loop->sync(), so if the connection is - // broken there is not a race between this thread and - // the disconnect handler trying to destroy the thread - // client object. - loop.sync([&] { - // Clear cancellation callback. At this point the - // method invocation finished and the result is - // either being returned, or discarded if a - // cancellation happened. So we do not need to be - // notified of cancellations after this point. Also - // we do not want to be notified because - // cancel_mutex and server_context could be out of - // scope when it happens. - cancel_monitor.m_on_cancel = nullptr; - auto self_dispose{kj::mv(self)}; - if (erase_thread) { - // Look up the thread again without using existing - // iterator since entry may no longer be there after - // a disconnect. Destroy node after releasing - // Waiter::m_mutex, so the ProxyClient - // destructor is able to use EventLoop::mutex - // without violating lock order. - ConnThreads::node_type removed; - { - Lock lock(thread_context.waiter->m_mutex); - removed = request_threads.extract(server.m_context.connection); - } - } - }); - ); - if (server_context.request_canceled) { - MP_LOG(loop, Log::Info) << "IPC server request #" << req << " canceled before it could be executed"; - } else KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]{ - try { - fn.invoke(server_context, args...); - } catch (const InterruptException& e) { - MP_LOG(loop, Log::Info) << "IPC server request #" << req << " interrupted (" << e.what() << ")"; - } - })) { - MP_LOG(loop, Log::Error) << "IPC server request #" << req << " uncaught exception (" << kj::str(*exception).cStr() << ")"; - kj::throwRecoverableException(kj::mv(*exception)); - } - return call_context; - // End of scope: if KJ_DEFER was reached, it runs here - }; - - // 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(). - const auto& params = server_context.call_context.getParams(); - Context::Reader context_arg = Accessor::get(params); - auto thread_client = context_arg.getThread(); - auto result = server.m_context.connection->m_threads.getLocalServer(thread_client) - .then([&loop, invoke = kj::mv(invoke), req](const kj::Maybe& perhaps) mutable { - // 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) { - auto& thread = static_cast&>(*thread_server); - MP_LOG(loop, Log::Debug) - << "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}"; - return thread.template post(std::move(invoke)); - } else { - MP_LOG(loop, Log::Error) - << "IPC server error request #" << req << ", missing thread to execute request"; - throw std::runtime_error("invalid thread handle"); - } - }, [&loop, req](::kj::Exception&& e) -> kj::Promise { - // If you see the error "(remote):0: failed: remote exception: - // Called null capability" here, it probably means your Init class - // is missing a declaration like: - // - // construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); - // - // which passes a ThreadMap reference from the client to the server, - // allowing the server to create threads to run IPC calls on the - // client, and also returns a ThreadMap reference from the server to - // the client, allowing the client to create threads on the server. - // (Typically the latter ThreadMap is used more often because there - // are more client-to-server calls.) - // - // If the other side of the connection did not previously get a - // ThreadMap reference from this side of the connection, when the - // other side calls `m_thread_map.makeThreadRequest()` in - // `BuildField` above, `m_thread_map` will be null, but that call - // will not fail immediately due to Cap'n Proto's request pipelining - // and delayed execution. Instead that call will return an invalid - // Thread reference, and when that reference is passed to this side - // of the connection as `thread_client` above, the - // `getLocalServer(thread_client)` call there will be the first - // thing to overtly fail, leading to an error here. - // - // Potentially there are also other things that could cause errors - // here, but this is the most likely cause. - // - // The log statement here is not strictly necessary since the same - // exception will also be logged in serverInvoke, but this logging - // may provide extra context that could be helpful for debugging. - MP_LOG(loop, Log::Info) - << "IPC server error request #" << req << " CapabilityServerSet::getLocalServer call failed, did you forget to provide a ThreadMap to the client prior to this IPC call?"; - return kj::mv(e); - }); - // Use connection m_canceler object to cancel the result promise if the - // connection is destroyed. (By default Cap'n Proto does not cancel requests - // on disconnect, since it's possible clients might want to make requests - // and immediately disconnect without waiting for results, but not want the - // requests to be canceled.) - return server.m_context.connection->m_canceler.wrap(kj::mv(result)); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_CONTEXT_H diff --git a/include/mp/type-data.h b/include/mp/type-data.h deleted file mode 100644 index cdcdd30e..00000000 --- a/include/mp/type-data.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 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 - -#include -#include - -namespace mp { -template -concept IsSpanOf = - std::convertible_to> && - std::constructible_from; - -template -concept IsByteSpan = - IsSpanOf || - IsSpanOf || - IsSpanOf || - IsSpanOf; - -//! Generic ::capnp::Data field builder for any C++ type that can be converted -//! to a span of bytes, like std::vector or std::array, or custom -//! blob types like uint256 or PKHash with data() and size() methods pointing to -//! bytes. -template -void CustomBuildField(TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output) -requires (std::is_same_v && IsByteSpan) -{ - auto data = std::span{value}; - auto result = output.init(data.size()); - memcpy(result.begin(), data.data(), data.size()); -} - -template -decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) -requires (std::is_same_v && IsByteSpan) -{ - using ByteType = decltype(std::span{std::declval().begin(), std::declval().end()})::element_type; - const kj::byte *begin{input.get().begin()}, *end{input.get().end()}; - return read_dest.construct(reinterpret_cast(begin), reinterpret_cast(end)); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_DATA_H diff --git a/include/mp/type-decay.h b/include/mp/type-decay.h deleted file mode 100644 index 65934372..00000000 --- a/include/mp/type-decay.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList, - Priority<0>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} - -template -void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} - -template -void CustomBuildField(TypeList, - Priority<0>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_DECAY_H diff --git a/include/mp/type-exception.h b/include/mp/type-exception.h deleted file mode 100644 index 3f04d7a9..00000000 --- a/include/mp/type-exception.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - const std::exception& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::string(value.what())); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_EXCEPTION_H diff --git a/include/mp/type-function.h b/include/mp/type-function.h deleted file mode 100644 index 951e0885..00000000 --- a/include/mp/type-function.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -//! Adapter to convert ProxyCallback object call to function object call. -template -class ProxyCallbackImpl final : public ProxyCallback> -{ - using Fn = std::function; - Fn m_fn; - -public: - ProxyCallbackImpl(Fn fn) : m_fn(std::move(fn)) {} - Result call(Args&&... args) override { return m_fn(std::forward(args)...); } -}; - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - using Callback = ProxyCallbackImpl; - output.set(kj::heap>( - std::make_shared(std::forward(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(params)...) -template -struct ProxyCallFn -{ - InvokeContext m_proxy; - - template - decltype(auto) operator()(CallParams&&... params) { return this->m_proxy->call(std::forward(params)...); } -}; - -template -decltype(auto) CustomReadField(TypeList> types, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - if (CustomHasField(types, invoke_context, input)) { - using Interface = typename Decay::Calls; - auto client = std::make_shared>( - input.get(), &invoke_context.connection, /* destroy_connection= */ false); - return read_dest.construct(ProxyCallFn{std::move(client)}); - } - return read_dest.construct(); -}; -} // namespace mp - -#endif // MP_PROXY_TYPE_FUNCTION_H diff --git a/include/mp/type-interface.h b/include/mp/type-interface.h deleted file mode 100644 index a32c53d2..00000000 --- a/include/mp/type-interface.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -kj::Own MakeProxyServer(InvokeContext& context, std::shared_ptr impl) -{ - return kj::heap>(std::move(impl), context.connection); -} - -template -kj::Own CustomMakeProxyServer(InvokeContext& context, std::shared_ptr&& impl) -{ - return MakeProxyServer(context, std::move(impl)); -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename Decay::Calls* enable = nullptr) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - output.set(CustomMakeProxyServer(invoke_context, std::shared_ptr(value.release()))); - } -} - -template -void CustomBuildField(TypeList>, - Priority<2>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename Decay::Calls* enable = nullptr) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - output.set(CustomMakeProxyServer(invoke_context, std::forward(value))); - } -} - -template -void CustomBuildField(TypeList, - 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(invoke_context, std::shared_ptr(&value, [](Impl*){}))); -} - -template -std::unique_ptr MakeProxyClient(InvokeContext& context, typename Interface::Client&& client) -{ - return std::make_unique>( - std::move(client), &context.connection, /* destroy_connection= */ false); -} - -template -std::unique_ptr CustomMakeProxyClient(InvokeContext& context, typename Interface::Client&& client) -{ - return MakeProxyClient(context, kj::mv(client)); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename Decay::Calls* enable = nullptr) -{ - using Interface = typename Decay::Calls; - if (CustomHasField(TypeList(), invoke_context, input)) { - return read_dest.construct( - CustomMakeProxyClient(invoke_context, std::move(input.get()))); - } - return read_dest.construct(); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename Decay::Calls* enable = nullptr) -{ - using Interface = typename Decay::Calls; - if (CustomHasField(TypeList(), invoke_context, input)) { - return read_dest.construct( - CustomMakeProxyClient(invoke_context, std::move(input.get()))); - } - return read_dest.construct(); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_INTERFACE_H diff --git a/include/mp/type-map.h b/include/mp/type-map.h deleted file mode 100644 index 213ed811..00000000 --- a/include/mp/type-map.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 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 -#include -#include - -namespace mp { -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildList(TypeList>(), invoke_context, output, value); -} - -// Replacement for `m.emplace(piecewise_construct, t1, t2)` to work around a -// Clang 22 regression triggered by libc++'s std::map piecewise emplace: when -// the key constructor argument tuple is empty (std::tuple<>), libc++'s internal -// "try key extraction" SFINAE probe instantiates std::tuple_element<0, -// std::tuple<>>, which Clang 22 diagnoses as an out-of-bounds pack access ("a -// parameter pack may not be accessed at an out of bounds index") instead of -// treating it as substitution failure. See LLVM issue #167709 and the upstream -// fix in llvm/llvm-project PR #183614. -// https://github.com/llvm/llvm-project/issues/167709 -// https://github.com/llvm/llvm-project/pull/183614 -template -auto EmplacePiecewiseSafe( - Map& m, - const std::piecewise_construct_t&, - Tuple1&& t1, - Tuple2&& t2) -{ - if constexpr (std::tuple_size_v> == 0) { - // Avoid tuple<> / tuple<> (LLVM 22 libc++ regression path) - return m.emplace(std::piecewise_construct, - std::forward_as_tuple(typename Map::key_type{}), - std::forward(t2)); - } else { - return m.emplace(std::piecewise_construct, - std::forward(t1), - std::forward(t2)); - } -} - -template -decltype(auto) CustomReadField(TypeList>, - 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>(), invoke_context, - Make(item), - ReadDestEmplace( - TypeList>(), [&](auto&&... args) -> auto& { - return *EmplacePiecewiseSafe(value, std::forward(args)...).first; - })); - } - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_MAP_H diff --git a/include/mp/type-message.h b/include/mp/type-message.h deleted file mode 100644 index b1cec13d..00000000 --- a/include/mp/type-message.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 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 - -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 -void CustomBuildField(TypeList, 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 -decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Input&& input, - ReadDest&& read_dest, - decltype(CustomReadMessage(invoke_context, input.get(), - std::declval()))* enable = nullptr) -{ - return read_dest.update([&](auto& value) { if (CustomHasField(TypeList(), invoke_context, input)) CustomReadMessage(invoke_context, input.get(), value); }); -} - -//! Helper for CustomPassField below. Call Accessor::init method if it has one, -//! otherwise do nothing. -template -decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr) -{ - return Accessor::init(message); -} - -template -::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 -auto CustomPassField(TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) - -> decltype(CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), - MaybeGet(server_context.call_context.getResults()), nullptr)) -{ - CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), - MaybeInit(server_context.call_context.getResults()), - [&](LocalTypes... param) { fn.invoke(server_context, std::forward(args)..., param...); }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_MESSAGE_H diff --git a/include/mp/type-number.h b/include/mp/type-number.h deleted file mode 100644 index ddff5cdd..00000000 --- a/include/mp/type-number.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value>::type* enable = nullptr) -{ - using E = std::make_unsigned_t>; - using T = std::make_unsigned_t; - static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral/enum types"); - return static_cast(value); -} - -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value, int>::type* enable = nullptr) -{ - static_assert( - std::numeric_limits::lowest() <= std::numeric_limits::lowest(), "mismatched integral types"); - static_assert( - std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral types"); - return value; -} - -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value>::type* enable = nullptr) -{ - static_assert(std::is_same::value, - "mismatched floating point types. please fix message.capnp type declaration to match wrapped interface"); - return value; -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = nullptr) -{ - // Disable clang-tidy out-of-range enum value check which triggers when - // using an enum type that does not have a 0 value. The check correctly - // triggers when it detects that Cap'n Proto returns 0 when reading an - // integer field that is unset. But the warning is spurious because the - // corresponding BuildField call should never leave the field unset. - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - return read_dest.construct(static_cast(input.get())); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = nullptr) -{ - auto value = input.get(); - if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { - throw std::range_error("out of bound int received"); - } - return read_dest.construct(static_cast(value)); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = nullptr) -{ - auto value = input.get(); - static_assert(std::is_same::value, "floating point type mismatch"); - return read_dest.construct(value); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_NUMBER_H diff --git a/include/mp/type-optional.h b/include/mp/type-optional.h deleted file mode 100644 index 80a8f4f3..00000000 --- a/include/mp/type-optional.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - if (value) { - output.setHas(); - BuildField(TypeList(), invoke_context, output, *std::forward(value)); - } -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!CustomHasField(TypeList(), invoke_context, input)) { - value.reset(); - } else if (value) { - ReadField(TypeList(), invoke_context, input, ReadDestUpdate(*value)); - } else { - ReadField(TypeList(), invoke_context, input, - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value.emplace(std::forward(args)...); - return *value; - })); - } - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_OPTIONAL_H diff --git a/include/mp/type-pair.h b/include/mp/type-pair.h deleted file mode 100644 index b1914c9d..00000000 --- a/include/mp/type-pair.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -// FIXME: Overload on output type instead of value type and switch to std::get and merge with next overload -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto pair = output.init(); - using Accessors = typename ProxyStruct::Accessors; - BuildField(TypeList(), invoke_context, Make>(pair), value.first); - BuildField(TypeList(), invoke_context, Make>(pair), value.second); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - const auto& pair = input.get(); - using Accessors = typename ProxyStruct::Reads>::Accessors; - - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestEmplace(TypeList(), [&](auto&&... key_args) -> auto& { - KeyLocalType* key = nullptr; - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestEmplace(TypeList(), [&](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 diff --git a/include/mp/type-pointer.h b/include/mp/type-pointer.h deleted file mode 100644 index 192e1b03..00000000 --- a/include/mp/type-pointer.h +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList, Priority<3>, InvokeContext& invoke_context, Value&& value, Output&& output) -{ - if (value) { - BuildField(TypeList(), invoke_context, output, *value); - } -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - if (value) { - BuildField(TypeList(), invoke_context, output, *value); - } -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (value) { - ReadField(TypeList(), invoke_context, std::forward(input), ReadDestUpdate(*value)); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<0>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!CustomHasField(TypeList(), invoke_context, input)) { - value.reset(); - } else if (value) { - ReadField(TypeList(), invoke_context, input, ReadDestUpdate(*value)); - } else { - ReadField(TypeList(), invoke_context, input, - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value = std::make_shared(std::forward(args)...); - return *value; - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!CustomHasField(TypeList(), invoke_context, input)) { - value.reset(); - return; - } - ReadField(TypeList(), invoke_context, std::forward(input), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value = std::make_shared(std::forward(args)...); - return *value; - })); - }); -} - -//! PassField override for C++ pointer arguments. -template -void PassField(Priority<1>, TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) -{ - const auto& params = server_context.call_context.getParams(); - const auto& input = Make(params); - - if (!input.want()) { - fn.invoke(server_context, std::forward(args)..., nullptr); - return; - } - - InvokeContext& invoke_context = server_context; - Decay param; - - MaybeReadField(std::integral_constant(), TypeList(), invoke_context, input, - ReadDestUpdate(param)); - - fn.invoke(server_context, std::forward(args)..., ¶m); - - auto&& results = server_context.call_context.getResults(); - MaybeBuildField(std::integral_constant(), TypeList(), invoke_context, - Make(results), param); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_POINTER_H diff --git a/include/mp/type-set.h b/include/mp/type-set.h deleted file mode 100644 index f051f738..00000000 --- a/include/mp/type-set.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 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 -#include - -namespace mp { -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildList(TypeList(), invoke_context, output, value); -} - -template -decltype(auto) CustomReadField(TypeList>, - 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(), invoke_context, Make(item), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - return *value.emplace(std::forward(args)...).first; - })); - } - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_SET_H diff --git a/include/mp/type-string.h b/include/mp/type-string.h deleted file mode 100644 index d4c3383b..00000000 --- a/include/mp/type-string.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void CustomBuildField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto result = output.init(value.size()); - memcpy(result.begin(), value.data(), value.size()); -} - -template -decltype(auto) CustomReadField(TypeList, - 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 diff --git a/include/mp/type-struct.h b/include/mp/type-struct.h deleted file mode 100644 index 6d396387..00000000 --- a/include/mp/type-struct.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -void BuildOne(TypeList param, - InvokeContext& invoke_context, - Output&& output, - Value&& value, - typename std::enable_if < index::fields>::type * enable = nullptr) -{ - using Index = std::integral_constant; - using Struct = typename ProxyType::Struct; - using Accessor = typename std::tuple_element::Accessors>::type; - auto&& field_output = Make(output); - auto&& field_value = value.*ProxyType::get(Index()); - BuildField(TypeList>(), invoke_context, field_output, field_value); - BuildOne(param, invoke_context, output, value); -} - -template -void BuildOne(TypeList param, - InvokeContext& invoke_context, - Output&& output, - Value&& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ -} - -template -void CustomBuildField(TypeList local_type, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename ProxyType::Struct* enable = nullptr) -{ - BuildOne<0>(local_type, invoke_context, output.init(), value); -} - -template -void ReadOne(TypeList param, - InvokeContext& invoke_context, - Input&& input, - Value&& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ - using Index = std::integral_constant; - using Struct = typename ProxyType::Struct; - using Accessor = typename std::tuple_element::Accessors>::type; - const auto& struc = input.get(); - auto&& field_value = value.*ProxyType::get(Index()); - ReadField(TypeList>(), invoke_context, Make(struc), - ReadDestUpdate(field_value)); - ReadOne(param, invoke_context, input, value); -} - -template -void ReadOne(TypeList param, - InvokeContext& invoke_context, - Input& input, - Value& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ -} - -template -decltype(auto) CustomReadField(TypeList param, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename ProxyType::Struct* enable = nullptr) -{ - return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_STRUCT_H diff --git a/include/mp/type-threadmap.h b/include/mp/type-threadmap.h deleted file mode 100644 index 3005d9de..00000000 --- a/include/mp/type-threadmap.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template <> -struct ProxyServer final : public virtual ThreadMap::Server -{ -public: - ProxyServer(Connection& connection); - kj::Promise makeThread(MakeThreadContext context) override; - Connection& m_connection; -}; - -template -void CustomBuildField(TypeList<>, - Priority<1>, - InvokeContext& invoke_context, - Output&& output, - typename std::enable_if::value>::type* enable = nullptr) -{ - output.set(kj::heap>(invoke_context.connection)); -} - -template -decltype(auto) CustomReadField(TypeList<>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - typename std::enable_if::value>::type* enable = nullptr) -{ - invoke_context.connection.m_thread_map = input.get(); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_THREADMAP_H diff --git a/include/mp/type-tuple.h b/include/mp/type-tuple.h deleted file mode 100644 index 597ffbfb..00000000 --- a/include/mp/type-tuple.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto pair = output.init(); - using Accessors = typename ProxyStruct::Accessors; - BuildField(TypeList(), invoke_context, Make>(pair), std::get<0>(value)); - BuildField(TypeList(), invoke_context, Make>(pair), std::get<1>(value)); -} - -// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - const auto& pair = input.get(); - using Struct = ProxyStruct::Reads>; - using Accessors = typename Struct::Accessors; - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestUpdate(std::get<0>(value))); - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestUpdate(std::get<1>(value))); - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_TUPLE_H diff --git a/include/mp/type-unordered-set.h b/include/mp/type-unordered-set.h deleted file mode 100644 index ad4c0ab8..00000000 --- a/include/mp/type-unordered-set.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 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_UNORDERED_SET_H -#define MP_PROXY_TYPE_UNORDERED_SET_H - -#include -#include -#include - -namespace mp { -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildList(TypeList(), invoke_context, output, value); -} - -template -decltype(auto) CustomReadField(TypeList>, - 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(), invoke_context, Make(item), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - return *value.emplace(std::forward(args)...).first; - })); - } - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_UNORDERED_SET_H \ No newline at end of file diff --git a/include/mp/type-vector.h b/include/mp/type-vector.h deleted file mode 100644 index 648fda5d..00000000 --- a/include/mp/type-vector.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 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 -#include - -namespace mp { -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildList(TypeList(), invoke_context, output, value); -} - -inline static bool BuildPrimitive(InvokeContext& invoke_context, std::vector::const_reference value, TypeList) -{ - return value; -} - -template -decltype(auto) CustomReadField(TypeList>, - 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(), invoke_context, Make(item), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value.emplace_back(std::forward(args)...); - return value.back(); - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - 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(), invoke_context, Make(item), ReadDestTemp())); - } - }); -} -} // namespace mp - -#endif // MP_PROXY_TYPE_VECTOR_H diff --git a/include/mp/type-void.h b/include/mp/type-void.h deleted file mode 100644 index ed733985..00000000 --- a/include/mp/type-void.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 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 - -namespace mp { -template -::capnp::Void BuildPrimitive(InvokeContext& invoke_context, Value&&, TypeList<::capnp::Void>) -{ - return {}; -} - -template -void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, ::capnp::Void, Output&& output) -{ -} -} // namespace mp - -#endif // MP_PROXY_TYPE_VOID_H diff --git a/include/mp/util.h b/include/mp/util.h deleted file mode 100644 index 380a142f..00000000 --- a/include/mp/util.h +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (c) 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if __has_include() -#include -#include -#endif - -namespace mp { - -//! Generic utility functions used by capnp code. - -//! Type holding a list of types. -//! -//! Example: -//! TypeList -template -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(5, true) // Constructs std::pair(5, true); -template