diff --git a/.github/workflows/ci-build-binary-artifacts.yaml b/.github/workflows/ci-build-binary-artifacts.yaml index 586458d5..63644e5a 100644 --- a/.github/workflows/ci-build-binary-artifacts.yaml +++ b/.github/workflows/ci-build-binary-artifacts.yaml @@ -148,6 +148,7 @@ jobs: mkdir -p $BUILD_DIR cmake -B $BUILD_DIR \ -G "${{ matrix.generator }}" ${{ matrix.arch }} \ + -DUSE_ASIO=ON \ -DBUILD_TESTS=OFF \ -DVCPKG_TRIPLET=${{ matrix.triplet }} \ -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} \ @@ -174,6 +175,7 @@ jobs: mkdir -p $BUILD_DIR cmake -B $BUILD_DIR \ -G "${{ matrix.generator }}" ${{ matrix.arch }} \ + -DUSE_ASIO=ON \ -DBUILD_TESTS=OFF \ -DVCPKG_TRIPLET=${{ matrix.triplet }} \ -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR_DEBUG \ @@ -192,3 +194,34 @@ jobs: with: name: ${{ matrix.triplet }}-Debug path: ${{ env.INSTALL_DIR }}-Debug + + package-macos: + name: Build macOS libraries + runs-on: macos-latest + timeout-minutes: 500 + + strategy: + fail-fast: false + matrix: + arch: [x86_64, arm64] + + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + export ARCH=${{ matrix.arch }} + ./pkg/mac/build-static-library.sh + + - name: Zip artifact + run: | + cd ./pkg/mac/.install + zip -r macos-${{ matrix.arch }}.zip ./include/pulsar/* ./lib/* + cp macos-${{ matrix.arch }}.zip ../../../ + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: macos-${{ matrix.arch }}.zip + path: macos-${{ matrix.arch }}.zip diff --git a/.github/workflows/ci-pr-validation.yaml b/.github/workflows/ci-pr-validation.yaml index 03d417fa..9f113b03 100644 --- a/.github/workflows/ci-pr-validation.yaml +++ b/.github/workflows/ci-pr-validation.yaml @@ -29,8 +29,20 @@ concurrency: jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run clang-format style check for C/C++/Protobuf programs. + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: '11' + exclude-regex: '.*\.(proto|hpp)' + wireshark-dissector-build: name: Build the Wireshark dissector + needs: formatting-check runs-on: ${{ matrix.os }} timeout-minutes: 60 strategy: @@ -50,6 +62,13 @@ jobs: - name: Install deps (macOS) if: ${{ startsWith(matrix.os, 'macos') }} run: | + # See https://github.com/Homebrew/homebrew-core/issues/157142 + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + cd /usr/local/bin + rm -f 2to3* idle3* pydoc* python3* + rm -f /usr/local/share/man/man1/python3.1 /usr/local/lib/pkgconfig/python3* + cd /usr/local/Frameworks/Python.framework + rm -rf Headers Python Resources Versions/Current brew update brew install pkg-config wireshark protobuf - name: Build wireshark plugin @@ -59,48 +78,55 @@ jobs: unit-tests: name: Run unit tests + needs: formatting-check runs-on: ubuntu-22.04 timeout-minutes: 120 steps: - name: checkout uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive - - name: Install deps + - name: Build core libraries + run: | + cmake . -DINTEGRATE_VCPKG=ON -DBUILD_TESTS=OFF + cmake --build . -j8 + + - name: Check formatting run: | - sudo apt-get update -y && \ - sudo apt-get install -y \ - libcurl4-openssl-dev \ - protobuf-compiler \ - libprotobuf-dev \ - libboost-dev \ - libboost-program-options-dev \ - libzstd-dev \ - libsnappy-dev \ - libgmock-dev \ - libgtest-dev + ./vcpkg/vcpkg format-manifest vcpkg.json + if [[ $(git diff | wc -l) -gt 0 ]]; then + echo "Please run `./vcpkg/vcpkg format-manifest vcpkg.json` to reformat vcpkg.json" + exit 1 + fi + + - name: Build tests + run: | + cmake . -DINTEGRATE_VCPKG=ON -DBUILD_TESTS=ON + cmake --build . -j8 - name: Install gtest-parallel run: | sudo curl -o /gtest-parallel https://raw.githubusercontent.com/google/gtest-parallel/master/gtest_parallel.py - - name: CMake - run: cmake . -DCMAKE_BUILD_TYPE=Debug -DBUILD_PERF_TOOLS=ON - - - name: Check formatting - run: make check-format + - name: Run unit tests + run: RETRY_FAILED=3 ./run-unit-tests.sh - - name: Build + - name: Build perf tools run: | - # Build the libraries first to avoid possible link failures - cmake --build . -j8 --target pulsarShared pulsarStatic + cmake . -DINTEGRATE_VCPKG=ON -DBUILD_TESTS=ON -DBUILD_PERF_TOOLS=ON cmake --build . -j8 - - name: Run unit tests - run: RETRY_FAILED=3 ./run-unit-tests.sh + - name: Verify custom vcpkg installation + run: | + mv vcpkg /tmp/ + cmake -B build -DINTEGRATE_VCPKG=ON -DCMAKE_TOOLCHAIN_FILE="/tmp/vcpkg/scripts/buildsystems/vcpkg.cmake" cpp20-build: name: Build with the C++20 standard + needs: formatting-check runs-on: ubuntu-22.04 timeout-minutes: 60 @@ -189,6 +215,7 @@ jobs: cmake \ -B ./build-1 \ -G "${{ matrix.generator }}" ${{ matrix.arch }} \ + -DUSE_ASIO=ON \ -DBUILD_TESTS=OFF \ -DVCPKG_TRIPLET="${{ matrix.triplet }}" \ -DCMAKE_INSTALL_PREFIX="${{ env.INSTALL_DIR }}" \ @@ -230,6 +257,7 @@ jobs: cmake \ -B ./build-2 \ -G "${{ matrix.generator }}" ${{ matrix.arch }} \ + -DUSE_ASIO=ON \ -DBUILD_TESTS=OFF \ -DVCPKG_TRIPLET="${{ matrix.triplet }}" \ -DCMAKE_INSTALL_PREFIX="${{ env.INSTALL_DIR }}" \ @@ -284,8 +312,8 @@ jobs: cpp-build-macos: timeout-minutes: 120 name: Build CPP Client on macOS + needs: formatting-check runs-on: macos-12 - needs: unit-tests steps: - name: checkout uses: actions/checkout@v3 @@ -302,11 +330,39 @@ jobs: run: | cmake --build ./build-macos --parallel --config Release + - name: Build with C++20 + shell: bash + run: | + cmake -B build-macos-cpp20 -DCMAKE_CXX_STANDARD=20 + cmake --build build-macos-cpp20 -j8 + + cpp-build-macos-static: + timeout-minutes: 120 + name: Build CPP Client on macOS with static dependencies + runs-on: macos-12 + needs: unit-tests + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Build libraries + run: ./pkg/mac/build-static-library.sh + + - name: Test static libraries + run: | + export PULSAR_DIR=$PWD/pkg/mac/.install + echo "Build with static library" + clang++ win-examples/example.cc -o static.out -std=c++11 -I $PULSAR_DIR/include $PULSAR_DIR/lib/libpulsarwithdeps.a + ./static.out + echo "Build with dynamic library" + clang++ win-examples/example.cc -o dynamic.out -std=c++11 -I $PULSAR_DIR/include -L $PULSAR_DIR/lib -Wl,-rpath $PULSAR_DIR/lib -lpulsar + ./dynamic.out + # Job that will be required to complete and depends on all the other jobs check-completion: name: Check Completion runs-on: ubuntu-latest - needs: [wireshark-dissector-build, unit-tests, cpp20-build, cpp-build-windows, package, cpp-build-macos] + needs: [formatting-check, wireshark-dissector-build, unit-tests, cpp20-build, cpp-build-windows, package, cpp-build-macos] steps: - run: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..c877c642 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '16 21 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-22.04 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Install deps + run: | + sudo apt-get update -y + sudo apt-get install -y libcurl4-openssl-dev libssl-dev \ + protobuf-compiler libprotobuf-dev libboost-dev \ + libboost-dev libboost-program-options-dev \ + libzstd-dev libsnappy-dev + + - name: Build + run: | + cmake . -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=OFF -DBUILD_STATIC_LIB=OFF -DBUILD_PERF_TOOLS=ON + cmake --build . -j8 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index a569bda0..a1e5224a 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,5 @@ vcpkg_installed/ .tests-container-id.txt Testing .test-token.txt +pkg/mac/.build +pkg/mac/.install diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a0a57f3d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c1e7a79c..b0046534 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,28 @@ cmake_minimum_required(VERSION 3.13) +option(USE_ASIO "Use Asio instead of Boost.Asio" OFF) + +option(INTEGRATE_VCPKG "Integrate with Vcpkg" OFF) +if (INTEGRATE_VCPKG) + set(USE_ASIO ON) + if (NOT CMAKE_TOOLCHAIN_FILE) + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake") + endif () +endif () + +option(BUILD_TESTS "Build tests" ON) +message(STATUS "BUILD_TESTS: " ${BUILD_TESTS}) +if (BUILD_TESTS) + list(APPEND VCPKG_MANIFEST_FEATURES "tests") +endif () + +option(BUILD_PERF_TOOLS "Build Pulsar CLI perf producer/consumer" OFF) +message(STATUS "BUILD_PERF_TOOLS: " ${BUILD_PERF_TOOLS}) +if (BUILD_PERF_TOOLS) + list(APPEND VCPKG_MANIFEST_FEATURES "perf") +endif () + project (pulsar-cpp) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake_modules") @@ -33,30 +55,6 @@ message(STATUS "Pulsar Client version macro: ${PULSAR_CLIENT_VERSION_MACRO}") set(PVM_COMMENT "This is generated from Version.h.in by CMAKE. DO NOT EDIT DIRECTLY") configure_file(templates/Version.h.in include/pulsar/Version.h @ONLY) -option(LINK_STATIC "Link against static libraries" OFF) -if (VCPKG_TRIPLET) - message(STATUS "Use vcpkg, triplet is ${VCPKG_TRIPLET}") - set(CMAKE_PREFIX_PATH "${PROJECT_SOURCE_DIR}/vcpkg_installed/${VCPKG_TRIPLET}") - message(STATUS "Use CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") - set(PROTOC_PATH "${CMAKE_PREFIX_PATH}/tools/protobuf/protoc") - message(STATUS "Use protoc: ${PROTOC_PATH}") - set(VCPKG_ROOT "${PROJECT_SOURCE_DIR}/vcpkg_installed/${VCPKG_TRIPLET}") - set(VCPKG_DEBUG_ROOT "${VCPKG_ROOT}/debug") - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(ZLIB_ROOT ${VCPKG_DEBUG_ROOT}) - set(OPENSSL_ROOT_DIR ${VCPKG_ROOT} ${VCPKG_DEBUG_ROOT}) - set(CMAKE_PREFIX_PATH ${VCPKG_DEBUG_ROOT} ${CMAKE_PREFIX_PATH}) - else () - set(OPENSSL_ROOT_DIR ${VCPKG_ROOT}) - endif () - if (VCPKG_TRIPLET MATCHES ".*-static") - set(LINK_STATIC ON) - else () - set(LINK_STATIC OFF) - endif () -endif() -MESSAGE(STATUS "LINK_STATIC: " ${LINK_STATIC}) - find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) set(CMAKE_CXX_COMPILER_LAUNCHER "ccache") @@ -71,12 +69,6 @@ MESSAGE(STATUS "BUILD_DYNAMIC_LIB: " ${BUILD_DYNAMIC_LIB}) option(BUILD_STATIC_LIB "Build static lib" ON) MESSAGE(STATUS "BUILD_STATIC_LIB: " ${BUILD_STATIC_LIB}) -option(BUILD_TESTS "Build tests" ON) -MESSAGE(STATUS "BUILD_TESTS: " ${BUILD_TESTS}) - -option(BUILD_PERF_TOOLS "Build Pulsar CLI perf producer/consumer" OFF) -MESSAGE(STATUS "BUILD_PERF_TOOLS: " ${BUILD_PERF_TOOLS}) - IF (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) ENDIF () @@ -87,17 +79,6 @@ set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) MESSAGE(STATUS "Threads library: " ${CMAKE_THREAD_LIBS_INIT}) -set(Boost_NO_BOOST_CMAKE ON) -if (NOT CMAKE_CXX_STANDARD) - if (APPLE) - # The latest Protobuf dependency on macOS requires the C++17 support - set(CMAKE_CXX_STANDARD 17) - else () - set(CMAKE_CXX_STANDARD 11) - endif () -endif () -set(CMAKE_C_STANDARD 11) - # Compiler specific configuration: # https://stackoverflow.com/questions/10046114/in-cmake-how-can-i-test-if-the-compiler-is-clang if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") @@ -125,256 +106,53 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_definitions(-DBUILDING_PULSAR -DBOOST_ALL_NO_LIB -DBOOST_ALLOW_DEPRECATED_HEADERS) -# For dependencies other than OpenSSL, dynamic libraries are forbidden to link when LINK_STATIC is ON -if (LINK_STATIC) - if (NOT MSVC) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - endif() -endif () - -find_package(Boost REQUIRED) -message("Boost_INCLUDE_DIRS: " ${Boost_INCLUDE_DIRS}) - -set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /usr/lib64/) -if (APPLE) - set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /usr/local/opt/openssl/ /opt/homebrew/opt/openssl) -endif () -find_package(OpenSSL REQUIRED) -message("OPENSSL_INCLUDE_DIR: " ${OPENSSL_INCLUDE_DIR}) -message("OPENSSL_LIBRARIES: " ${OPENSSL_LIBRARIES}) - -if (APPLE) - # See https://github.com/apache/arrow/issues/35987 - add_definitions(-DPROTOBUF_USE_DLLS) - # Use Config mode to avoid FindProtobuf.cmake does not find the Abseil library - find_package(Protobuf REQUIRED CONFIG) -else () - find_package(Protobuf REQUIRED) -endif () -message("Protobuf_INCLUDE_DIRS: " ${Protobuf_INCLUDE_DIRS}) -message("Protobuf_LIBRARIES: " ${Protobuf_LIBRARIES}) - -# NOTE: CMake might not find curl and zlib on some platforms like Ubuntu, in this case, find them manually -set(CURL_NO_CURL_CMAKE ON) -find_package(curl QUIET) -if (NOT CURL_FOUND) - find_path(CURL_INCLUDE_DIRS NAMES curl/curl.h) - find_library(CURL_LIBRARIES NAMES curl curllib libcurl_imp curllib_static libcurl) -endif () -message("CURL_INCLUDE_DIRS: " ${CURL_INCLUDE_DIRS}) -message("CURL_LIBRARIES: " ${CURL_LIBRARIES}) -if (NOT CURL_INCLUDE_DIRS OR NOT CURL_LIBRARIES) - message(FATAL_ERROR "Could not find libcurl") -endif () - -find_package(zlib QUIET) -if (NOT ZLIB_FOUND) - find_path(ZLIB_INCLUDE_DIRS NAMES zlib.h) - find_library(ZLIB_LIBRARIES NAMES z zlib zdll zlib1 zlibstatic) -endif () -message("ZLIB_INCLUDE_DIRS: " ${ZLIB_INCLUDE_DIRS}) -message("ZLIB_LIBRARIES: " ${ZLIB_LIBRARIES}) -if (NOT ZLIB_INCLUDE_DIRS OR NOT ZLIB_LIBRARIES) - message(FATAL_ERROR "Could not find zlib") -endif () - -if (LINK_STATIC AND NOT VCPKG_TRIPLET) - find_library(LIB_ZSTD NAMES libzstd.a) - message(STATUS "ZStd: ${LIB_ZSTD}") - find_library(LIB_SNAPPY NAMES libsnappy.a) - message(STATUS "LIB_SNAPPY: ${LIB_SNAPPY}") +set(AUTOGEN_DIR ${PROJECT_BINARY_DIR}/generated) +file(MAKE_DIRECTORY ${AUTOGEN_DIR}) - if (MSVC) - add_definitions(-DCURL_STATICLIB) - endif() -elseif (LINK_STATIC AND VCPKG_TRIPLET) - find_package(Protobuf REQUIRED) - message(STATUS "Found protobuf static library: " ${Protobuf_LIBRARIES}) - if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) - find_library(ZLIB_LIBRARIES NAMES zlibd) - else () - find_library(ZLIB_LIBRARIES NAMES zlib z) - endif () - if (ZLIB_LIBRARIES) - message(STATUS "Found zlib static library: " ${ZLIB_LIBRARIES}) - else () - message(FATAL_ERROR "Failed to find zlib static library") - endif () - if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) - find_library(CURL_LIBRARIES NAMES libcurl-d) - else () - find_library(CURL_LIBRARIES NAMES libcurl) - endif () - if (CURL_LIBRARIES) - message(STATUS "Found libcurl: ${CURL_LIBRARIES}") - else () - message(FATAL_ERROR "Cannot find libcurl") - endif () - find_library(LIB_ZSTD zstd) - if (LIB_ZSTD) - message(STATUS "Found ZSTD library: ${LIB_ZSTD}") - endif () - find_library(LIB_SNAPPY NAMES snappy) - if (LIB_SNAPPY) - message(STATUS "Found Snappy library: ${LIB_SNAPPY}") +if (INTEGRATE_VCPKG) + if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) endif () -else() - if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) - find_library(LIB_ZSTD zstdd HINTS "${VCPKG_DEBUG_ROOT}/lib") - else () - find_library(LIB_ZSTD zstd) + set(CMAKE_C_STANDARD 11) + set(Boost_NO_BOOST_CMAKE ON) + find_package(Boost REQUIRED) + include_directories(${Boost_INCLUDE_DIRS}) + message("Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}") + + set(CURL_NO_CURL_CMAKE ON) + find_package(CURL REQUIRED) + find_package(ZLIB REQUIRED) + find_package(OpenSSL REQUIRED) + find_package(protobuf CONFIG REQUIRED) + find_package(zstd CONFIG REQUIRED) + find_package(Snappy CONFIG REQUIRED) + set(COMMON_LIBS CURL::libcurl + ZLIB::ZLIB + OpenSSL::SSL + OpenSSL::Crypto + protobuf::libprotobuf + $,zstd::libzstd_shared,zstd::libzstd_static> + Snappy::snappy + ) + if (USE_ASIO) + find_package(asio CONFIG REQUIRED) + set(COMMON_LIBS ${COMMON_LIBS} asio::asio) endif () - if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) - find_library(LIB_SNAPPY NAMES snappyd HINTS "${VCPKG_DEBUG_ROOT}/lib") - else () - find_library(LIB_SNAPPY NAMES snappy libsnappy) + add_definitions(-DHAS_ZSTD -DHAS_SNAPPY) + if (MSVC) + find_package(dlfcn-win32 CONFIG REQUIRED) endif () -endif () - -if (Boost_MAJOR_VERSION EQUAL 1 AND Boost_MINOR_VERSION LESS 69) - # Boost System does not require linking since 1.69 - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} system) - MESSAGE(STATUS "Linking with Boost:System") -endif() - -if (MSVC) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} date_time) -endif() - -if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) - # GCC 4.8.2 implementation of std::regex is buggy - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex) - set(CMAKE_CXX_FLAGS " -DPULSAR_USE_BOOST_REGEX") - MESSAGE(STATUS "Using Boost::Regex") -elseif (CMAKE_COMPILER_IS_GNUCC) - MESSAGE(STATUS "Using std::regex") - # Turn on color error messages and show additional help with errors (only available in GCC v4.9+): - add_compile_options(-fdiagnostics-show-option -fdiagnostics-color) -endif() - -if(BUILD_PERF_TOOLS) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} program_options) -endif() - -find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) - -if (BUILD_TESTS) - find_path(GTEST_INCLUDE_PATH gtest/gtest.h) - find_path(GMOCK_INCLUDE_PATH gmock/gmock.h) -endif () - -if (NOT APPLE AND NOT MSVC) - # Hide all non-exported symbols to avoid conflicts - add_compile_options(-fvisibility=hidden) - if (CMAKE_COMPILER_IS_GNUCC) - add_link_options(-Wl,--exclude-libs=ALL) + if (BUILD_PERF_TOOLS) + find_package(Boost COMPONENTS program_options REQUIRED) endif () -endif () - -if (LIB_ZSTD) - set(HAS_ZSTD 1) -else () - set(HAS_ZSTD 0) -endif () -MESSAGE(STATUS "HAS_ZSTD: ${HAS_ZSTD}") - -if (LIB_SNAPPY) - set(HAS_SNAPPY 1) -else () - set(HAS_SNAPPY 0) -endif () -MESSAGE(STATUS "HAS_SNAPPY: ${HAS_SNAPPY}") - -set(ADDITIONAL_LIBRARIES $ENV{PULSAR_ADDITIONAL_LIBRARIES}) -link_directories( $ENV{PULSAR_ADDITIONAL_LIBRARY_PATH} ) - -set(AUTOGEN_DIR ${PROJECT_BINARY_DIR}/generated) -file(MAKE_DIRECTORY ${AUTOGEN_DIR}) - -include_directories( - ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR}/include - ${AUTOGEN_DIR} - ${Boost_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIR} - ${ZLIB_INCLUDE_DIRS} - ${CURL_INCLUDE_DIRS} - ${Protobuf_INCLUDE_DIRS} - ${GTEST_INCLUDE_PATH} - ${GMOCK_INCLUDE_PATH} -) - -set(COMMON_LIBS - ${COMMON_LIBS} - ${CMAKE_THREAD_LIBS_INIT} - ${Boost_REGEX_LIBRARY} - ${Boost_SYSTEM_LIBRARY} - ${Boost_DATE_TIME_LIBRARY} - ${CURL_LIBRARIES} - ${OPENSSL_LIBRARIES} - ${ZLIB_LIBRARIES} - ${ADDITIONAL_LIBRARIES} - ${CMAKE_DL_LIBS} -) - -if (APPLE) - # Protobuf_LIBRARIES is empty when finding Protobuf in Config mode - set(COMMON_LIBS ${COMMON_LIBS} protobuf::libprotobuf) else () - set(COMMON_LIBS ${COMMON_LIBS} ${Protobuf_LIBRARIES}) + include(./LegacyFindPackages.cmake) endif () -if (MSVC) - set(COMMON_LIBS - ${COMMON_LIBS} - ${Boost_DATE_TIME_LIBRARY} - wldap32.lib - Normaliz.lib) - if (LINK_STATIC) - # add external dependencies of libcurl - set(COMMON_LIBS ${COMMON_LIBS} ws2_32.lib crypt32.lib) - # the default compile options have /MD, which cannot be used to build DLLs that link static libraries - string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) - string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) - string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELWITHDEBINFO ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}) - message(STATUS "CMAKE_CXX_FLAGS_DEBUG: " ${CMAKE_CXX_FLAGS_DEBUG}) - message(STATUS "CMAKE_CXX_FLAGS_RELEASE: " ${CMAKE_CXX_FLAGS_RELEASE}) - message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO: " ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}) - endif () -else() - set(COMMON_LIBS ${COMMON_LIBS} m) -endif() - -if (USE_LOG4CXX) - set(COMMON_LIBS - ${COMMON_LIBS} - ${LOG4CXX_LIBRARY_PATH} - ${APR_LIBRARY_PATH} - ${APR_UTIL_LIBRARY_PATH} - ${EXPAT_LIBRARY_PATH} - ${ICONV_LIBRARY_PATH} - ) -endif () - -if (HAS_ZSTD) - set(COMMON_LIBS ${COMMON_LIBS} ${LIB_ZSTD} ) +if (USE_ASIO) + add_definitions(-DUSE_ASIO) endif () -add_definitions(-DHAS_ZSTD=${HAS_ZSTD}) - -if (HAS_SNAPPY) - set(COMMON_LIBS ${COMMON_LIBS} ${LIB_SNAPPY} ) -endif () - -add_definitions(-DHAS_SNAPPY=${HAS_SNAPPY}) - -if(NOT APPLE AND NOT MSVC) - set(COMMON_LIBS ${COMMON_LIBS} rt) -endif () - -link_directories(${PROJECT_BINARY_DIR}/lib) - set(LIB_NAME $ENV{PULSAR_LIBRARY_NAME}) if (NOT LIB_NAME) set(LIB_NAME pulsar) @@ -395,6 +173,8 @@ if (BUILD_DYNAMIC_LIB) endif() if (BUILD_TESTS) + set(TOKEN_PATH "${PROJECT_SOURCE_DIR}/.test-token.txt") + set(TEST_CONF_DIR "${PROJECT_SOURCE_DIR}/test-conf") add_subdirectory(tests) endif() diff --git a/LEGACY_BUILD.md b/LEGACY_BUILD.md new file mode 100644 index 00000000..0920e2b5 --- /dev/null +++ b/LEGACY_BUILD.md @@ -0,0 +1,250 @@ + + +# Build without vcpkg + +## Requirements + +- A C++ compiler that supports C++11, like GCC >= 4.8 +- CMake >= 3.13 +- [Boost](http://www.boost.org/) +- [Protocol Buffer](https://developers.google.com/protocol-buffers/) >= 3 +- [libcurl](https://curl.se/libcurl/) +- [openssl](https://github.com/openssl/openssl) + +The default supported [compression types](include/pulsar/CompressionType.h) are: + +- `CompressionNone` +- `CompressionLZ4` + +If you want to enable other compression types, you need to install: + +- `CompressionZLib`: [zlib](https://zlib.net/) +- `CompressionZSTD`: [zstd](https://github.com/facebook/zstd) +- `CompressionSNAPPY`: [snappy](https://github.com/google/snappy) + +If you want to build and run the tests, you need to install [GTest](https://github.com/google/googletest). Otherwise, you need to add CMake option `-DBUILD_TESTS=OFF`. + +The [dependencies.yaml](./dependencies.yaml) file provides the recommended dependency versions, while you can still build from source with other dependency versions. If a dependency requires a higher C++ standard, e.g. C++14, you can specify the standard like: + +```bash +cmake . -DCMAKE_CXX_STANDARD=14 +``` + +> **Note**: +> +> On macOS, the default C++ standard is 17 because the latest Protobuf from Homebrew requires the C++17 support. + +## Compilation + +### Clone + +First of all, clone the source code: + +```shell +git clone https://github.com/apache/pulsar-client-cpp +cd pulsar-client-cpp +``` + +### Compile on Ubuntu + +#### Install all dependencies: + +```shell +sudo apt-get update -y && sudo apt-get install -y g++ cmake libssl-dev libcurl4-openssl-dev \ + libprotobuf-dev libboost-all-dev libgtest-dev libgmock-dev \ + protobuf-compiler +``` + +#### Compile Pulsar client library: + +```shell +cmake . +make +``` + +If you want to build performance tools, you need to run: + +```shell +cmake . -DBUILD_PERF_TOOLS=ON +make +``` + +#### Checks + +Client library will be placed in: + +``` +lib/libpulsar.so +lib/libpulsar.a +``` + +Examples will be placed in: + +``` +examples/ +``` + +Tools will be placed in: + +``` +perf/perfProducer +perf/perfConsumer +``` + +### Compile on Mac OS X + +#### Install all dependencies: + +```shell +brew install cmake openssl protobuf boost googletest zstd snappy +``` + +#### Compile Pulsar client library: + +```shell +cmake . +make +``` + +If you want to build performance tools, you need to run: + +```shell +cmake . -DBUILD_PERF_TOOLS=ON +make +``` + +#### Checks + +Client library will be placed in: + +``` +lib/libpulsar.dylib +lib/libpulsar.a +``` + +Examples will be placed in: + +``` +examples/ +``` + +Tools will be placed in: + +``` +perf/perfProducer +perf/perfConsumer +``` + +### Compile on Windows + +#### Install with [vcpkg](https://github.com/microsoft/vcpkg) + +It's highly recommended to use `vcpkg` for C++ package management on Windows. It's easy to install and well supported by Visual Studio (2015/2017/2019) and CMake. See [here](https://github.com/microsoft/vcpkg#quick-start-windows) for quick start. + +Take Windows 64-bit library as an example, you only need to run + +```bash +vcpkg install --feature-flags=manifests --triplet x64-windows +``` + +> **NOTE**: +> +> For Windows 32-bit library, change `x64-windows` to `x86-windows`, see [here](https://github.com/microsoft/vcpkg/blob/master/docs/users/triplets.md) for more details about the triplet concept in Vcpkg. + +The all dependencies, which are specified by [vcpkg.json](vcpkg.json), will be installed in `vcpkg_installed/` subdirectory, + +With `vcpkg`, you only need to run two commands: + +```bash +cmake \ + -B ./build \ + -A x64 \ + -DBUILD_TESTS=OFF \ + -DVCPKG_TRIPLET=x64-windows \ + -DCMAKE_BUILD_TYPE=Release \ + -S . +cmake --build ./build --config Release +``` + +Then all artifacts will be built into `build` subdirectory. + +> **NOTE**: +> +> 1. For Windows 32-bit, you need to use `-A Win32` and `-DVCPKG_TRIPLET=x86-windows`. +> 2. For MSVC Debug mode, you need to replace `Release` with `Debug` for both `CMAKE_BUILD_TYPE` variable and `--config` option. + +#### Install dependencies manually + +You need to install [dlfcn-win32](https://github.com/dlfcn-win32/dlfcn-win32) in addition. + +If you installed the dependencies manually, you need to run + +```shell +#If all dependencies are in your path, all that is necessary is +cmake . + +#if all dependencies are not in your path, then passing in a PROTOC_PATH and CMAKE_PREFIX_PATH is necessary +cmake -DPROTOC_PATH=C:/protobuf/bin/protoc -DCMAKE_PREFIX_PATH="C:/boost;C:/openssl;C:/zlib;C:/curl;C:/protobuf;C:/googletest;C:/dlfcn-win32" . + +#This will generate pulsar-cpp.sln. Open this in Visual Studio and build the desired configurations. +``` + +#### Checks + +Client library will be placed in: + +``` +build/lib/Release/pulsar.lib +build/lib/Release/pulsar.dll +``` + +#### Examples + +Add Windows environment paths: + +``` +build/lib/Release +vcpkg_installed +``` + +Examples will be available in: + +``` +build/examples/Release +``` + +## Tests + +```shell +# Execution +# Start standalone broker +./pulsar-test-service-start.sh + +# Run the tests +cd tests +./pulsar-tests + +# When no longer needed, stop standalone broker +./pulsar-test-service-stop.sh +``` + + diff --git a/LegacyFindPackages.cmake b/LegacyFindPackages.cmake new file mode 100644 index 00000000..5004545b --- /dev/null +++ b/LegacyFindPackages.cmake @@ -0,0 +1,308 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +option(LINK_STATIC "Link against static libraries" OFF) +if (VCPKG_TRIPLET) + message(STATUS "Use vcpkg, triplet is ${VCPKG_TRIPLET}") + set(CMAKE_PREFIX_PATH "${PROJECT_SOURCE_DIR}/vcpkg_installed/${VCPKG_TRIPLET}") + message(STATUS "Use CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") + set(PROTOC_PATH "${CMAKE_PREFIX_PATH}/tools/protobuf/protoc") + message(STATUS "Use protoc: ${PROTOC_PATH}") + set(VCPKG_ROOT "${PROJECT_SOURCE_DIR}/vcpkg_installed/${VCPKG_TRIPLET}") + set(VCPKG_DEBUG_ROOT "${VCPKG_ROOT}/debug") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(ZLIB_ROOT ${VCPKG_DEBUG_ROOT}) + set(OPENSSL_ROOT_DIR ${VCPKG_ROOT} ${VCPKG_DEBUG_ROOT}) + set(CMAKE_PREFIX_PATH ${VCPKG_DEBUG_ROOT} ${CMAKE_PREFIX_PATH}) + else () + set(OPENSSL_ROOT_DIR ${VCPKG_ROOT}) + endif () + if (VCPKG_TRIPLET MATCHES ".*-static") + set(LINK_STATIC ON) + else () + set(LINK_STATIC OFF) + endif () +endif() +MESSAGE(STATUS "LINK_STATIC: " ${LINK_STATIC}) + +if (MSVC) + find_package(dlfcn-win32 REQUIRED) +endif () + +set(Boost_NO_BOOST_CMAKE ON) + +if (APPLE AND NOT LINK_STATIC) + # The latest Protobuf dependency on macOS requires the C++17 support and + # it could only be found by the CONFIG mode + set(LATEST_PROTOBUF TRUE) +else () + set(LATEST_PROTOBUF FALSE) +endif () + +if (NOT CMAKE_CXX_STANDARD) + if (LATEST_PROTOBUF) + set(CMAKE_CXX_STANDARD 17) + else () + set(CMAKE_CXX_STANDARD 11) + endif () +endif () +set(CMAKE_C_STANDARD 11) + +# For dependencies other than OpenSSL, dynamic libraries are forbidden to link when LINK_STATIC is ON +if (LINK_STATIC) + if (NOT MSVC) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + endif() +endif () + +find_package(Boost REQUIRED) +message("Boost_INCLUDE_DIRS: " ${Boost_INCLUDE_DIRS}) + +set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /usr/lib64/) +if (APPLE) + set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /usr/local/opt/openssl/ /opt/homebrew/opt/openssl) +endif () +find_package(OpenSSL REQUIRED) +message("OPENSSL_INCLUDE_DIR: " ${OPENSSL_INCLUDE_DIR}) +message("OPENSSL_LIBRARIES: " ${OPENSSL_LIBRARIES}) + +if (LATEST_PROTOBUF) + # See https://github.com/apache/arrow/issues/35987 + add_definitions(-DPROTOBUF_USE_DLLS) + # Use Config mode to avoid FindProtobuf.cmake does not find the Abseil library + find_package(Protobuf REQUIRED CONFIG) +else () + find_package(Protobuf REQUIRED) +endif () +message("Protobuf_INCLUDE_DIRS: " ${Protobuf_INCLUDE_DIRS}) +message("Protobuf_LIBRARIES: " ${Protobuf_LIBRARIES}) + +# NOTE: CMake might not find curl and zlib on some platforms like Ubuntu, in this case, find them manually +set(CURL_NO_CURL_CMAKE ON) +find_package(curl QUIET) +if (NOT CURL_FOUND) + find_path(CURL_INCLUDE_DIRS NAMES curl/curl.h) + find_library(CURL_LIBRARIES NAMES curl curllib libcurl_imp curllib_static libcurl) +endif () +message("CURL_INCLUDE_DIRS: " ${CURL_INCLUDE_DIRS}) +message("CURL_LIBRARIES: " ${CURL_LIBRARIES}) +if (NOT CURL_INCLUDE_DIRS OR NOT CURL_LIBRARIES) + message(FATAL_ERROR "Could not find libcurl") +endif () + +find_package(zlib QUIET) +if (NOT ZLIB_FOUND) + find_path(ZLIB_INCLUDE_DIRS NAMES zlib.h) + find_library(ZLIB_LIBRARIES NAMES z zlib zdll zlib1 zlibstatic) +endif () +message("ZLIB_INCLUDE_DIRS: " ${ZLIB_INCLUDE_DIRS}) +message("ZLIB_LIBRARIES: " ${ZLIB_LIBRARIES}) +if (NOT ZLIB_INCLUDE_DIRS OR NOT ZLIB_LIBRARIES) + message(FATAL_ERROR "Could not find zlib") +endif () + +if (LINK_STATIC AND NOT VCPKG_TRIPLET) + find_library(LIB_ZSTD NAMES libzstd.a) + message(STATUS "ZStd: ${LIB_ZSTD}") + find_library(LIB_SNAPPY NAMES libsnappy.a) + message(STATUS "LIB_SNAPPY: ${LIB_SNAPPY}") + + if (MSVC) + add_definitions(-DCURL_STATICLIB) + endif() +elseif (LINK_STATIC AND VCPKG_TRIPLET) + find_package(Protobuf REQUIRED) + message(STATUS "Found protobuf static library: " ${Protobuf_LIBRARIES}) + if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) + find_library(ZLIB_LIBRARIES NAMES zlibd) + else () + find_library(ZLIB_LIBRARIES NAMES zlib z) + endif () + if (ZLIB_LIBRARIES) + message(STATUS "Found zlib static library: " ${ZLIB_LIBRARIES}) + else () + message(FATAL_ERROR "Failed to find zlib static library") + endif () + if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) + find_library(CURL_LIBRARIES NAMES libcurl-d) + else () + find_library(CURL_LIBRARIES NAMES libcurl) + endif () + if (CURL_LIBRARIES) + message(STATUS "Found libcurl: ${CURL_LIBRARIES}") + else () + message(FATAL_ERROR "Cannot find libcurl") + endif () + find_library(LIB_ZSTD zstd) + if (LIB_ZSTD) + message(STATUS "Found ZSTD library: ${LIB_ZSTD}") + endif () + find_library(LIB_SNAPPY NAMES snappy) + if (LIB_SNAPPY) + message(STATUS "Found Snappy library: ${LIB_SNAPPY}") + endif () +else() + if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) + find_library(LIB_ZSTD zstdd HINTS "${VCPKG_DEBUG_ROOT}/lib") + else () + find_library(LIB_ZSTD zstd) + endif () + if (MSVC AND (${CMAKE_BUILD_TYPE} STREQUAL Debug)) + find_library(LIB_SNAPPY NAMES snappyd HINTS "${VCPKG_DEBUG_ROOT}/lib") + else () + find_library(LIB_SNAPPY NAMES snappy libsnappy) + endif () +endif () + +if (Boost_MAJOR_VERSION EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + # Boost System does not require linking since 1.69 + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} system) + MESSAGE(STATUS "Linking with Boost:System") +endif() + +if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) + # GCC 4.8.2 implementation of std::regex is buggy + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex) + set(CMAKE_CXX_FLAGS " -DPULSAR_USE_BOOST_REGEX") + MESSAGE(STATUS "Using Boost::Regex") +elseif (CMAKE_COMPILER_IS_GNUCC) + MESSAGE(STATUS "Using std::regex") + # Turn on color error messages and show additional help with errors (only available in GCC v4.9+): + add_compile_options(-fdiagnostics-show-option -fdiagnostics-color) +endif() + +if(BUILD_PERF_TOOLS) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} program_options) +endif() + +find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) + +if (BUILD_TESTS) + find_path(GTEST_INCLUDE_PATH gtest/gtest.h) + find_path(GMOCK_INCLUDE_PATH gmock/gmock.h) +endif () + +if (NOT APPLE AND NOT MSVC) + # Hide all non-exported symbols to avoid conflicts + add_compile_options(-fvisibility=hidden) + if (CMAKE_COMPILER_IS_GNUCC) + add_link_options(-Wl,--exclude-libs=ALL) + endif () +endif () + +if (LIB_ZSTD) + set(HAS_ZSTD 1) +else () + set(HAS_ZSTD 0) +endif () +MESSAGE(STATUS "HAS_ZSTD: ${HAS_ZSTD}") + +if (LIB_SNAPPY) + set(HAS_SNAPPY 1) +else () + set(HAS_SNAPPY 0) +endif () +MESSAGE(STATUS "HAS_SNAPPY: ${HAS_SNAPPY}") + +set(ADDITIONAL_LIBRARIES $ENV{PULSAR_ADDITIONAL_LIBRARIES}) +link_directories( $ENV{PULSAR_ADDITIONAL_LIBRARY_PATH} ) + +include_directories( + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${AUTOGEN_DIR} + ${Boost_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} + ${Protobuf_INCLUDE_DIRS} + ${GTEST_INCLUDE_PATH} + ${GMOCK_INCLUDE_PATH} +) + +set(COMMON_LIBS + ${COMMON_LIBS} + ${CMAKE_THREAD_LIBS_INIT} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_DATE_TIME_LIBRARY} + ${CURL_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${ZLIB_LIBRARIES} + ${ADDITIONAL_LIBRARIES} + ${CMAKE_DL_LIBS} +) + +if (LATEST_PROTOBUF) + # Protobuf_LIBRARIES is empty when finding Protobuf in Config mode + set(COMMON_LIBS ${COMMON_LIBS} protobuf::libprotobuf) +else () + set(COMMON_LIBS ${COMMON_LIBS} ${Protobuf_LIBRARIES}) +endif () + +if (MSVC) + set(COMMON_LIBS + ${COMMON_LIBS} + ${Boost_DATE_TIME_LIBRARY} + wldap32.lib + Normaliz.lib) + if (LINK_STATIC) + # add external dependencies of libcurl + set(COMMON_LIBS ${COMMON_LIBS} ws2_32.lib crypt32.lib) + # the default compile options have /MD, which cannot be used to build DLLs that link static libraries + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELWITHDEBINFO ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}) + message(STATUS "CMAKE_CXX_FLAGS_DEBUG: " ${CMAKE_CXX_FLAGS_DEBUG}) + message(STATUS "CMAKE_CXX_FLAGS_RELEASE: " ${CMAKE_CXX_FLAGS_RELEASE}) + message(STATUS "CMAKE_CXX_FLAGS_RELWITHDEBINFO: " ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}) + endif () +else() + set(COMMON_LIBS ${COMMON_LIBS} m) +endif() + +if (USE_LOG4CXX) + set(COMMON_LIBS + ${COMMON_LIBS} + ${LOG4CXX_LIBRARY_PATH} + ${APR_LIBRARY_PATH} + ${APR_UTIL_LIBRARY_PATH} + ${EXPAT_LIBRARY_PATH} + ${ICONV_LIBRARY_PATH} + ) +endif () + +if (HAS_ZSTD) + set(COMMON_LIBS ${COMMON_LIBS} ${LIB_ZSTD} ) +endif () + +add_definitions(-DHAS_ZSTD=${HAS_ZSTD}) + +if (HAS_SNAPPY) + set(COMMON_LIBS ${COMMON_LIBS} ${LIB_SNAPPY} ) +endif () + +add_definitions(-DHAS_SNAPPY=${HAS_SNAPPY}) + +if(NOT APPLE AND NOT MSVC) + set(COMMON_LIBS ${COMMON_LIBS} rt) +endif () + +link_directories(${PROJECT_BINARY_DIR}/lib) diff --git a/README.md b/README.md index 055fb8fd..4c86b631 100644 --- a/README.md +++ b/README.md @@ -27,243 +27,110 @@ For the supported Pulsar features, see [Client Feature Matrix](https://pulsar.ap For how to use APIs to publish and consume messages, see [examples](https://github.com/apache/pulsar-client-cpp/tree/main/examples). -## Generate the API documents - -Pulsar C++ client uses [doxygen](https://www.doxygen.nl) to build API documents. After installing `doxygen`, you only need to run `doxygen` to generate the API documents whose main page is under the `doxygen/html/index.html` path. - -## Requirements - -- A C++ compiler that supports C++11, like GCC >= 4.8 -- CMake >= 3.13 -- [Boost](http://www.boost.org/) -- [Protocol Buffer](https://developers.google.com/protocol-buffers/) >= 3 -- [libcurl](https://curl.se/libcurl/) -- [openssl](https://github.com/openssl/openssl) - -The default supported [compression types](include/pulsar/CompressionType.h) are: +## Import the library into your project -- `CompressionNone` -- `CompressionLZ4` +### CMake with vcpkg integration -If you want to enable other compression types, you need to install: +Navigate to [vcpkg-example](./vcpkg-example) for how to import the `pulsar-client-cpp` into your project via vcpkg. -- `CompressionZLib`: [zlib](https://zlib.net/) -- `CompressionZSTD`: [zstd](https://github.com/facebook/zstd) -- `CompressionSNAPPY`: [snappy](https://github.com/google/snappy) - -If you want to build and run the tests, you need to install [GTest](https://github.com/google/googletest). Otherwise, you need to add CMake option `-DBUILD_TESTS=OFF`. - -The [dependencies.yaml](./dependencies.yaml) file provides the recommended dependency versions, while you can still build from source with other dependency versions. If a dependency requires a higher C++ standard, e.g. C++14, you can specify the standard like: - -```bash -cmake . -DCMAKE_CXX_STANDARD=14 -``` +### Download pre-built binaries -> **Note**: -> -> On macOS, the default C++ standard is 17 because the latest Protobuf from Homebrew requires the C++17 support. +For non-vcpkg projects, you can download pre-built binaries from the [official release page](https://pulsar.apache.org/download/#pulsar-c-client). -## Platforms +## Generate the API documents -Pulsar C++ Client Library has been tested on: +Pulsar C++ client uses [doxygen](https://www.doxygen.nl) to build API documents. After installing `doxygen`, you only need to run `doxygen` to generate the API documents whose main page is under the `doxygen/html/index.html` path. -- Linux -- Mac OS X -- Windows x64 +## Build with vcpkg -## Compilation +Since it's integrated with vcpkg, see [vcpkg#README](https://github.com/microsoft/vcpkg#readme) for the requirements. See [LEGACY_BUILD](./LEGACY_BUILD.md) if you want to manage dependencies by yourself or you cannot install vcpkg in your own environment. -### Clone +### How to build from source -First of all, clone the source code: +The simplest way is to clone this project with the vcpkg submodule. -```shell -git clone https://github.com/apache/pulsar-client-cpp +```bash +git clone https://github.com/apache/pulsar-client-cpp.git cd pulsar-client-cpp +git submodule update --init --recursive +cmake -B build -DINTEGRATE_VCPKG=ON +cmake --build build -j8 ``` -### Compile on Ubuntu - -#### Install all dependencies: - -```shell -sudo apt-get update -y && sudo apt-get install -y g++ cmake libssl-dev libcurl4-openssl-dev \ - libprotobuf-dev libboost-all-dev libgtest-dev libgmock-dev \ - protobuf-compiler -``` - -#### Compile Pulsar client library: - -```shell -cmake . -make -``` - -If you want to build performance tools, you need to run: - -```shell -cmake . -DBUILD_PERF_TOOLS=ON -make -``` - -#### Checks - -Client library will be placed in: - -``` -lib/libpulsar.so -lib/libpulsar.a -``` - -Examples will be placed in: - -``` -examples/ -``` - -Tools will be placed in: - -``` -perf/perfProducer -perf/perfConsumer -``` - -### Compile on Mac OS X - -#### Install all dependencies: +The 1st step will download vcpkg and then install all dependencies according to the version description in [vcpkg.json](./vcpkg.json). The 2nd step will build the Pulsar C++ libraries under `./build/lib/`, where `./build` is the CMake build directory. -```shell -brew install cmake openssl protobuf boost googletest zstd snappy -``` +> You can also add the CMAKE_TOOLCHAIN_FILE option if your system already have vcpkg installed. +> +> ```bash +> git clone https://github.com/apache/pulsar-client-cpp.git +> cd pulsar-client-cpp +> # For example, you can install vcpkg in /tmp/vcpkg +> cd /tmp && git clone https://github.com/microsoft/vcpkg.git && cd - +> cmake -B build -DINTEGRATE_VCPKG=ON -DCMAKE_TOOLCHAIN_FILE="/tmp/vcpkg/scripts/buildsystems/vcpkg.cmake" +> cmake --build build -j8 +> ``` -#### Compile Pulsar client library: +After the build, the hierarchy of the `build` directory will be: -```shell -cmake . -make ``` - -If you want to build performance tools, you need to run: - -```shell -cmake . -DBUILD_PERF_TOOLS=ON -make +build/ + include/ -- extra C++ headers + lib/ -- libraries + tests/ -- test executables + examples/ -- example executables + generated/ + lib/ -- protobuf source files for PulsarApi.proto + tests/ -- protobuf source files for *.proto used in tests ``` -#### Checks +### How to install -Client library will be placed in: +To install the C++ headers and libraries into a specific path, e.g. `/tmp/pulsar`, run the following commands: -``` -lib/libpulsar.dylib -lib/libpulsar.a -``` - -Examples will be placed in: - -``` -examples/ +```bash +cmake -B build -DINTEGRATE_VCPKG=ON -DCMAKE_INSTALL_PREFIX=/tmp/pulsar +cmake --build build -j8 --target install ``` -Tools will be placed in: +For example, on macOS you will see: ``` -perf/perfProducer -perf/perfConsumer +/tmp/pulsar/ + include/pulsar -- C/C++ headers + lib/ + libpulsar.a -- Static library + libpulsar.dylib -- Dynamic library ``` -### Compile on Windows - -#### Install with [vcpkg](https://github.com/microsoft/vcpkg) +### Tests -It's highly recommended to use `vcpkg` for C++ package management on Windows. It's easy to install and well supported by Visual Studio (2015/2017/2019) and CMake. See [here](https://github.com/microsoft/vcpkg#quick-start-windows) for quick start. +Tests are built by default. You should execute [run-unit-tests.sh](./run-unit-tests.sh) to run tests locally. -Take Windows 64-bit library as an example, you only need to run +If you don't want to build the tests, disable the `BUILD_TESTS` option: ```bash -vcpkg install --feature-flags=manifests --triplet x64-windows +cmake -B build -DINTEGRATE_VCPKG=ON -DBUILD_TESTS=OFF +cmake --build build -j8 ``` -> **NOTE**: -> -> For Windows 32-bit library, change `x64-windows` to `x86-windows`, see [here](https://github.com/microsoft/vcpkg/blob/master/docs/users/triplets.md) for more details about the triplet concept in Vcpkg. - -The all dependencies, which are specified by [vcpkg.json](vcpkg.json), will be installed in `vcpkg_installed/` subdirectory, +### Build perf tools -With `vcpkg`, you only need to run two commands: +If you want to build the perf tools, enable the `BUILD_PERF_TOOLS` option: ```bash -cmake \ - -B ./build \ - -A x64 \ - -DBUILD_TESTS=OFF \ - -DVCPKG_TRIPLET=x64-windows \ - -DCMAKE_BUILD_TYPE=Release \ - -S . -cmake --build ./build --config Release +cmake -B build -DINTEGRATE_VCPKG=ON -DBUILD_PERF_TOOLS=ON +cmake --build build -j8 ``` -Then all artifacts will be built into `build` subdirectory. - -> **NOTE**: -> -> 1. For Windows 32-bit, you need to use `-A Win32` and `-DVCPKG_TRIPLET=x86-windows`. -> 2. For MSVC Debug mode, you need to replace `Release` with `Debug` for both `CMAKE_BUILD_TYPE` variable and `--config` option. - -#### Install dependencies manually - -You need to install [dlfcn-win32](https://github.com/dlfcn-win32/dlfcn-win32) in addition. - -If you installed the dependencies manually, you need to run - -```shell -#If all dependencies are in your path, all that is necessary is -cmake . - -#if all dependencies are not in your path, then passing in a PROTOC_PATH and CMAKE_PREFIX_PATH is necessary -cmake -DPROTOC_PATH=C:/protobuf/bin/protoc -DCMAKE_PREFIX_PATH="C:/boost;C:/openssl;C:/zlib;C:/curl;C:/protobuf;C:/googletest;C:/dlfcn-win32" . - -#This will generate pulsar-cpp.sln. Open this in Visual Studio and build the desired configurations. -``` +Then the perf tools will be built under `./build/perf/`. -#### Checks - -Client library will be placed in: - -``` -build/lib/Release/pulsar.lib -build/lib/Release/pulsar.dll -``` - -#### Examples - -Add Windows environment paths: - -``` -build/lib/Release -vcpkg_installed -``` - -Examples will be available in: - -``` -build/examples/Release -``` - -## Tests - -```shell -# Execution -# Start standalone broker -./pulsar-test-service-start.sh +## Platforms -# Run the tests -cd tests -./pulsar-tests +Pulsar C++ Client Library has been tested on: -# When no longer needed, stop standalone broker -./pulsar-test-service-stop.sh -``` +- Linux +- Mac OS X +- Windows x64 ## Wireshark Dissector diff --git a/build-support/stage-release.sh b/build-support/stage-release.sh index 2f26d383..c6665948 100755 --- a/build-support/stage-release.sh +++ b/build-support/stage-release.sh @@ -43,6 +43,11 @@ pushd "$DEST_PATH" tar cvzf x64-windows-static.tar.gz x64-windows-static tar cvzf x86-windows-static.tar.gz x86-windows-static rm -r x64-windows-static x86-windows-static +mv macos-arm64.zip macos-arm64 +mv macos-arm64/* . +mv macos-x86_64.zip macos-x86_64 +mv macos-x86_64/* . +rm -rf macos-x86_64/ macos-arm64/ popd # Sign all files diff --git a/dependencies.yaml b/dependencies.yaml index f206ccc4..8d338e4d 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -17,11 +17,12 @@ # under the License. # -boost : 1.80.0 -cmake: 3.24.2 +# Note: GCC 4.8 is incompatible with Boost >= 1.84 for the missed std::align +boost: 1.83.0 +cmake: 3.28.3 protobuf: 3.20.0 -zlib: 1.2.12 -zstd: 1.5.2 -snappy: 1.1.9 -openssl: 1.1.1q -curl: 8.4.0 +zlib: 1.3.1 +zstd: 1.5.5 +snappy: 1.1.10 +openssl: 1.1.1w +curl: 8.6.0 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index af1c91aa..d54c2284 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -86,6 +86,11 @@ add_executable(SampleKeyValueSchemaConsumer ${SAMPLE_KEY_VALUE_SCHEMA_ add_executable(SampleKeyValueSchemaProducer ${SAMPLE_KEY_VALUE_SCHEMA_PRODUCER}) add_executable(SampleCustomLoggerCApi ${SAMPLE_CUSTOM_LOGGER_CAPI}) +if (INTEGRATE_VCPKG) + # pulsarShared already carries INCLUDE_DIRECTORIES and LINK_DIRECTORIES properties + set(CLIENT_LIBS "") +endif () + target_link_libraries(SampleAsyncProducer ${CLIENT_LIBS} pulsarShared) target_link_libraries(SampleConsumer ${CLIENT_LIBS} pulsarShared) target_link_libraries(SampleConsumerListener ${CLIENT_LIBS} pulsarShared) diff --git a/include/pulsar/ClientConfiguration.h b/include/pulsar/ClientConfiguration.h index 3d651e9c..cc3c3ed3 100644 --- a/include/pulsar/ClientConfiguration.h +++ b/include/pulsar/ClientConfiguration.h @@ -32,6 +32,10 @@ class PULSAR_PUBLIC ClientConfiguration { ~ClientConfiguration(); ClientConfiguration(const ClientConfiguration&); ClientConfiguration& operator=(const ClientConfiguration&); + enum ProxyProtocol + { + SNI = 0 + }; /** * Configure a limit on the amount of memory that will be allocated by this client instance. @@ -320,6 +324,33 @@ class PULSAR_PUBLIC ClientConfiguration { */ ClientConfiguration& setConnectionTimeout(int timeoutMs); + /** + * Set proxy-service url when client would like to connect to broker via proxy. Client must configure both + * proxyServiceUrl and appropriate proxyProtocol. + * + * Example: pulsar+ssl://ats-proxy.example.com:4443 + * + * @param proxyServiceUrl proxy url to connect with broker + * @return + */ + ClientConfiguration& setProxyServiceUrl(const std::string& proxyServiceUrl); + + const std::string& getProxyServiceUrl() const; + + /** + * Set appropriate proxy-protocol along with proxy-service url. Currently Pulsar supports SNI proxy + * routing. + * + * SNI routing: + * https://docs.trafficserver.apache.org/en/latest/admin-guide/layer-4-routing.en.html#sni-routing. + * + * @param proxyProtocol possible options (SNI) + * @return + */ + ClientConfiguration& setProxyProtocol(ProxyProtocol proxyProtocol); + + ProxyProtocol getProxyProtocol() const; + /** * The getter associated with setConnectionTimeout(). */ diff --git a/include/pulsar/Consumer.h b/include/pulsar/Consumer.h index 6596894d..63defdb8 100644 --- a/include/pulsar/Consumer.h +++ b/include/pulsar/Consumer.h @@ -48,10 +48,15 @@ class PULSAR_PUBLIC Consumer { const std::string& getTopic() const; /** - * @return the consumer name + * @return the subscription name */ const std::string& getSubscriptionName() const; + /** + * @return the consumer name + */ + const std::string& getConsumerName() const; + /** * Unsubscribe the current consumer from the topic. * diff --git a/include/pulsar/c/client_configuration.h b/include/pulsar/c/client_configuration.h index 90a29a61..7ff5dcb7 100644 --- a/include/pulsar/c/client_configuration.h +++ b/include/pulsar/c/client_configuration.h @@ -187,6 +187,11 @@ PULSAR_PUBLIC int pulsar_client_configuration_is_validate_hostname(pulsar_client PULSAR_PUBLIC void pulsar_client_configuration_set_validate_hostname(pulsar_client_configuration_t *conf, int validateHostName); +PULSAR_PUBLIC void pulsar_client_configuration_set_listener_name(pulsar_client_configuration_t *conf, + const char *listenerName); + +PULSAR_PUBLIC const char *pulsar_client_configuration_get_listener_name(pulsar_client_configuration_t *conf); + /* * Get the stats interval set in the client. */ diff --git a/lib/AckGroupingTrackerEnabled.cc b/lib/AckGroupingTrackerEnabled.cc index d90cc874..7233b2c9 100644 --- a/lib/AckGroupingTrackerEnabled.cc +++ b/lib/AckGroupingTrackerEnabled.cc @@ -117,7 +117,7 @@ void AckGroupingTrackerEnabled::close() { this->flush(); std::lock_guard lock(this->mutexTimer_); if (this->timer_) { - boost::system::error_code ec; + ASIO_ERROR ec; this->timer_->cancel(ec); } } @@ -168,9 +168,9 @@ void AckGroupingTrackerEnabled::scheduleTimer() { std::lock_guard lock(this->mutexTimer_); this->timer_ = this->executor_->createDeadlineTimer(); - this->timer_->expires_from_now(boost::posix_time::milliseconds(std::max(1L, this->ackGroupingTimeMs_))); + this->timer_->expires_from_now(std::chrono::milliseconds(std::max(1L, this->ackGroupingTimeMs_))); auto self = shared_from_this(); - this->timer_->async_wait([this, self](const boost::system::error_code& ec) -> void { + this->timer_->async_wait([this, self](const ASIO_ERROR& ec) -> void { if (!ec) { this->flush(); this->scheduleTimer(); diff --git a/lib/AckGroupingTrackerEnabled.h b/lib/AckGroupingTrackerEnabled.h index ec1d66be..b04f4059 100644 --- a/lib/AckGroupingTrackerEnabled.h +++ b/lib/AckGroupingTrackerEnabled.h @@ -22,18 +22,17 @@ #include #include -#include #include #include #include #include "AckGroupingTracker.h" +#include "AsioTimer.h" namespace pulsar { class ClientImpl; using ClientImplPtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; class HandlerBase; diff --git a/lib/AsioDefines.h b/lib/AsioDefines.h new file mode 100644 index 00000000..2e89812c --- /dev/null +++ b/lib/AsioDefines.h @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// This header defines common macros to use Asio or Boost.Asio. +#pragma once + +#ifdef USE_ASIO +#define ASIO ::asio +#define ASIO_ERROR asio::error_code +#define ASIO_SUCCESS (ASIO_ERROR{}) +#define ASIO_SYSTEM_ERROR asio::system_error +#else +#define ASIO boost::asio +#define ASIO_ERROR boost::system::error_code +#define ASIO_SUCCESS boost::system::errc::make_error_code(boost::system::errc::success) +#define ASIO_SYSTEM_ERROR boost::system::system_error +#endif diff --git a/lib/AsioTimer.h b/lib/AsioTimer.h new file mode 100644 index 00000000..d0c3de58 --- /dev/null +++ b/lib/AsioTimer.h @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#pragma once + +#ifdef USE_ASIO +#include +#else +#include +#endif + +#include + +#include "AsioDefines.h" + +using DeadlineTimerPtr = std::shared_ptr; diff --git a/lib/Backoff.cc b/lib/Backoff.cc index 4d954220..e2c43d1c 100644 --- a/lib/Backoff.cc +++ b/lib/Backoff.cc @@ -21,7 +21,9 @@ #include /* time */ #include -#include +#include + +#include "TimeUtils.h" namespace pulsar { @@ -34,8 +36,8 @@ TimeDuration Backoff::next() { // Check for mandatory stop if (!mandatoryStopMade_) { - const boost::posix_time::ptime& now = boost::posix_time::microsec_clock::universal_time(); - TimeDuration timeElapsedSinceFirstBackoff = boost::posix_time::milliseconds(0); + auto now = TimeUtils::now(); + TimeDuration timeElapsedSinceFirstBackoff = std::chrono::nanoseconds(0); if (initial_ == current) { firstBackoffTime_ = now; } else { @@ -47,7 +49,7 @@ TimeDuration Backoff::next() { } } // Add Randomness - boost::random::uniform_int_distribution dist; + std::uniform_int_distribution dist; int randomNumber = dist(rng_); current = current - (current * (randomNumber % 10) / 100); diff --git a/lib/Backoff.h b/lib/Backoff.h index 4bcebc75..d9d7fae3 100644 --- a/lib/Backoff.h +++ b/lib/Backoff.h @@ -20,12 +20,12 @@ #define _PULSAR_BACKOFF_HEADER_ #include -#include -#include +#include +#include -namespace pulsar { +#include "TimeUtils.h" -using TimeDuration = boost::posix_time::time_duration; +namespace pulsar { class PULSAR_PUBLIC Backoff { public: @@ -38,8 +38,8 @@ class PULSAR_PUBLIC Backoff { const TimeDuration max_; TimeDuration next_; TimeDuration mandatoryStop_; - boost::posix_time::ptime firstBackoffTime_; - boost::random::mt19937 rng_; + decltype(std::chrono::high_resolution_clock::now()) firstBackoffTime_; + std::mt19937 rng_; bool mandatoryStopMade_ = false; friend class PulsarFriend; diff --git a/lib/BatchMessageKeyBasedContainer.cc b/lib/BatchMessageKeyBasedContainer.cc index 20067363..5b181843 100644 --- a/lib/BatchMessageKeyBasedContainer.cc +++ b/lib/BatchMessageKeyBasedContainer.cc @@ -77,11 +77,16 @@ std::vector> BatchMessageKeyBasedContainer::createOpS // Store raw pointers to use std::sort std::vector rawOpSendMsgs; for (auto& kv : batches_) { - rawOpSendMsgs.emplace_back(createOpSendMsgHelper(kv.second).release()); + if (!kv.second.empty()) { + rawOpSendMsgs.emplace_back(createOpSendMsgHelper(kv.second).release()); + } } std::sort(rawOpSendMsgs.begin(), rawOpSendMsgs.end(), [](const OpSendMsg* lhs, const OpSendMsg* rhs) { return lhs->sendArgs->sequenceId < rhs->sendArgs->sequenceId; }); + if (rawOpSendMsgs.empty()) { + return {}; + } rawOpSendMsgs.back()->addTrackerCallback(flushCallback); std::vector> opSendMsgs{rawOpSendMsgs.size()}; diff --git a/lib/BinaryProtoLookupService.cc b/lib/BinaryProtoLookupService.cc index c8d6b769..489d8a27 100644 --- a/lib/BinaryProtoLookupService.cc +++ b/lib/BinaryProtoLookupService.cc @@ -22,7 +22,6 @@ #include "ConnectionPool.h" #include "LogUtils.h" #include "NamespaceName.h" -#include "ServiceNameResolver.h" #include "TopicName.h" DECLARE_LOG_OBJECT() @@ -83,12 +82,13 @@ auto BinaryProtoLookupService::findBroker(const std::string& address, bool autho } }); } else { - LOG_DEBUG("Lookup response for " << topic << ", lookup-broker-url " << data->getBrokerUrl()); + LOG_INFO("Lookup response for " << topic << ", lookup-broker-url " << data->getBrokerUrl() + << ", from " << cnx->cnxString()); if (data->shouldProxyThroughServiceUrl()) { // logicalAddress is the proxy's address, we should still connect through proxy - promise->setValue({responseBrokerAddress, address}); + promise->setValue({responseBrokerAddress, address, true}); } else { - promise->setValue({responseBrokerAddress, responseBrokerAddress}); + promise->setValue({responseBrokerAddress, responseBrokerAddress, false}); } } }); diff --git a/lib/BinaryProtoLookupService.h b/lib/BinaryProtoLookupService.h index a3c059e4..6132825d 100644 --- a/lib/BinaryProtoLookupService.h +++ b/lib/BinaryProtoLookupService.h @@ -38,9 +38,9 @@ using GetSchemaPromisePtr = std::shared_ptr>; class PULSAR_PUBLIC BinaryProtoLookupService : public LookupService { public: - BinaryProtoLookupService(ServiceNameResolver& serviceNameResolver, ConnectionPool& pool, + BinaryProtoLookupService(const std::string& serviceUrl, ConnectionPool& pool, const ClientConfiguration& clientConfiguration) - : serviceNameResolver_(serviceNameResolver), + : serviceNameResolver_(serviceUrl), cnxPool_(pool), listenerName_(clientConfiguration.getListenerName()), maxLookupRedirects_(clientConfiguration.getMaxLookupRedirects()) {} @@ -54,6 +54,8 @@ class PULSAR_PUBLIC BinaryProtoLookupService : public LookupService { Future getSchema(const TopicNamePtr& topicName, const std::string& version) override; + ServiceNameResolver& getServiceNameResolver() override { return serviceNameResolver_; } + protected: // Mark findBroker as protected to make it accessible from test. LookupResultFuture findBroker(const std::string& address, bool authoritative, const std::string& topic, @@ -63,7 +65,7 @@ class PULSAR_PUBLIC BinaryProtoLookupService : public LookupService { std::mutex mutex_; uint64_t requestIdGenerator_ = 0; - ServiceNameResolver& serviceNameResolver_; + ServiceNameResolver serviceNameResolver_; ConnectionPool& cnxPool_; std::string listenerName_; const int32_t maxLookupRedirects_; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index eca76380..6edc05e2 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -27,16 +27,25 @@ set(LIB_AUTOGEN_DIR ${AUTOGEN_DIR}/lib) file(MAKE_DIRECTORY ${LIB_AUTOGEN_DIR}) include_directories(${LIB_AUTOGEN_DIR}) -# Protobuf generation is only supported natively starting from CMake 3.8 -# Using custom command for now -set(PROTO_SOURCES ${LIB_AUTOGEN_DIR}/PulsarApi.pb.cc ${LIB_AUTOGEN_DIR}/PulsarApi.pb.h) -set(PULSAR_SOURCES ${PULSAR_SOURCES} ${PROTO_SOURCES}) -ADD_CUSTOM_COMMAND( - OUTPUT ${PROTO_SOURCES} - COMMAND ${PROTOC_PATH} -I ../proto ../proto/PulsarApi.proto --cpp_out=${LIB_AUTOGEN_DIR} - DEPENDS - ../proto/PulsarApi.proto - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +if (INTEGRATE_VCPKG) + add_library(PROTO_OBJECTS OBJECT "${CMAKE_SOURCE_DIR}/proto/PulsarApi.proto") + target_link_libraries(PROTO_OBJECTS PUBLIC protobuf::libprotobuf) + target_include_directories(PROTO_OBJECTS PUBLIC ${LIB_AUTOGEN_DIR}) + protobuf_generate( + TARGET PROTO_OBJECTS + IMPORT_DIRS ${CMAKE_SOURCE_DIR}/proto + PROTOC_OUT_DIR ${LIB_AUTOGEN_DIR}) + set(COMMON_LIBS ${COMMON_LIBS} PROTO_OBJECTS) +else () + set(PROTO_SOURCES ${LIB_AUTOGEN_DIR}/PulsarApi.pb.cc ${LIB_AUTOGEN_DIR}/PulsarApi.pb.h) + set(PULSAR_SOURCES ${PULSAR_SOURCES} ${PROTO_SOURCES}) + ADD_CUSTOM_COMMAND( + OUTPUT ${PROTO_SOURCES} + COMMAND ${PROTOC_PATH} -I ../proto ../proto/PulsarApi.proto --cpp_out=${LIB_AUTOGEN_DIR} + DEPENDS + ../proto/PulsarApi.proto + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endif () set(LIBRARY_VERSION $ENV{PULSAR_LIBRARY_VERSION}) if (NOT LIBRARY_VERSION) @@ -44,7 +53,6 @@ if (NOT LIBRARY_VERSION) endif(NOT LIBRARY_VERSION) if (MSVC) - find_package(dlfcn-win32 REQUIRED) set(CMAKE_DL_LIBS dlfcn-win32::dl psapi.lib) if (CMAKE_BUILD_TYPE STREQUAL "Debug") get_target_property(dlfcn-win32_LIBRARY dlfcn-win32::dl IMPORTED_LOCATION_DEBUG) @@ -60,12 +68,23 @@ set(LIB_NAME_SHARED ${LIB_NAME}) # this is the "object library" target: compiles the sources only once add_library(PULSAR_OBJECT_LIB OBJECT ${PULSAR_SOURCES}) set_property(TARGET PULSAR_OBJECT_LIB PROPERTY POSITION_INDEPENDENT_CODE 1) +if (INTEGRATE_VCPKG) + target_link_libraries(PULSAR_OBJECT_LIB PROTO_OBJECTS) +endif () +target_include_directories(PULSAR_OBJECT_LIB PUBLIC + "${CMAKE_SOURCE_DIR}" + "${CMAKE_SOURCE_DIR}/include" + "${CMAKE_BINARY_DIR}/include") if (BUILD_DYNAMIC_LIB) add_library(pulsarShared SHARED $) set_property(TARGET pulsarShared PROPERTY OUTPUT_NAME ${LIB_NAME_SHARED}) set_property(TARGET pulsarShared PROPERTY VERSION ${LIBRARY_VERSION}) - target_link_libraries(pulsarShared ${COMMON_LIBS} ${CMAKE_DL_LIBS}) + target_link_libraries(pulsarShared PRIVATE ${COMMON_LIBS} ${CMAKE_DL_LIBS}) + target_include_directories(pulsarShared PUBLIC + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include) + target_link_directories(pulsarShared PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) if (MSVC) target_include_directories(pulsarShared PRIVATE ${dlfcn-win32_INCLUDE_DIRS}) target_link_options(pulsarShared PRIVATE $<$:/NODEFAULTLIB:MSVCRT>) @@ -80,6 +99,10 @@ endif() if (BUILD_STATIC_LIB) add_library(pulsarStatic STATIC $) + target_include_directories(pulsarStatic PUBLIC + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include) if (MSVC) set_property(TARGET pulsarStatic PROPERTY OUTPUT_NAME "${LIB_NAME}-static") target_include_directories(pulsarStatic PRIVATE ${dlfcn-win32_INCLUDE_DIRS}) @@ -156,3 +179,4 @@ if (BUILD_DYNAMIC_LIB) endif() install(DIRECTORY "../include/pulsar" DESTINATION include) +install(FILES "${PROJECT_BINARY_DIR}/include/pulsar/Version.h" DESTINATION include/pulsar/) diff --git a/lib/ClientConfiguration.cc b/lib/ClientConfiguration.cc index 977a8807..6a91ec1d 100644 --- a/lib/ClientConfiguration.cc +++ b/lib/ClientConfiguration.cc @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +#include #include #include "ClientConfigurationImpl.h" +#include "auth/AuthOauth2.h" namespace pulsar { @@ -60,11 +62,13 @@ Authentication& ClientConfiguration::getAuth() const { return *impl_->authentica const AuthenticationPtr& ClientConfiguration::getAuthPtr() const { return impl_->authenticationPtr; } ClientConfiguration& ClientConfiguration::setOperationTimeoutSeconds(int timeout) { - impl_->operationTimeoutSeconds = timeout; + impl_->operationTimeout = std::chrono::seconds(timeout); return *this; } -int ClientConfiguration::getOperationTimeoutSeconds() const { return impl_->operationTimeoutSeconds; } +int ClientConfiguration::getOperationTimeoutSeconds() const { + return std::chrono::duration_cast(impl_->operationTimeout).count(); +} ClientConfiguration& ClientConfiguration::setIOThreads(int threads) { impl_->ioThreads = threads; @@ -133,6 +137,22 @@ ClientConfiguration& ClientConfiguration::setConcurrentLookupRequest(int concurr return *this; } +ClientConfiguration& ClientConfiguration::setProxyServiceUrl(const std::string& proxyServiceUrl) { + impl_->proxyServiceUrl = proxyServiceUrl; + return *this; +} + +const std::string& ClientConfiguration::getProxyServiceUrl() const { return impl_->proxyServiceUrl; } + +ClientConfiguration& ClientConfiguration::setProxyProtocol(ClientConfiguration::ProxyProtocol proxyProtocol) { + impl_->proxyProtocol = proxyProtocol; + return *this; +} + +ClientConfiguration::ProxyProtocol ClientConfiguration::getProxyProtocol() const { + return impl_->proxyProtocol; +} + int ClientConfiguration::getConcurrentLookupRequest() const { return impl_->concurrentLookupRequest; } ClientConfiguration& ClientConfiguration::setMaxLookupRedirects(int maxLookupRedirects) { diff --git a/lib/ClientConfigurationImpl.h b/lib/ClientConfigurationImpl.h index 3458a052..08d4b6e4 100644 --- a/lib/ClientConfigurationImpl.h +++ b/lib/ClientConfigurationImpl.h @@ -21,6 +21,8 @@ #include +#include + namespace pulsar { struct ClientConfigurationImpl { @@ -28,7 +30,7 @@ struct ClientConfigurationImpl { uint64_t memoryLimit{0ull}; int ioThreads{1}; int connectionsPerBroker{1}; - int operationTimeoutSeconds{30}; + std::chrono::nanoseconds operationTimeout{30L * 1000 * 1000 * 1000}; int messageListenerThreads{1}; int concurrentLookupRequest{50000}; int maxLookupRedirects{20}; @@ -46,6 +48,8 @@ struct ClientConfigurationImpl { std::string listenerName; int connectionTimeoutMs{10000}; // 10 seconds std::string description; + std::string proxyServiceUrl; + ClientConfiguration::ProxyProtocol proxyProtocol; std::unique_ptr takeLogger() { return std::move(loggerFactory); } }; diff --git a/lib/ClientConnection.cc b/lib/ClientConnection.cc index 8f42535c..04202d31 100644 --- a/lib/ClientConnection.cc +++ b/lib/ClientConnection.cc @@ -23,6 +23,9 @@ #include #include +#include "AsioDefines.h" +#include "ClientConnectionAdaptor.h" +#include "ClientImpl.h" #include "Commands.h" #include "ConnectionPool.h" #include "ConsumerImpl.h" @@ -33,12 +36,13 @@ #include "PulsarApi.pb.h" #include "ResultUtils.h" #include "Url.h" +#include "auth/AuthOauth2.h" #include "auth/InitialAuthData.h" #include "checksum/ChecksumProvider.h" DECLARE_LOG_OBJECT() -using namespace boost::asio::ip; +using namespace ASIO::ip; namespace pulsar { @@ -77,9 +81,9 @@ static Result getResult(ServerError serverError, const std::string& message) { return ResultConsumerBusy; case ServiceNotReady: - // If the error is not caused by a PulsarServerException, treat it as retryable. - return (message.find("PulsarServerException") == std::string::npos) ? ResultRetryable - : ResultServiceUnitNotReady; + return (message.find("the broker do not have test listener") == std::string::npos) + ? ResultRetryable + : ResultConnectError; case ProducerBlockedQuotaExceededError: return ResultProducerBlockedQuotaExceededError; @@ -160,20 +164,14 @@ ClientConnection::ClientConnection(const std::string& logicalAddress, const std: ExecutorServicePtr executor, const ClientConfiguration& clientConfiguration, const AuthenticationPtr& authentication, const std::string& clientVersion, - ConnectionPool& pool) - : operationsTimeout_(seconds(clientConfiguration.getOperationTimeoutSeconds())), + ConnectionPool& pool, size_t poolIndex) + : operationsTimeout_(ClientImpl::getOperationTimeout(clientConfiguration)), authentication_(authentication), serverProtocolVersion_(proto::ProtocolVersion_MIN), executor_(executor), resolver_(executor_->createTcpResolver()), socket_(executor_->createSocket()), -#if BOOST_VERSION >= 107000 - strand_(boost::asio::make_strand(executor_->getIOService().get_executor())), -#elif BOOST_VERSION >= 106600 - strand_(executor_->getIOService().get_executor()), -#else - strand_(executor_->getIOService()), -#endif + strand_(ASIO::make_strand(executor_->getIOService().get_executor())), logicalAddress_(logicalAddress), physicalAddress_(physicalAddress), cnxString_("[ -> " + physicalAddress + "] "), @@ -184,21 +182,40 @@ ClientConnection::ClientConnection(const std::string& logicalAddress, const std: consumerStatsRequestTimer_(executor_->createDeadlineTimer()), maxPendingLookupRequest_(clientConfiguration.getConcurrentLookupRequest()), clientVersion_(clientVersion), - pool_(pool) { + pool_(pool), + poolIndex_(poolIndex) { LOG_INFO(cnxString_ << "Create ClientConnection, timeout=" << clientConfiguration.getConnectionTimeout()); + if (!authentication_) { + LOG_ERROR("Invalid authentication plugin"); + throw ResultAuthenticationError; + return; + } + + auto oauth2Auth = std::dynamic_pointer_cast(authentication_); + if (oauth2Auth) { + // Configure the TLS trust certs file for Oauth2 + auto authData = std::dynamic_pointer_cast( + std::make_shared(clientConfiguration.getTlsTrustCertsFilePath())); + oauth2Auth->getAuthData(authData); + } + if (clientConfiguration.isUseTls()) { -#if BOOST_VERSION >= 105400 - boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client); -#else - boost::asio::ssl::context ctx(executor_->getIOService(), boost::asio::ssl::context::tlsv1_client); -#endif + ASIO::ssl::context ctx(ASIO::ssl::context::tlsv12_client); Url serviceUrl; + Url proxyUrl; Url::parse(physicalAddress, serviceUrl); + proxyServiceUrl_ = clientConfiguration.getProxyServiceUrl(); + proxyProtocol_ = clientConfiguration.getProxyProtocol(); + if (proxyProtocol_ == ClientConfiguration::SNI && !proxyServiceUrl_.empty()) { + Url::parse(proxyServiceUrl_, proxyUrl); + isSniProxy_ = true; + LOG_INFO("Configuring SNI Proxy-url=" << proxyServiceUrl_); + } if (clientConfiguration.isTlsAllowInsecureConnection()) { - ctx.set_verify_mode(boost::asio::ssl::context::verify_none); + ctx.set_verify_mode(ASIO::ssl::context::verify_none); isTlsAllowInsecureConnection_ = true; } else { - ctx.set_verify_mode(boost::asio::ssl::context::verify_peer); + ctx.set_verify_mode(ASIO::ssl::context::verify_peer); std::string trustCertFilePath = clientConfiguration.getTlsTrustCertsFilePath(); if (!trustCertFilePath.empty()) { @@ -206,44 +223,34 @@ ClientConnection::ClientConnection(const std::string& logicalAddress, const std: ctx.load_verify_file(trustCertFilePath); } else { LOG_ERROR(trustCertFilePath << ": No such trustCertFile"); - close(ResultAuthenticationError, false); - return; + throw ResultAuthenticationError; } } else { ctx.set_default_verify_paths(); } } - if (!authentication_) { - LOG_ERROR("Invalid authentication plugin"); - close(ResultAuthenticationError, false); - return; - } - std::string tlsCertificates = clientConfiguration.getTlsCertificateFilePath(); std::string tlsPrivateKey = clientConfiguration.getTlsPrivateKeyFilePath(); - auto authData = std::dynamic_pointer_cast( - std::make_shared(clientConfiguration.getTlsTrustCertsFilePath())); + AuthenticationDataPtr authData; if (authentication_->getAuthData(authData) == ResultOk && authData->hasDataForTls()) { tlsCertificates = authData->getTlsCertificates(); tlsPrivateKey = authData->getTlsPrivateKey(); if (!file_exists(tlsCertificates)) { LOG_ERROR(tlsCertificates << ": No such tlsCertificates"); - close(ResultAuthenticationError, false); - return; + throw ResultAuthenticationError; } if (!file_exists(tlsCertificates)) { LOG_ERROR(tlsCertificates << ": No such tlsCertificates"); - close(ResultAuthenticationError, false); - return; + throw ResultAuthenticationError; } - ctx.use_private_key_file(tlsPrivateKey, boost::asio::ssl::context::pem); - ctx.use_certificate_file(tlsCertificates, boost::asio::ssl::context::pem); + ctx.use_private_key_file(tlsPrivateKey, ASIO::ssl::context::pem); + ctx.use_certificate_file(tlsCertificates, ASIO::ssl::context::pem); } else { if (file_exists(tlsPrivateKey) && file_exists(tlsCertificates)) { - ctx.use_private_key_file(tlsPrivateKey, boost::asio::ssl::context::pem); - ctx.use_certificate_file(tlsCertificates, boost::asio::ssl::context::pem); + ctx.use_private_key_file(tlsPrivateKey, ASIO::ssl::context::pem); + ctx.use_certificate_file(tlsCertificates, ASIO::ssl::context::pem); } } @@ -251,21 +258,21 @@ ClientConnection::ClientConnection(const std::string& logicalAddress, const std: if (!clientConfiguration.isTlsAllowInsecureConnection() && clientConfiguration.isValidateHostName()) { LOG_DEBUG("Validating hostname for " << serviceUrl.host() << ":" << serviceUrl.port()); - tlsSocket_->set_verify_callback(boost::asio::ssl::rfc2818_verification(serviceUrl.host())); + std::string urlHost = isSniProxy_ ? proxyUrl.host() : serviceUrl.host(); + tlsSocket_->set_verify_callback(ASIO::ssl::rfc2818_verification(urlHost)); } LOG_DEBUG("TLS SNI Host: " << serviceUrl.host()); if (!SSL_set_tlsext_host_name(tlsSocket_->native_handle(), serviceUrl.host().c_str())) { - boost::system::error_code ec{static_cast(::ERR_get_error()), - boost::asio::error::get_ssl_category()}; - LOG_ERROR(boost::system::system_error{ec}.what() << ": Error while setting TLS SNI"); + ASIO_ERROR ec{static_cast(::ERR_get_error()), ASIO::error::get_ssl_category()}; + LOG_ERROR(ec.message() << ": Error while setting TLS SNI"); return; } } } ClientConnection::~ClientConnection() { - LOG_INFO(cnxString_ << "Destroyed connection to " << logicalAddress_); + LOG_INFO(cnxString_ << "Destroyed connection to " << logicalAddress_ << "-" << poolIndex_); } void ClientConnection::handlePulsarConnected(const proto::CommandConnected& cmdConnected) { @@ -295,9 +302,9 @@ void ClientConnection::handlePulsarConnected(const proto::CommandConnected& cmdC // Only send keep-alive probes if the broker supports it keepAliveTimer_ = executor_->createDeadlineTimer(); if (keepAliveTimer_) { - keepAliveTimer_->expires_from_now(boost::posix_time::seconds(KeepAliveIntervalInSeconds)); + keepAliveTimer_->expires_from_now(std::chrono::seconds(KeepAliveIntervalInSeconds)); auto weakSelf = weak_from_this(); - keepAliveTimer_->async_wait([weakSelf](const boost::system::error_code&) { + keepAliveTimer_->async_wait([weakSelf](const ASIO_ERROR&) { auto self = weakSelf.lock(); if (self) { self->handleKeepAliveTimeout(); @@ -342,13 +349,12 @@ void ClientConnection::startConsumerStatsTimer(std::vector consumerSta if (consumerStatsRequestTimer_) { consumerStatsRequestTimer_->expires_from_now(operationsTimeout_); auto weakSelf = weak_from_this(); - consumerStatsRequestTimer_->async_wait( - [weakSelf, consumerStatsRequests](const boost::system::error_code& err) { - auto self = weakSelf.lock(); - if (self) { - self->handleConsumerStatsTimeout(err, consumerStatsRequests); - } - }); + consumerStatsRequestTimer_->async_wait([weakSelf, consumerStatsRequests](const ASIO_ERROR& err) { + auto self = weakSelf.lock(); + if (self) { + self->handleConsumerStatsTimeout(err, consumerStatsRequests); + } + }); } lock.unlock(); // Complex logic since promises need to be fulfilled outside the lock @@ -360,19 +366,19 @@ void ClientConnection::startConsumerStatsTimer(std::vector consumerSta /// The number of unacknowledged probes to send before considering the connection dead and notifying the /// application layer -typedef boost::asio::detail::socket_option::integer tcp_keep_alive_count; +typedef ASIO::detail::socket_option::integer tcp_keep_alive_count; /// The interval between subsequential keepalive probes, regardless of what the connection has exchanged in /// the meantime -typedef boost::asio::detail::socket_option::integer tcp_keep_alive_interval; +typedef ASIO::detail::socket_option::integer tcp_keep_alive_interval; /// The interval between the last data packet sent (simple ACKs are not considered data) and the first /// keepalive /// probe; after the connection is marked to need keepalive, this counter is not used any further #ifdef __APPLE__ -typedef boost::asio::detail::socket_option::integer tcp_keep_alive_idle; +typedef ASIO::detail::socket_option::integer tcp_keep_alive_idle; #else -typedef boost::asio::detail::socket_option::integer tcp_keep_alive_idle; +typedef ASIO::detail::socket_option::integer tcp_keep_alive_idle; #endif /* @@ -381,15 +387,14 @@ typedef boost::asio::detail::socket_option::integer t * if async_connect without any error, connected_ would be set to true * at this point the connection is deemed valid to be used by clients of this class */ -void ClientConnection::handleTcpConnected(const boost::system::error_code& err, - tcp::resolver::iterator endpointIterator) { +void ClientConnection::handleTcpConnected(const ASIO_ERROR& err, tcp::resolver::iterator endpointIterator) { if (!err) { std::stringstream cnxStringStream; try { cnxStringStream << "[" << socket_->local_endpoint() << " -> " << socket_->remote_endpoint() << "] "; cnxString_ = cnxStringStream.str(); - } catch (const boost::system::system_error& e) { + } catch (const ASIO_SYSTEM_ERROR& e) { LOG_ERROR("Failed to get endpoint: " << e.what()); close(ResultRetryable); return; @@ -397,7 +402,9 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, if (logicalAddress_ == physicalAddress_) { LOG_INFO(cnxString_ << "Connected to broker"); } else { - LOG_INFO(cnxString_ << "Connected to broker through proxy. Logical broker: " << logicalAddress_); + LOG_INFO(cnxString_ << "Connected to broker through proxy. Logical broker: " << logicalAddress_ + << ", proxy: " << proxyServiceUrl_ + << ", physical address:" << physicalAddress_); } Lock lock(mutex_); @@ -408,7 +415,7 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, state_ = TcpConnected; lock.unlock(); - boost::system::error_code error; + ASIO_ERROR error; socket_->set_option(tcp::no_delay(true), error); if (error) { LOG_WARN(cnxString_ << "Socket failed to set tcp::no_delay: " << error.message()); @@ -441,7 +448,7 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, if (tlsSocket_) { if (!isTlsAllowInsecureConnection_) { - boost::system::error_code err; + ASIO_ERROR err; Url service_url; if (!Url::parse(physicalAddress_, service_url)) { LOG_ERROR(cnxString_ << "Invalid Url, unable to parse: " << err << " " << err.message()); @@ -450,26 +457,25 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, } } auto weakSelf = weak_from_this(); - auto callback = [weakSelf](const boost::system::error_code& err) { + auto socket = socket_; + auto tlsSocket = tlsSocket_; + // socket and ssl::stream objects must exist until async_handshake is done, otherwise segmentation + // fault might happen + auto callback = [weakSelf, socket, tlsSocket](const ASIO_ERROR& err) { auto self = weakSelf.lock(); if (self) { self->handleHandshake(err); } }; -#if BOOST_VERSION >= 106600 - tlsSocket_->async_handshake(boost::asio::ssl::stream::client, - boost::asio::bind_executor(strand_, callback)); -#else - tlsSocket_->async_handshake(boost::asio::ssl::stream::client, - strand_.wrap(callback)); -#endif + tlsSocket_->async_handshake(ASIO::ssl::stream::client, + ASIO::bind_executor(strand_, callback)); } else { - handleHandshake(boost::system::errc::make_error_code(boost::system::errc::success)); + handleHandshake(ASIO_SUCCESS); } } else if (endpointIterator != tcp::resolver::iterator()) { LOG_WARN(cnxString_ << "Failed to establish connection: " << err.message()); // The connection failed. Try the next endpoint in the list. - boost::system::error_code closeError; + ASIO_ERROR closeError; socket_->close(closeError); // ignore the error of close if (closeError) { LOG_WARN(cnxString_ << "Failed to close socket: " << err.message()); @@ -481,15 +487,14 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, connectTimeoutTask_->start(); tcp::endpoint endpoint = *endpointIterator; auto weakSelf = weak_from_this(); - socket_->async_connect(endpoint, - [weakSelf, endpointIterator](const boost::system::error_code& err) { - auto self = weakSelf.lock(); - if (self) { - self->handleTcpConnected(err, endpointIterator); - } - }); + socket_->async_connect(endpoint, [weakSelf, endpointIterator](const ASIO_ERROR& err) { + auto self = weakSelf.lock(); + if (self) { + self->handleTcpConnected(err, endpointIterator); + } + }); } else { - if (err == boost::asio::error::operation_aborted) { + if (err == ASIO::error::operation_aborted) { // TCP connect timeout, which is not retryable close(); } else { @@ -502,10 +507,15 @@ void ClientConnection::handleTcpConnected(const boost::system::error_code& err, } } -void ClientConnection::handleHandshake(const boost::system::error_code& err) { +void ClientConnection::handleHandshake(const ASIO_ERROR& err) { if (err) { - LOG_ERROR(cnxString_ << "Handshake failed: " << err.message()); - close(); + if (err.value() == ASIO::ssl::error::stream_truncated) { + LOG_WARN(cnxString_ << "Handshake failed: " << err.message()); + close(ResultRetryable); + } else { + LOG_ERROR(cnxString_ << "Handshake failed: " << err.message()); + close(); + } return; } @@ -521,13 +531,12 @@ void ClientConnection::handleHandshake(const boost::system::error_code& err) { // Send CONNECT command to broker auto self = shared_from_this(); asyncWrite(buffer.const_asio_buffer(), - customAllocWriteHandler([this, self, buffer](const boost::system::error_code& err, size_t) { + customAllocWriteHandler([this, self, buffer](const ASIO_ERROR& err, size_t) { handleSentPulsarConnect(err, buffer); })); } -void ClientConnection::handleSentPulsarConnect(const boost::system::error_code& err, - const SharedBuffer& buffer) { +void ClientConnection::handleSentPulsarConnect(const ASIO_ERROR& err, const SharedBuffer& buffer) { if (isClosed()) { return; } @@ -541,8 +550,7 @@ void ClientConnection::handleSentPulsarConnect(const boost::system::error_code& readNextCommand(); } -void ClientConnection::handleSentAuthResponse(const boost::system::error_code& err, - const SharedBuffer& buffer) { +void ClientConnection::handleSentAuthResponse(const ASIO_ERROR& err, const SharedBuffer& buffer) { if (isClosed()) { return; } @@ -564,9 +572,10 @@ void ClientConnection::tcpConnectAsync() { return; } - boost::system::error_code err; + ASIO_ERROR err; Url service_url; - if (!Url::parse(physicalAddress_, service_url)) { + std::string hostUrl = isSniProxy_ ? proxyServiceUrl_ : physicalAddress_; + if (!Url::parse(hostUrl, service_url)) { LOG_ERROR(cnxString_ << "Invalid Url, unable to parse: " << err << " " << err.message()); close(); return; @@ -582,19 +591,18 @@ void ClientConnection::tcpConnectAsync() { LOG_DEBUG(cnxString_ << "Resolving " << service_url.host() << ":" << service_url.port()); tcp::resolver::query query(service_url.host(), std::to_string(service_url.port())); auto weakSelf = weak_from_this(); - resolver_->async_resolve( - query, [weakSelf](const boost::system::error_code& err, tcp::resolver::iterator iterator) { - auto self = weakSelf.lock(); - if (self) { - self->handleResolve(err, iterator); - } - }); + resolver_->async_resolve(query, [weakSelf](const ASIO_ERROR& err, tcp::resolver::iterator iterator) { + auto self = weakSelf.lock(); + if (self) { + self->handleResolve(err, iterator); + } + }); } -void ClientConnection::handleResolve(const boost::system::error_code& err, - tcp::resolver::iterator endpointIterator) { +void ClientConnection::handleResolve(const ASIO_ERROR& err, tcp::resolver::iterator endpointIterator) { if (err) { - LOG_ERROR(cnxString_ << "Resolve error: " << err << " : " << err.message()); + std::string hostUrl = isSniProxy_ ? cnxString_ : proxyServiceUrl_; + LOG_ERROR(hostUrl << "Resolve error: " << err << " : " << err.message()); close(); return; } @@ -624,13 +632,12 @@ void ClientConnection::handleResolve(const boost::system::error_code& err, if (endpointIterator != tcp::resolver::iterator()) { LOG_DEBUG(cnxString_ << "Resolved hostname " << endpointIterator->host_name() // << " to " << endpointIterator->endpoint()); - socket_->async_connect(*endpointIterator, - [weakSelf, endpointIterator](const boost::system::error_code& err) { - auto self = weakSelf.lock(); - if (self) { - self->handleTcpConnected(err, endpointIterator); - } - }); + socket_->async_connect(*endpointIterator, [weakSelf, endpointIterator](const ASIO_ERROR& err) { + auto self = weakSelf.lock(); + if (self) { + self->handleTcpConnected(err, endpointIterator); + } + }); } else { LOG_WARN(cnxString_ << "No IP address found"); close(); @@ -641,15 +648,13 @@ void ClientConnection::handleResolve(const boost::system::error_code& err, void ClientConnection::readNextCommand() { const static uint32_t minReadSize = sizeof(uint32_t); auto self = shared_from_this(); - asyncReceive( - incomingBuffer_.asio_buffer(), - customAllocReadHandler([this, self](const boost::system::error_code& err, size_t bytesTransferred) { - handleRead(err, bytesTransferred, minReadSize); - })); + asyncReceive(incomingBuffer_.asio_buffer(), + customAllocReadHandler([this, self](const ASIO_ERROR& err, size_t bytesTransferred) { + handleRead(err, bytesTransferred, minReadSize); + })); } -void ClientConnection::handleRead(const boost::system::error_code& err, size_t bytesTransferred, - uint32_t minReadSize) { +void ClientConnection::handleRead(const ASIO_ERROR& err, size_t bytesTransferred, uint32_t minReadSize) { if (isClosed()) { return; } @@ -657,9 +662,9 @@ void ClientConnection::handleRead(const boost::system::error_code& err, size_t b incomingBuffer_.bytesWritten(bytesTransferred); if (err || bytesTransferred == 0) { - if (err == boost::asio::error::operation_aborted) { + if (err == ASIO::error::operation_aborted) { LOG_DEBUG(cnxString_ << "Read operation was canceled: " << err.message()); - } else if (bytesTransferred == 0 || err == boost::asio::error::eof) { + } else if (bytesTransferred == 0 || err == ASIO::error::eof) { LOG_DEBUG(cnxString_ << "Server closed the connection: " << err.message()); } else { LOG_ERROR(cnxString_ << "Read operation failed: " << err.message()); @@ -671,11 +676,11 @@ void ClientConnection::handleRead(const boost::system::error_code& err, size_t b SharedBuffer buffer = incomingBuffer_.slice(bytesTransferred); auto self = shared_from_this(); auto nextMinReadSize = minReadSize - bytesTransferred; - asyncReceive(buffer.asio_buffer(), customAllocReadHandler([this, self, nextMinReadSize]( - const boost::system::error_code& err, - size_t bytesTransferred) { - handleRead(err, bytesTransferred, nextMinReadSize); - })); + asyncReceive(buffer.asio_buffer(), + customAllocReadHandler( + [this, self, nextMinReadSize](const ASIO_ERROR& err, size_t bytesTransferred) { + handleRead(err, bytesTransferred, nextMinReadSize); + })); } else { processIncomingBuffer(); } @@ -702,12 +707,11 @@ void ClientConnection::processIncomingBuffer() { incomingBuffer_ = SharedBuffer::copyFrom(incomingBuffer_, newBufferSize); } auto self = shared_from_this(); - asyncReceive( - incomingBuffer_.asio_buffer(), - customAllocReadHandler([this, self, bytesToReceive](const boost::system::error_code& err, - size_t bytesTransferred) { - handleRead(err, bytesTransferred, bytesToReceive); - })); + asyncReceive(incomingBuffer_.asio_buffer(), + customAllocReadHandler( + [this, self, bytesToReceive](const ASIO_ERROR& err, size_t bytesTransferred) { + handleRead(err, bytesTransferred, bytesToReceive); + })); return; } @@ -785,11 +789,11 @@ void ClientConnection::processIncomingBuffer() { uint32_t minReadSize = sizeof(uint32_t) - incomingBuffer_.readableBytes(); auto self = shared_from_this(); - asyncReceive(incomingBuffer_.asio_buffer(), - customAllocReadHandler([this, self, minReadSize](const boost::system::error_code& err, - size_t bytesTransferred) { - handleRead(err, bytesTransferred, minReadSize); - })); + asyncReceive( + incomingBuffer_.asio_buffer(), + customAllocReadHandler([this, self, minReadSize](const ASIO_ERROR& err, size_t bytesTransferred) { + handleRead(err, bytesTransferred, minReadSize); + })); return; } @@ -942,6 +946,10 @@ void ClientConnection::handleIncomingCommand(BaseCommand& incomingCmd) { handleError(incomingCmd.error()); break; + case BaseCommand::TOPIC_MIGRATED: + handleTopicMigrated(incomingCmd.topicmigrated()); + break; + case BaseCommand::CLOSE_PRODUCER: handleCloseProducer(incomingCmd.close_producer()); break; @@ -1038,7 +1046,7 @@ void ClientConnection::newLookup(const SharedBuffer& cmd, const uint64_t request requestData.timer = executor_->createDeadlineTimer(); requestData.timer->expires_from_now(operationsTimeout_); auto weakSelf = weak_from_this(); - requestData.timer->async_wait([weakSelf, requestData](const boost::system::error_code& ec) { + requestData.timer->async_wait([weakSelf, requestData](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self) { self->handleLookupTimeout(ec, requestData); @@ -1064,11 +1072,7 @@ void ClientConnection::sendCommand(const SharedBuffer& cmd) { self->sendCommandInternal(cmd); } }; -#if BOOST_VERSION >= 106600 - boost::asio::post(strand_, callback); -#else - strand_.post(callback); -#endif + ASIO::post(strand_, callback); } else { sendCommandInternal(cmd); } @@ -1081,8 +1085,9 @@ void ClientConnection::sendCommand(const SharedBuffer& cmd) { void ClientConnection::sendCommandInternal(const SharedBuffer& cmd) { auto self = shared_from_this(); asyncWrite(cmd.const_asio_buffer(), - customAllocWriteHandler([this, self, cmd](const boost::system::error_code& err, - size_t bytesTransferred) { handleSend(err, cmd); })); + customAllocWriteHandler([this, self, cmd](const ASIO_ERROR& err, size_t bytesTransferred) { + handleSend(err, cmd); + })); } void ClientConnection::sendMessage(const std::shared_ptr& args) { @@ -1098,21 +1103,18 @@ void ClientConnection::sendMessage(const std::shared_ptr& args) { // Capture the buffer because asio does not copy the buffer, if the buffer is destroyed before the // callback is called, an invalid buffer range might be passed to the underlying socket send. asyncWrite(buffer, customAllocWriteHandler( - [this, self, buffer](const boost::system::error_code& err, - size_t bytesTransferred) { handleSendPair(err); })); + [this, self, buffer](const ASIO_ERROR& err, size_t bytesTransferred) { + handleSendPair(err); + })); }; if (tlsSocket_) { -#if BOOST_VERSION >= 106600 - boost::asio::post(strand_, sendMessageInternal); -#else - strand_.post(sendMessageInternal); -#endif + ASIO::post(strand_, sendMessageInternal); } else { sendMessageInternal(); } } -void ClientConnection::handleSend(const boost::system::error_code& err, const SharedBuffer&) { +void ClientConnection::handleSend(const ASIO_ERROR& err, const SharedBuffer&) { if (isClosed()) { return; } @@ -1124,7 +1126,7 @@ void ClientConnection::handleSend(const boost::system::error_code& err, const Sh } } -void ClientConnection::handleSendPair(const boost::system::error_code& err) { +void ClientConnection::handleSendPair(const ASIO_ERROR& err) { if (isClosed()) { return; } @@ -1148,8 +1150,8 @@ void ClientConnection::sendPendingCommands() { if (any.type() == typeid(SharedBuffer)) { SharedBuffer buffer = boost::any_cast(any); asyncWrite(buffer.const_asio_buffer(), - customAllocWriteHandler([this, self, buffer](const boost::system::error_code& err, - size_t) { handleSend(err, buffer); })); + customAllocWriteHandler( + [this, self, buffer](const ASIO_ERROR& err, size_t) { handleSend(err, buffer); })); } else { assert(any.type() == typeid(std::shared_ptr)); @@ -1160,9 +1162,9 @@ void ClientConnection::sendPendingCommands() { // Capture the buffer because asio does not copy the buffer, if the buffer is destroyed before the // callback is called, an invalid buffer range might be passed to the underlying socket send. - asyncWrite(buffer, - customAllocWriteHandler([this, self, buffer](const boost::system::error_code& err, - size_t) { handleSendPair(err); })); + asyncWrite(buffer, customAllocWriteHandler([this, self, buffer](const ASIO_ERROR& err, size_t) { + handleSendPair(err); + })); } } else { // No more pending writes @@ -1184,7 +1186,7 @@ Future ClientConnection::sendRequestWithId(SharedBuffer cm requestData.timer = executor_->createDeadlineTimer(); requestData.timer->expires_from_now(operationsTimeout_); auto weakSelf = weak_from_this(); - requestData.timer->async_wait([weakSelf, requestData](const boost::system::error_code& ec) { + requestData.timer->async_wait([weakSelf, requestData](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self) { self->handleRequestTimeout(ec, requestData); @@ -1198,21 +1200,19 @@ Future ClientConnection::sendRequestWithId(SharedBuffer cm return requestData.promise.getFuture(); } -void ClientConnection::handleRequestTimeout(const boost::system::error_code& ec, - PendingRequestData pendingRequestData) { +void ClientConnection::handleRequestTimeout(const ASIO_ERROR& ec, PendingRequestData pendingRequestData) { if (!ec && !pendingRequestData.hasGotResponse->load()) { pendingRequestData.promise.setFailed(ResultTimeout); } } -void ClientConnection::handleLookupTimeout(const boost::system::error_code& ec, - LookupRequestData pendingRequestData) { +void ClientConnection::handleLookupTimeout(const ASIO_ERROR& ec, LookupRequestData pendingRequestData) { if (!ec) { pendingRequestData.promise->setFailed(ResultTimeout); } } -void ClientConnection::handleGetLastMessageIdTimeout(const boost::system::error_code& ec, +void ClientConnection::handleGetLastMessageIdTimeout(const ASIO_ERROR& ec, ClientConnection::LastMessageIdRequestData data) { if (!ec) { data.promise->setFailed(ResultTimeout); @@ -1237,9 +1237,9 @@ void ClientConnection::handleKeepAliveTimeout() { // be zero And we do not attempt to dereference the pointer. Lock lock(mutex_); if (keepAliveTimer_) { - keepAliveTimer_->expires_from_now(boost::posix_time::seconds(KeepAliveIntervalInSeconds)); + keepAliveTimer_->expires_from_now(std::chrono::seconds(KeepAliveIntervalInSeconds)); auto weakSelf = weak_from_this(); - keepAliveTimer_->async_wait([weakSelf](const boost::system::error_code&) { + keepAliveTimer_->async_wait([weakSelf](const ASIO_ERROR&) { auto self = weakSelf.lock(); if (self) { self->handleKeepAliveTimeout(); @@ -1250,7 +1250,7 @@ void ClientConnection::handleKeepAliveTimeout() { } } -void ClientConnection::handleConsumerStatsTimeout(const boost::system::error_code& ec, +void ClientConnection::handleConsumerStatsTimeout(const ASIO_ERROR& ec, std::vector consumerStatsRequests) { if (ec) { LOG_DEBUG(cnxString_ << " Ignoring timer cancelled event, code[" << ec << "]"); @@ -1267,15 +1267,15 @@ void ClientConnection::close(Result result, bool detach) { state_ = Disconnected; if (socket_) { - boost::system::error_code err; - socket_->shutdown(boost::asio::socket_base::shutdown_both, err); + ASIO_ERROR err; + socket_->shutdown(ASIO::socket_base::shutdown_both, err); socket_->close(err); if (err) { LOG_WARN(cnxString_ << "Failed to close socket: " << err.message()); } } if (tlsSocket_) { - boost::system::error_code err; + ASIO_ERROR err; tlsSocket_->lowest_layer().close(err); if (err) { LOG_WARN(cnxString_ << "Failed to close TLS socket: " << err.message()); @@ -1294,6 +1294,7 @@ void ClientConnection::close(Result result, bool detach) { auto pendingConsumerStatsMap = std::move(pendingConsumerStatsMap_); auto pendingGetLastMessageIdRequests = std::move(pendingGetLastMessageIdRequests_); auto pendingGetNamespaceTopicsRequests = std::move(pendingGetNamespaceTopicsRequests_); + auto pendingGetSchemaRequests = std::move(pendingGetSchemaRequests_); numOfPendingLookupRequest_ = 0; @@ -1320,7 +1321,7 @@ void ClientConnection::close(Result result, bool detach) { } // Remove the connection from the pool before completing any promise if (detach) { - pool_.remove(logicalAddress_, this); // trigger the destructor + pool_.remove(logicalAddress_, physicalAddress_, poolIndex_, this); } auto self = shared_from_this(); @@ -1358,6 +1359,9 @@ void ClientConnection::close(Result result, bool detach) { for (auto& kv : pendingGetNamespaceTopicsRequests) { kv.second.setFailed(result); } + for (auto& kv : pendingGetSchemaRequests) { + kv.second.promise.setFailed(result); + } } bool ClientConnection::isClosed() const { return state_ == Disconnected; } @@ -1414,7 +1418,7 @@ Future ClientConnection::newGetLastMessageId(u requestData.timer = executor_->createDeadlineTimer(); requestData.timer->expires_from_now(operationsTimeout_); auto weakSelf = weak_from_this(); - requestData.timer->async_wait([weakSelf, requestData](const boost::system::error_code& ec) { + requestData.timer->async_wait([weakSelf, requestData](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self) { self->handleGetLastMessageIdTimeout(ec, requestData); @@ -1446,6 +1450,7 @@ Future ClientConnection::newGetTopicsOfNamespace( Future ClientConnection::newGetSchema(const std::string& topicName, const std::string& version, uint64_t requestId) { Lock lock(mutex_); + Promise promise; if (isClosed()) { lock.unlock(); @@ -1454,25 +1459,33 @@ Future ClientConnection::newGetSchema(const std::string& top return promise.getFuture(); } - pendingGetSchemaRequests_.insert(std::make_pair(requestId, promise)); + auto timer = executor_->createDeadlineTimer(); + pendingGetSchemaRequests_.emplace(requestId, GetSchemaRequest{promise, timer}); lock.unlock(); + + auto weakSelf = weak_from_this(); + timer->expires_from_now(operationsTimeout_); + timer->async_wait([this, weakSelf, requestId](const ASIO_ERROR& ec) { + auto self = weakSelf.lock(); + if (!self) { + return; + } + Lock lock(mutex_); + auto it = pendingGetSchemaRequests_.find(requestId); + if (it != pendingGetSchemaRequests_.end()) { + auto promise = std::move(it->second.promise); + pendingGetSchemaRequests_.erase(it); + lock.unlock(); + promise.setFailed(ResultTimeout); + } + }); + sendCommand(Commands::newGetSchema(topicName, version, requestId)); return promise.getFuture(); } -void ClientConnection::checkServerError(ServerError error) { - switch (error) { - case proto::ServerError::ServiceNotReady: - close(ResultDisconnected); - break; - case proto::ServerError::TooManyRequests: - // TODO: Implement maxNumberOfRejectedRequestPerConnection like - // https://github.com/apache/pulsar/pull/274 - close(ResultDisconnected); - break; - default: - break; - } +void ClientConnection::checkServerError(ServerError error, const std::string& message) { + pulsar::adaptor::checkServerError(*this, error, message); } void ClientConnection::handleSendReceipt(const proto::CommandSendReceipt& sendReceipt) { @@ -1564,7 +1577,7 @@ void ClientConnection::handlePartitionedMetadataResponse( << partitionMetadataResponse.request_id() << " error: " << partitionMetadataResponse.error() << " msg: " << partitionMetadataResponse.message()); - checkServerError(partitionMetadataResponse.error()); + checkServerError(partitionMetadataResponse.error(), partitionMetadataResponse.message()); lookupDataPromise->setFailed( getResult(partitionMetadataResponse.error(), partitionMetadataResponse.message())); } else { @@ -1641,7 +1654,7 @@ void ClientConnection::handleLookupTopicRespose( LOG_ERROR(cnxString_ << "Failed lookup req_id: " << lookupTopicResponse.request_id() << " error: " << lookupTopicResponse.error() << " msg: " << lookupTopicResponse.message()); - checkServerError(lookupTopicResponse.error()); + checkServerError(lookupTopicResponse.error(), lookupTopicResponse.message()); lookupDataPromise->setFailed( getResult(lookupTopicResponse.error(), lookupTopicResponse.message())); } else { @@ -1753,6 +1766,82 @@ void ClientConnection::handleError(const proto::CommandError& error) { } } +std::string ClientConnection::getMigratedBrokerServiceUrl( + const proto::CommandTopicMigrated& commandTopicMigrated) { + if (tlsSocket_) { + if (commandTopicMigrated.has_brokerserviceurltls()) { + return commandTopicMigrated.brokerserviceurltls(); + } + } else if (commandTopicMigrated.has_brokerserviceurl()) { + return commandTopicMigrated.brokerserviceurl(); + } + return ""; +} + +void ClientConnection::handleTopicMigrated(const proto::CommandTopicMigrated& commandTopicMigrated) { + const long resourceId = commandTopicMigrated.resource_id(); + const std::string migratedBrokerServiceUrl = getMigratedBrokerServiceUrl(commandTopicMigrated); + + if (migratedBrokerServiceUrl.empty()) { + LOG_WARN("Failed to find the migrated broker url for resource:" + << resourceId + << (commandTopicMigrated.has_brokerserviceurl() + ? ", migratedBrokerUrl: " + commandTopicMigrated.brokerserviceurl() + : "") + << (commandTopicMigrated.has_brokerserviceurltls() + ? ", migratedBrokerUrlTls: " + commandTopicMigrated.brokerserviceurltls() + : "")); + return; + } + + Lock lock(mutex_); + if (commandTopicMigrated.resource_type() == proto::CommandTopicMigrated_ResourceType_Producer) { + auto it = producers_.find(resourceId); + if (it != producers_.end()) { + auto producer = it->second.lock(); + producer->setRedirectedClusterURI(migratedBrokerServiceUrl); + unsafeRemovePendingRequest(producer->firstRequestIdAfterConnect()); + LOG_INFO("Producer id:" << resourceId << " is migrated to " << migratedBrokerServiceUrl); + } else { + LOG_WARN("Got invalid producer Id in topicMigrated command: " << resourceId); + } + } else { + auto it = consumers_.find(resourceId); + if (it != consumers_.end()) { + auto consumer = it->second.lock(); + consumer->setRedirectedClusterURI(migratedBrokerServiceUrl); + unsafeRemovePendingRequest(consumer->firstRequestIdAfterConnect()); + LOG_INFO("Consumer id:" << resourceId << " is migrated to " << migratedBrokerServiceUrl); + } else { + LOG_WARN("Got invalid consumer Id in topicMigrated command: " << resourceId); + } + } +} + +boost::optional ClientConnection::getAssignedBrokerServiceUrl( + const proto::CommandCloseProducer& closeProducer) { + if (tlsSocket_) { + if (closeProducer.has_assignedbrokerserviceurltls()) { + return closeProducer.assignedbrokerserviceurltls(); + } + } else if (closeProducer.has_assignedbrokerserviceurl()) { + return closeProducer.assignedbrokerserviceurl(); + } + return boost::none; +} + +boost::optional ClientConnection::getAssignedBrokerServiceUrl( + const proto::CommandCloseConsumer& closeConsumer) { + if (tlsSocket_) { + if (closeConsumer.has_assignedbrokerserviceurltls()) { + return closeConsumer.assignedbrokerserviceurltls(); + } + } else if (closeConsumer.has_assignedbrokerserviceurl()) { + return closeConsumer.assignedbrokerserviceurl(); + } + return boost::none; +} + void ClientConnection::handleCloseProducer(const proto::CommandCloseProducer& closeProducer) { int producerId = closeProducer.producer_id(); @@ -1766,7 +1855,8 @@ void ClientConnection::handleCloseProducer(const proto::CommandCloseProducer& cl lock.unlock(); if (producer) { - producer->disconnectProducer(); + auto assignedBrokerServiceUrl = getAssignedBrokerServiceUrl(closeProducer); + producer->disconnectProducer(assignedBrokerServiceUrl); } } else { LOG_ERROR(cnxString_ << "Got invalid producer Id in closeProducer command: " << producerId); @@ -1786,7 +1876,8 @@ void ClientConnection::handleCloseConsumer(const proto::CommandCloseConsumer& cl lock.unlock(); if (consumer) { - consumer->disconnectConsumer(); + auto assignedBrokerServiceUrl = getAssignedBrokerServiceUrl(closeconsumer); + consumer->disconnectConsumer(assignedBrokerServiceUrl); } } else { LOG_ERROR(cnxString_ << "Got invalid consumer Id in closeConsumer command: " << consumerId); @@ -1805,7 +1896,7 @@ void ClientConnection::handleAuthChallenge() { } auto self = shared_from_this(); asyncWrite(buffer.const_asio_buffer(), - customAllocWriteHandler([this, self, buffer](const boost::system::error_code& err, size_t) { + customAllocWriteHandler([this, self, buffer](const ASIO_ERROR& err, size_t) { handleSentAuthResponse(err, buffer); })); } @@ -1883,7 +1974,7 @@ void ClientConnection::handleGetSchemaResponse(const proto::CommandGetSchemaResp Lock lock(mutex_); auto it = pendingGetSchemaRequests_.find(response.request_id()); if (it != pendingGetSchemaRequests_.end()) { - Promise getSchemaPromise = it->second; + Promise getSchemaPromise = it->second.promise; pendingGetSchemaRequests_.erase(it); lock.unlock(); @@ -1938,4 +2029,14 @@ void ClientConnection::handleAckResponse(const proto::CommandAckResponse& respon } } +void ClientConnection::unsafeRemovePendingRequest(long requestId) { + auto it = pendingRequests_.find(requestId); + if (it != pendingRequests_.end()) { + it->second.promise.setFailed(ResultDisconnected); + ASIO_ERROR ec; + it->second.timer->cancel(ec); + pendingRequests_.erase(it); + } +} + } // namespace pulsar diff --git a/lib/ClientConnection.h b/lib/ClientConnection.h index 30ea8d82..418cb2f9 100644 --- a/lib/ClientConnection.h +++ b/lib/ClientConnection.h @@ -23,33 +23,40 @@ #include #include -#include +#ifdef USE_ASIO +#include +#include +#include +#include +#include +#else #include -#include #include #include #include #include +#endif +#include #include #include #include #include #include +#include #include +#include "AsioTimer.h" #include "Commands.h" #include "GetLastMessageIdResponse.h" #include "LookupDataResult.h" #include "SharedBuffer.h" +#include "TimeUtils.h" #include "UtilAllocator.h" - namespace pulsar { class PulsarFriend; -using DeadlineTimerPtr = std::shared_ptr; -using TimeDuration = boost::posix_time::time_duration; -using TcpResolverPtr = std::shared_ptr; +using TcpResolverPtr = std::shared_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; @@ -78,6 +85,7 @@ class BrokerEntryMetadata; class CommandActiveConsumerChange; class CommandAckResponse; class CommandMessage; +class CommandTopicMigrated; class CommandCloseConsumer; class CommandCloseProducer; class CommandConnected; @@ -114,10 +122,10 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this SocketPtr; - typedef std::shared_ptr> TlsSocketPtr; + typedef std::shared_ptr SocketPtr; + typedef std::shared_ptr> TlsSocketPtr; typedef std::shared_ptr ConnectionPtr; - typedef std::function ConnectionListener; + typedef std::function ConnectionListener; typedef std::vector::iterator ListenerIterator; /* @@ -129,7 +137,7 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this promise; + DeadlineTimerPtr timer; + }; + /* * handler for connectAsync * creates a ConnectionPtr which has a valid ClientConnection object * although not usable at this point, since this is just tcp connection * Pulsar - Connect/Connected has yet to happen */ - void handleTcpConnected(const boost::system::error_code& err, - boost::asio::ip::tcp::resolver::iterator endpointIterator); + void handleTcpConnected(const ASIO_ERROR& err, ASIO::ip::tcp::resolver::iterator endpointIterator); - void handleHandshake(const boost::system::error_code& err); + void handleHandshake(const ASIO_ERROR& err); - void handleSentPulsarConnect(const boost::system::error_code& err, const SharedBuffer& buffer); - void handleSentAuthResponse(const boost::system::error_code& err, const SharedBuffer& buffer); + void handleSentPulsarConnect(const ASIO_ERROR& err, const SharedBuffer& buffer); + void handleSentAuthResponse(const ASIO_ERROR& err, const SharedBuffer& buffer); void readNextCommand(); - void handleRead(const boost::system::error_code& err, size_t bytesTransferred, uint32_t minReadSize); + void handleRead(const ASIO_ERROR& err, size_t bytesTransferred, uint32_t minReadSize); void processIncomingBuffer(); bool verifyChecksum(SharedBuffer& incomingBuffer_, uint32_t& remainingBytes, @@ -250,19 +260,18 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this= 106600 - boost::asio::async_write(*tlsSocket_, buffers, boost::asio::bind_executor(strand_, handler)); -#else - boost::asio::async_write(*tlsSocket_, buffers, strand_.wrap(handler)); -#endif + ASIO::async_write(*tlsSocket_, buffers, ASIO::bind_executor(strand_, handler)); } else { - boost::asio::async_write(*socket_, buffers, handler); + ASIO::async_write(*socket_, buffers, handler); } } @@ -298,11 +303,7 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this= 106600 - tlsSocket_->async_read_some(buffers, boost::asio::bind_executor(strand_, handler)); -#else - tlsSocket_->async_read_some(buffers, strand_.wrap(handler)); -#endif + tlsSocket_->async_read_some(buffers, ASIO::bind_executor(strand_, handler)); } else { socket_->async_receive(buffers, handler); } @@ -323,11 +324,7 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this= 106600 - boost::asio::strand strand_; -#else - boost::asio::io_service::strand strand_; -#endif + ASIO::strand strand_; const std::string logicalAddress_; /* @@ -335,13 +332,17 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this server:6650] std::string cnxString_; /* * indicates if async connection establishment failed */ - boost::system::error_code error_; + ASIO_ERROR error_; SharedBuffer incomingBuffer_; @@ -369,7 +370,7 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this> PendingGetNamespaceTopicsMap; PendingGetNamespaceTopicsMap pendingGetNamespaceTopicsRequests_; - typedef std::map> PendingGetSchemaMap; + typedef std::unordered_map PendingGetSchemaMap; PendingGetSchemaMap pendingGetSchemaRequests_; mutable std::mutex mutex_; @@ -386,11 +387,11 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this consumerStatsRequests); + void handleConsumerStatsTimeout(const ASIO_ERROR& ec, std::vector consumerStatsRequests); void startConsumerStatsTimer(std::vector consumerStatsRequests); uint32_t maxPendingLookupRequest_; @@ -400,9 +401,11 @@ class PULSAR_PUBLIC ClientConnection : public std::enable_shared_from_this getAssignedBrokerServiceUrl(const proto::CommandCloseProducer&); + boost::optional getAssignedBrokerServiceUrl(const proto::CommandCloseConsumer&); + std::string getMigratedBrokerServiceUrl(const proto::CommandTopicMigrated&); + // This method must be called when `mutex_` is held + void unsafeRemovePendingRequest(long requestId); }; } // namespace pulsar diff --git a/lib/ClientConnectionAdaptor.h b/lib/ClientConnectionAdaptor.h new file mode 100644 index 00000000..2c299373 --- /dev/null +++ b/lib/ClientConnectionAdaptor.h @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// This file is used to mock ClientConnection's methods +#pragma once + +#include + +#include "ProtoApiEnums.h" +#include "PulsarApi.pb.h" + +namespace pulsar { + +namespace adaptor { + +template +inline void checkServerError(Connection& connection, ServerError error, const std::string& message) { + switch (error) { + case proto::ServerError::ServiceNotReady: + // There are some typical error messages that should not trigger the + // close() of the connection. + // "Namespace bundle ... is being unloaded" + // "KeeperException$..." + // "Failed to acquire ownership for namespace bundle ..." + // "the broker do not have test listener" + // Before https://github.com/apache/pulsar/pull/21211, the error of the 1st and 2nd messages + // is ServiceNotReady. Before https://github.com/apache/pulsar/pull/21993, the error of the 3rd + // message is ServiceNotReady. + if (message.find("Failed to acquire ownership") == std::string::npos && + message.find("KeeperException") == std::string::npos && + message.find("is being unloaded") == std::string::npos && + message.find("the broker do not have test listener") == std::string::npos) { + connection.close(ResultDisconnected); + } + break; + case proto::ServerError::TooManyRequests: + // TODO: Implement maxNumberOfRejectedRequestPerConnection like + // https://github.com/apache/pulsar/pull/274 + connection.close(ResultDisconnected); + break; + default: + break; + } +} + +} // namespace adaptor + +} // namespace pulsar diff --git a/lib/ClientImpl.cc b/lib/ClientImpl.cc index cb11b8e0..3d19c426 100644 --- a/lib/ClientImpl.cc +++ b/lib/ClientImpl.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -78,8 +79,8 @@ typedef std::vector StringList; ClientImpl::ClientImpl(const std::string& serviceUrl, const ClientConfiguration& clientConfiguration) : mutex_(), state_(Open), - serviceNameResolver_(serviceUrl), - clientConfiguration_(ClientConfiguration(clientConfiguration).setUseTls(serviceNameResolver_.useTls())), + clientConfiguration_(ClientConfiguration(clientConfiguration) + .setUseTls(ServiceNameResolver::useTls(ServiceURI(serviceUrl)))), memoryLimitController_(clientConfiguration.getMemoryLimit()), ioExecutorProvider_(std::make_shared(clientConfiguration_.getIOThreads())), listenerExecutorProvider_( @@ -90,32 +91,35 @@ ClientImpl::ClientImpl(const std::string& serviceUrl, const ClientConfiguration& ClientImpl::getClientVersion(clientConfiguration)), producerIdGenerator_(0), consumerIdGenerator_(0), - closingError(ResultOk) { + closingError(ResultOk), + useProxy_(false), + lookupCount_(0L) { std::unique_ptr loggerFactory = clientConfiguration_.impl_->takeLogger(); - if (!loggerFactory) { - // Use default simple console logger - loggerFactory.reset(new ConsoleLoggerFactory); + if (loggerFactory) { + LogUtils::setLoggerFactory(std::move(loggerFactory)); } - LogUtils::setLoggerFactory(std::move(loggerFactory)); + lookupServicePtr_ = createLookup(serviceUrl); +} + +ClientImpl::~ClientImpl() { shutdown(); } +LookupServicePtr ClientImpl::createLookup(const std::string& serviceUrl) { LookupServicePtr underlyingLookupServicePtr; - if (serviceNameResolver_.useHttp()) { + if (ServiceNameResolver::useHttp(ServiceURI(serviceUrl))) { LOG_DEBUG("Using HTTP Lookup"); underlyingLookupServicePtr = std::make_shared( - std::ref(serviceNameResolver_), std::cref(clientConfiguration_), - std::cref(clientConfiguration_.getAuthPtr())); + serviceUrl, std::cref(clientConfiguration_), std::cref(clientConfiguration_.getAuthPtr())); } else { LOG_DEBUG("Using Binary Lookup"); underlyingLookupServicePtr = std::make_shared( - std::ref(serviceNameResolver_), std::ref(pool_), std::cref(clientConfiguration_)); + serviceUrl, std::ref(pool_), std::cref(clientConfiguration_)); } - lookupServicePtr_ = RetryableLookupService::create( - underlyingLookupServicePtr, clientConfiguration_.getOperationTimeoutSeconds(), ioExecutorProvider_); + auto lookupServicePtr = RetryableLookupService::create( + underlyingLookupServicePtr, clientConfiguration_.impl_->operationTimeout, ioExecutorProvider_); + return lookupServicePtr; } -ClientImpl::~ClientImpl() { shutdown(); } - const ClientConfiguration& ClientImpl::conf() const { return clientConfiguration_; } MemoryLimitController& ClientImpl::getMemoryLimitController() { return memoryLimitController_; } @@ -128,7 +132,21 @@ ExecutorServiceProviderPtr ClientImpl::getPartitionListenerExecutorProvider() { return partitionListenerExecutorProvider_; } -LookupServicePtr ClientImpl::getLookup() { return lookupServicePtr_; } +LookupServicePtr ClientImpl::getLookup(const std::string& redirectedClusterURI) { + if (redirectedClusterURI.empty()) { + return lookupServicePtr_; + } + + Lock lock(mutex_); + auto it = redirectedClusterLookupServicePtrs_.find(redirectedClusterURI); + if (it == redirectedClusterLookupServicePtrs_.end()) { + auto lookup = createLookup(redirectedClusterURI); + redirectedClusterLookupServicePtrs_.emplace(redirectedClusterURI, lookup); + return lookup; + } + + return it->second; +} void ClientImpl::createProducerAsync(const std::string& topic, ProducerConfiguration conf, CreateProducerCallback callback, bool autoDownloadSchema) { @@ -516,7 +534,8 @@ void ClientImpl::handleConsumerCreated(Result result, ConsumerImplBaseWeakPtr co } } -Future ClientImpl::getConnection(const std::string& topic) { +GetConnectionFuture ClientImpl::getConnection(const std::string& redirectedClusterURI, + const std::string& topic, size_t key) { Promise promise; const auto topicNamePtr = TopicName::get(topic); @@ -527,13 +546,16 @@ Future ClientImpl::getConnection(const std::string& } auto self = shared_from_this(); - lookupServicePtr_->getBroker(*topicNamePtr) - .addListener([this, self, promise](Result result, const LookupService::LookupResult& data) { + getLookup(redirectedClusterURI) + ->getBroker(*topicNamePtr) + .addListener([this, self, promise, key](Result result, const LookupService::LookupResult& data) { if (result != ResultOk) { promise.setFailed(result); return; } - pool_.getConnectionAsync(data.logicalAddress, data.physicalAddress) + useProxy_ = data.proxyThroughServiceUrl; + lookupCount_++; + pool_.getConnectionAsync(data.logicalAddress, data.physicalAddress, key) .addListener([promise](Result result, const ClientConnectionWeakPtr& weakCnx) { if (result == ResultOk) { auto cnx = weakCnx.lock(); @@ -551,6 +573,35 @@ Future ClientImpl::getConnection(const std::string& return promise.getFuture(); } +const std::string& ClientImpl::getPhysicalAddress(const std::string& redirectedClusterURI, + const std::string& logicalAddress) { + if (useProxy_) { + return getLookup(redirectedClusterURI)->getServiceNameResolver().resolveHost(); + } else { + return logicalAddress; + } +} + +GetConnectionFuture ClientImpl::connect(const std::string& redirectedClusterURI, + const std::string& logicalAddress, size_t key) { + const auto& physicalAddress = getPhysicalAddress(redirectedClusterURI, logicalAddress); + Promise promise; + pool_.getConnectionAsync(logicalAddress, physicalAddress, key) + .addListener([promise](Result result, const ClientConnectionWeakPtr& weakCnx) { + if (result == ResultOk) { + auto cnx = weakCnx.lock(); + if (cnx) { + promise.setValue(cnx); + } else { + promise.setFailed(ResultConnectError); + } + } else { + promise.setFailed(result); + } + }); + return promise.getFuture(); +} + void ClientImpl::handleGetPartitions(const Result result, const LookupDataResultPtr partitionMetadata, TopicNamePtr topicName, GetPartitionsCallback callback) { if (result != ResultOk) { @@ -603,6 +654,9 @@ void ClientImpl::closeAsync(CloseCallback callback) { memoryLimitController_.close(); lookupServicePtr_->close(); + for (const auto& it : redirectedClusterLookupServicePtrs_) { + it.second->close(); + } auto producers = producers_.move(); auto consumers = consumers_.move(); @@ -636,6 +690,7 @@ void ClientImpl::closeAsync(CloseCallback callback) { if (*numberOfOpenHandlers == 0 && callback) { handleClose(ResultOk, numberOfOpenHandlers, callback); } + lookupCount_ = 0; } void ClientImpl::handleClose(Result result, SharedInt numberOfOpenHandlers, ResultCallback callback) { @@ -723,6 +778,7 @@ void ClientImpl::shutdown() { partitionListenerExecutorProvider_->close(timeoutProcessor.getLeftTimeout()); timeoutProcessor.tok(); LOG_DEBUG("partitionListenerExecutorProvider_ is closed"); + lookupCount_ = 0; } uint64_t ClientImpl::newProducerId() { @@ -770,4 +826,8 @@ std::string ClientImpl::getClientVersion(const ClientConfiguration& clientConfig return oss.str(); } +std::chrono::nanoseconds ClientImpl::getOperationTimeout(const ClientConfiguration& clientConfiguration) { + return clientConfiguration.impl_->operationTimeout; +} + } /* namespace pulsar */ diff --git a/lib/ClientImpl.h b/lib/ClientImpl.h index 0c6eeb40..27cde3a1 100644 --- a/lib/ClientImpl.h +++ b/lib/ClientImpl.h @@ -63,13 +63,14 @@ class TopicName; using TopicNamePtr = std::shared_ptr; using NamespaceTopicsPtr = std::shared_ptr>; +using GetConnectionFuture = Future; std::string generateRandomName(); class ClientImpl : public std::enable_shared_from_this { public: ClientImpl(const std::string& serviceUrl, const ClientConfiguration& clientConfiguration); - ~ClientImpl(); + virtual ~ClientImpl(); /** * @param autoDownloadSchema When it is true, Before creating a producer, it will try to get the schema @@ -95,7 +96,12 @@ class ClientImpl : public std::enable_shared_from_this { void getPartitionsForTopicAsync(const std::string& topic, GetPartitionsCallback callback); - Future getConnection(const std::string& topic); + // Use virtual method to test + virtual GetConnectionFuture getConnection(const std::string& redirectedClusterURI, + const std::string& topic, size_t key); + + GetConnectionFuture connect(const std::string& redirectedClusterURI, const std::string& logicalAddress, + size_t key); void closeAsync(CloseCallback callback); void shutdown(); @@ -115,7 +121,7 @@ class ClientImpl : public std::enable_shared_from_this { ExecutorServiceProviderPtr getIOExecutorProvider(); ExecutorServiceProviderPtr getListenerExecutorProvider(); ExecutorServiceProviderPtr getPartitionListenerExecutorProvider(); - LookupServicePtr getLookup(); + LookupServicePtr getLookup(const std::string& redirectedClusterURI = ""); void cleanupProducer(ProducerImplBase* address) { producers_.remove(address); } @@ -123,6 +129,11 @@ class ClientImpl : public std::enable_shared_from_this { std::shared_ptr> getRequestIdGenerator() const { return requestIdGenerator_; } + ConnectionPool& getConnectionPool() noexcept { return pool_; } + uint64_t getLookupCount() { return lookupCount_; } + + static std::chrono::nanoseconds getOperationTimeout(const ClientConfiguration& clientConfiguration); + friend class PulsarFriend; private: @@ -156,6 +167,11 @@ class ClientImpl : public std::enable_shared_from_this { const std::string& consumerName, const ConsumerConfiguration& conf, SubscribeCallback callback); + const std::string& getPhysicalAddress(const std::string& redirectedClusterURI, + const std::string& logicalAddress); + + LookupServicePtr createLookup(const std::string& serviceUrl); + static std::string getClientVersion(const ClientConfiguration& clientConfiguration); enum State @@ -168,7 +184,6 @@ class ClientImpl : public std::enable_shared_from_this { std::mutex mutex_; State state_; - ServiceNameResolver serviceNameResolver_; ClientConfiguration clientConfiguration_; MemoryLimitController memoryLimitController_; @@ -177,6 +192,7 @@ class ClientImpl : public std::enable_shared_from_this { ExecutorServiceProviderPtr partitionListenerExecutorProvider_; LookupServicePtr lookupServicePtr_; + std::unordered_map redirectedClusterLookupServicePtrs_; ConnectionPool pool_; uint64_t producerIdGenerator_; @@ -187,6 +203,8 @@ class ClientImpl : public std::enable_shared_from_this { SynchronizedHashMap consumers_; std::atomic closingError; + std::atomic useProxy_; + std::atomic lookupCount_; friend class Client; }; diff --git a/lib/CompressionCodec.cc b/lib/CompressionCodec.cc index 991d52c0..6105887a 100644 --- a/lib/CompressionCodec.cc +++ b/lib/CompressionCodec.cc @@ -45,7 +45,6 @@ CompressionCodec& CompressionCodecProvider::getCodec(CompressionType compression default: return compressionCodecNone_; } - BOOST_THROW_EXCEPTION(std::logic_error("Invalid CompressionType enumeration value")); } SharedBuffer CompressionCodecNone::encode(const SharedBuffer& raw) { return raw; } diff --git a/lib/ConnectionPool.cc b/lib/ConnectionPool.cc index 32325122..9a614ed1 100644 --- a/lib/ConnectionPool.cc +++ b/lib/ConnectionPool.cc @@ -18,15 +18,20 @@ */ #include "ConnectionPool.h" +#ifdef USE_ASIO +#include +#include +#else #include #include +#endif #include "ClientConnection.h" #include "ExecutorService.h" #include "LogUtils.h" -using boost::asio::ip::tcp; -namespace ssl = boost::asio::ssl; +using ASIO::ip::tcp; +namespace ssl = ASIO::ssl; typedef ssl::stream ssl_socket; DECLARE_LOG_OBJECT() @@ -61,8 +66,16 @@ bool ConnectionPool::close() { return true; } -Future ConnectionPool::getConnectionAsync( - const std::string& logicalAddress, const std::string& physicalAddress) { +static const std::string getKey(const std::string& logicalAddress, const std::string& physicalAddress, + size_t keySuffix) { + std::stringstream ss; + ss << logicalAddress << '-' << physicalAddress << '-' << keySuffix; + return ss.str(); +} + +Future ConnectionPool::getConnectionAsync(const std::string& logicalAddress, + const std::string& physicalAddress, + size_t keySuffix) { if (closed_) { Promise promise; promise.setFailed(ResultAlreadyClosed); @@ -71,9 +84,7 @@ Future ConnectionPool::getConnectionAsync( std::unique_lock lock(mutex_); - std::stringstream ss; - ss << logicalAddress << '-' << randomDistribution_(randomEngine_); - const std::string key = ss.str(); + auto key = getKey(logicalAddress, physicalAddress, keySuffix); PoolMap::iterator cnxIt = pool_.find(key); if (cnxIt != pool_.end()) { @@ -95,8 +106,13 @@ Future ConnectionPool::getConnectionAsync( // No valid or pending connection found in the pool, creating a new one ClientConnectionPtr cnx; try { - cnx.reset(new ClientConnection(logicalAddress, physicalAddress, executorProvider_->get(), - clientConfiguration_, authentication_, clientVersion_, *this)); + cnx.reset(new ClientConnection(logicalAddress, physicalAddress, executorProvider_->get(keySuffix), + clientConfiguration_, authentication_, clientVersion_, *this, + keySuffix)); + } catch (Result result) { + Promise promise; + promise.setFailed(result); + return promise.getFuture(); } catch (const std::runtime_error& e) { lock.unlock(); LOG_ERROR("Failed to create connection: " << e.what()) @@ -105,7 +121,7 @@ Future ConnectionPool::getConnectionAsync( return promise.getFuture(); } - LOG_INFO("Created connection for " << logicalAddress); + LOG_INFO("Created connection for " << key); Future future = cnx->getConnectFuture(); pool_.insert(std::make_pair(key, cnx)); @@ -116,10 +132,12 @@ Future ConnectionPool::getConnectionAsync( return future; } -void ConnectionPool::remove(const std::string& key, ClientConnection* value) { +void ConnectionPool::remove(const std::string& logicalAddress, const std::string& physicalAddress, + size_t keySuffix, ClientConnection* value) { + auto key = getKey(logicalAddress, physicalAddress, keySuffix); std::lock_guard lock(mutex_); auto it = pool_.find(key); - if (it->second.get() == value) { + if (it != pool_.end() && it->second.get() == value) { LOG_INFO("Remove connection for " << key); pool_.erase(it); } diff --git a/lib/ConnectionPool.h b/lib/ConnectionPool.h index c582dc9e..e044d62c 100644 --- a/lib/ConnectionPool.h +++ b/lib/ConnectionPool.h @@ -51,7 +51,8 @@ class PULSAR_PUBLIC ConnectionPool { */ bool close(); - void remove(const std::string& key, ClientConnection* value); + void remove(const std::string& logicalAddress, const std::string& physicalAddress, size_t keySuffix, + ClientConnection* value); /** * Get a connection from the pool. @@ -65,17 +66,29 @@ class PULSAR_PUBLIC ConnectionPool { * a proxy layer. Essentially, the pool is using the logical address as a way to * decide whether to reuse a particular connection. * + * There could be many connections to the same broker, so this pool uses an integer key as the suffix of + * the key that represents the connection. + * * @param logicalAddress the address to use as the broker tag * @param physicalAddress the real address where the TCP connection should be made + * @param keySuffix the key suffix to choose which connection on the same broker * @return a future that will produce the ClientCnx object */ Future getConnectionAsync(const std::string& logicalAddress, - const std::string& physicalAddress); + const std::string& physicalAddress, + size_t keySuffix); + + Future getConnectionAsync(const std::string& logicalAddress, + const std::string& physicalAddress) { + return getConnectionAsync(logicalAddress, physicalAddress, generateRandomIndex()); + } Future getConnectionAsync(const std::string& address) { return getConnectionAsync(address, address); } + size_t generateRandomIndex() { return randomDistribution_(randomEngine_); } + private: ClientConfiguration clientConfiguration_; ExecutorServiceProviderPtr executorProvider_; diff --git a/lib/Consumer.cc b/lib/Consumer.cc index bda7f862..031cdff9 100644 --- a/lib/Consumer.cc +++ b/lib/Consumer.cc @@ -39,6 +39,10 @@ const std::string& Consumer::getSubscriptionName() const { return impl_ != NULL ? impl_->getSubscriptionName() : EMPTY_STRING; } +const std::string& Consumer::getConsumerName() const { + return impl_ ? impl_->getConsumerName() : EMPTY_STRING; +} + Result Consumer::unsubscribe() { if (!impl_) { return ResultConsumerNotInitialized; diff --git a/lib/ConsumerImpl.cc b/lib/ConsumerImpl.cc index 8dd334ea..1a0b0cb9 100644 --- a/lib/ConsumerImpl.cc +++ b/lib/ConsumerImpl.cc @@ -26,6 +26,7 @@ #include "AckGroupingTracker.h" #include "AckGroupingTrackerDisabled.h" #include "AckGroupingTrackerEnabled.h" +#include "AsioDefines.h" #include "BatchMessageAcker.h" #include "BatchedMessageIdImpl.h" #include "BitSet.h" @@ -55,6 +56,9 @@ namespace pulsar { DECLARE_LOG_OBJECT() +using std::chrono::milliseconds; +using std::chrono::seconds; + ConsumerImpl::ConsumerImpl(const ClientImplPtr client, const std::string& topic, const std::string& subscriptionName, const ConsumerConfiguration& conf, bool isPersistent, const ConsumerInterceptorsPtr& interceptors, @@ -83,10 +87,9 @@ ConsumerImpl::ConsumerImpl(const ClientImplPtr client, const std::string& topic, availablePermits_(0), receiverQueueRefillThreshold_(config_.getReceiverQueueSize() / 2), consumerId_(client->newConsumerId()), - consumerName_(config_.getConsumerName()), consumerStr_("[" + topic + ", " + subscriptionName + ", " + std::to_string(consumerId_) + "] "), messageListenerRunning_(true), - negativeAcksTracker_(client, *this, conf), + negativeAcksTracker_(std::make_shared(client, *this, conf)), readCompacted_(conf.isReadCompacted()), startMessageId_(startMessageId), maxPendingChunkedMessage_(conf.getMaxPendingChunkedMessage()), @@ -105,6 +108,7 @@ ConsumerImpl::ConsumerImpl(const ClientImplPtr client, const std::string& topic, } else { unAckedMessageTrackerPtr_.reset(new UnAckedMessageTrackerDisabled()); } + unAckedMessageTrackerPtr_->start(); // Setup stats reporter. unsigned int statsIntervalInSeconds = client->getClientConfig().getStatsIntervalInSeconds(); @@ -232,30 +236,29 @@ Future ConsumerImpl::connectionOpened(const ClientConnectionPtr& c // sending the subscribe request. cnx->registerConsumer(consumerId_, get_shared_this_ptr()); - if (duringSeek_) { + if (duringSeek()) { ackGroupingTrackerPtr_->flushAndClean(); } Lock lockForMessageId(mutexForMessageId_); - // Update startMessageId so that we can discard messages after delivery restarts - const auto startMessageId = clearReceiveQueue(); + clearReceiveQueue(); const auto subscribeMessageId = - (subscriptionMode_ == Commands::SubscriptionModeNonDurable) ? startMessageId : boost::none; - startMessageId_ = startMessageId; + (subscriptionMode_ == Commands::SubscriptionModeNonDurable) ? startMessageId_.get() : boost::none; lockForMessageId.unlock(); unAckedMessageTrackerPtr_->clear(); ClientImplPtr client = client_.lock(); - uint64_t requestId = client->newRequestId(); + long requestId = client->newRequestId(); SharedBuffer cmd = Commands::newSubscribe( - topic(), subscription_, consumerId_, requestId, getSubType(), consumerName_, subscriptionMode_, + topic(), subscription_, consumerId_, requestId, getSubType(), getConsumerName(), subscriptionMode_, subscribeMessageId, readCompacted_, config_.getProperties(), config_.getSubscriptionProperties(), config_.getSchema(), getInitialPosition(), config_.isReplicateSubscriptionStateEnabled(), config_.getKeySharedPolicy(), config_.getPriorityLevel()); // Keep a reference to ensure object is kept alive. auto self = get_shared_this_ptr(); + setFirstRequestIdAfterConnect(requestId); cnx->sendRequestWithId(cmd, requestId) .addListener([this, self, cnx, promise](Result result, const ResponseData& responseData) { Result handleResult = handleCreateConsumer(cnx, result); @@ -273,7 +276,7 @@ void ConsumerImpl::connectionFailed(Result result) { // Keep a reference to ensure object is kept alive auto ptr = get_shared_this_ptr(); - if (consumerCreatedPromise_.setFailed(result)) { + if (!isResultRetryable(result) && consumerCreatedPromise_.setFailed(result)) { state_ = Failed; } } @@ -402,10 +405,9 @@ void ConsumerImpl::discardChunkMessages(std::string uuid, MessageId messageId, b } void ConsumerImpl::triggerCheckExpiredChunkedTimer() { - checkExpiredChunkedTimer_->expires_from_now( - boost::posix_time::milliseconds(expireTimeOfIncompleteChunkedMessageMs_)); + checkExpiredChunkedTimer_->expires_from_now(milliseconds(expireTimeOfIncompleteChunkedMessageMs_)); std::weak_ptr weakSelf{shared_from_this()}; - checkExpiredChunkedTimer_->async_wait([this, weakSelf](const boost::system::error_code& ec) -> void { + checkExpiredChunkedTimer_->async_wait([this, weakSelf](const ASIO_ERROR& ec) -> void { auto self = weakSelf.lock(); if (!self) { return; @@ -1044,14 +1046,23 @@ void ConsumerImpl::messageProcessed(Message& msg, bool track) { * Clear the internal receiver queue and returns the message id of what was the 1st message in the queue that * was * not seen by the application + * `startMessageId_` is updated so that we can discard messages after delivery restarts. */ -boost::optional ConsumerImpl::clearReceiveQueue() { - bool expectedDuringSeek = true; - if (duringSeek_.compare_exchange_strong(expectedDuringSeek, false)) { - return seekMessageId_.get(); +void ConsumerImpl::clearReceiveQueue() { + if (duringSeek()) { + if (!hasSoughtByTimestamp_.load(std::memory_order_acquire)) { + startMessageId_ = seekMessageId_.get(); + } + SeekStatus expected = SeekStatus::COMPLETED; + if (seekStatus_.compare_exchange_strong(expected, SeekStatus::NOT_STARTED)) { + auto seekCallback = seekCallback_.release(); + executor_->postWork([seekCallback] { seekCallback(ResultOk); }); + } + return; } else if (subscriptionMode_ == Commands::SubscriptionModeDurable) { - return startMessageId_.get(); + return; } + Message nextMessageInQueue; if (incomingMessages_.peekAndClear(nextMessageInQueue)) { // There was at least one message pending in the queue @@ -1067,16 +1078,12 @@ boost::optional ConsumerImpl::clearReceiveQueue() { .ledgerId(nextMessageId.ledgerId()) .entryId(nextMessageId.entryId() - 1) .build(); - return previousMessageId; + startMessageId_ = previousMessageId; } else if (lastDequedMessageId_ != MessageId::earliest()) { // If the queue was empty we need to restart from the message just after the last one that has been // dequeued // in the past - return lastDequedMessageId_; - } else { - // No message was received or dequeued by this consumer. Next message would still be the - // startMessageId - return startMessageId_.get(); + startMessageId_ = lastDequedMessageId_; } } @@ -1228,20 +1235,25 @@ std::pair ConsumerImpl::prepareCumulativeAck(const MessageId& m void ConsumerImpl::negativeAcknowledge(const MessageId& messageId) { unAckedMessageTrackerPtr_->remove(messageId); - negativeAcksTracker_.add(messageId); + negativeAcksTracker_->add(messageId); } -void ConsumerImpl::disconnectConsumer() { - LOG_INFO("Broker notification of Closed consumer: " << consumerId_); +void ConsumerImpl::disconnectConsumer() { disconnectConsumer(boost::none); } + +void ConsumerImpl::disconnectConsumer(const boost::optional& assignedBrokerUrl) { + LOG_INFO("Broker notification of Closed consumer: " + << consumerId_ << (assignedBrokerUrl ? (" assignedBrokerUrl: " + assignedBrokerUrl.get()) : "")); resetCnx(); - scheduleReconnection(); + scheduleReconnection(assignedBrokerUrl); } void ConsumerImpl::closeAsync(ResultCallback originalCallback) { - auto callback = [this, originalCallback](Result result) { + auto callback = [this, originalCallback](Result result, bool alreadyClosed = false) { shutdown(); if (result == ResultOk) { - LOG_INFO(getName() << "Closed consumer " << consumerId_); + if (!alreadyClosed) { + LOG_INFO(getName() << "Closed consumer " << consumerId_); + } } else { LOG_WARN(getName() << "Failed to close consumer: " << result); } @@ -1250,8 +1262,9 @@ void ConsumerImpl::closeAsync(ResultCallback originalCallback) { } }; - if (state_ != Ready) { - callback(ResultAlreadyClosed); + auto state = state_.load(); + if (state == Closing || state == Closed) { + callback(ResultOk, true); return; } @@ -1263,7 +1276,7 @@ void ConsumerImpl::closeAsync(ResultCallback originalCallback) { if (ackGroupingTrackerPtr_) { ackGroupingTrackerPtr_->close(); } - negativeAcksTracker_.close(); + negativeAcksTracker_->close(); ClientConnectionPtr cnx = getCnx().lock(); if (!cnx) { @@ -1301,7 +1314,7 @@ void ConsumerImpl::shutdown() { if (client) { client->cleanupConsumer(this); } - negativeAcksTracker_.close(); + negativeAcksTracker_->close(); cancelTimers(); consumerCreatedPromise_.setFailed(ResultAlreadyClosed); failPendingReceiveCallback(); @@ -1465,7 +1478,7 @@ void ConsumerImpl::seekAsync(const MessageId& msgId, ResultCallback callback) { return; } const auto requestId = client->newRequestId(); - seekAsyncInternal(requestId, Commands::newSeek(consumerId_, requestId, msgId), msgId, 0L, callback); + seekAsyncInternal(requestId, Commands::newSeek(consumerId_, requestId, msgId), SeekArg{msgId}, callback); } void ConsumerImpl::seekAsync(uint64_t timestamp, ResultCallback callback) { @@ -1484,49 +1497,62 @@ void ConsumerImpl::seekAsync(uint64_t timestamp, ResultCallback callback) { return; } const auto requestId = client->newRequestId(); - seekAsyncInternal(requestId, Commands::newSeek(consumerId_, requestId, timestamp), MessageId::earliest(), - timestamp, callback); + seekAsyncInternal(requestId, Commands::newSeek(consumerId_, requestId, timestamp), SeekArg{timestamp}, + callback); } bool ConsumerImpl::isReadCompacted() { return readCompacted_; } -inline bool hasMoreMessages(const MessageId& lastMessageIdInBroker, const MessageId& messageId) { - return lastMessageIdInBroker > messageId && lastMessageIdInBroker.entryId() != -1; -} - void ConsumerImpl::hasMessageAvailableAsync(HasMessageAvailableCallback callback) { - const auto startMessageId = startMessageId_.get(); - Lock lock(mutexForMessageId_); - const auto messageId = - (lastDequedMessageId_ == MessageId::earliest()) ? startMessageId.value() : lastDequedMessageId_; - - if (messageId == MessageId::latest()) { - lock.unlock(); - getLastMessageIdAsync([callback](Result result, const GetLastMessageIdResponse& response) { + bool compareMarkDeletePosition; + { + std::lock_guard lock{mutexForMessageId_}; + compareMarkDeletePosition = + (lastDequedMessageId_ == MessageId::earliest()) && + (startMessageId_.get().value_or(MessageId::earliest()) == MessageId::latest()); + } + if (compareMarkDeletePosition || hasSoughtByTimestamp_.load(std::memory_order_acquire)) { + auto self = get_shared_this_ptr(); + getLastMessageIdAsync([self, callback](Result result, const GetLastMessageIdResponse& response) { if (result != ResultOk) { callback(result, {}); return; } - if (response.hasMarkDeletePosition() && response.getLastMessageId().entryId() >= 0) { - // We only care about comparing ledger ids and entry ids as mark delete position doesn't have - // other ids such as batch index - callback(ResultOk, compareLedgerAndEntryId(response.getMarkDeletePosition(), - response.getLastMessageId()) < 0); + auto handleResponse = [self, response, callback] { + if (response.hasMarkDeletePosition() && response.getLastMessageId().entryId() >= 0) { + // We only care about comparing ledger ids and entry ids as mark delete position + // doesn't have other ids such as batch index + auto compareResult = compareLedgerAndEntryId(response.getMarkDeletePosition(), + response.getLastMessageId()); + callback(ResultOk, self->config_.isStartMessageIdInclusive() ? compareResult <= 0 + : compareResult < 0); + } else { + callback(ResultOk, false); + } + }; + if (self->config_.isStartMessageIdInclusive() && + !self->hasSoughtByTimestamp_.load(std::memory_order_acquire)) { + self->seekAsync(response.getLastMessageId(), [callback, handleResponse](Result result) { + if (result != ResultOk) { + callback(result, {}); + return; + } + handleResponse(); + }); } else { - callback(ResultOk, false); + handleResponse(); } }); } else { - if (hasMoreMessages(lastMessageIdInBroker_, messageId)) { - lock.unlock(); + if (hasMoreMessages()) { callback(ResultOk, true); return; } - lock.unlock(); - - getLastMessageIdAsync([callback, messageId](Result result, const GetLastMessageIdResponse& response) { - callback(result, (result == ResultOk) && hasMoreMessages(response.getLastMessageId(), messageId)); - }); + auto self = get_shared_this_ptr(); + getLastMessageIdAsync( + [this, self, callback](Result result, const GetLastMessageIdResponse& response) { + callback(result, (result == ResultOk) && hasMoreMessages()); + }); } } @@ -1578,7 +1604,7 @@ void ConsumerImpl::internalGetLastMessageIdAsync(const BackoffPtr& backoff, Time } } else { TimeDuration next = std::min(remainTime, backoff->next()); - if (next.total_milliseconds() <= 0) { + if (toMillis(next) <= 0) { LOG_ERROR(getName() << " Client Connection not ready for Consumer"); callback(ResultNotConnected, MessageId()); return; @@ -1589,8 +1615,8 @@ void ConsumerImpl::internalGetLastMessageIdAsync(const BackoffPtr& backoff, Time auto self = shared_from_this(); timer->async_wait([this, backoff, remainTime, timer, next, callback, - self](const boost::system::error_code& ec) -> void { - if (ec == boost::asio::error::operation_aborted) { + self](const ASIO_ERROR& ec) -> void { + if (ec == ASIO::error::operation_aborted) { LOG_DEBUG(getName() << " Get last message id operation was cancelled, code[" << ec << "]."); return; } @@ -1599,14 +1625,14 @@ void ConsumerImpl::internalGetLastMessageIdAsync(const BackoffPtr& backoff, Time return; } LOG_WARN(getName() << " Could not get connection while getLastMessageId -- Will try again in " - << next.total_milliseconds() << " ms") + << toMillis(next) << " ms") this->internalGetLastMessageIdAsync(backoff, remainTime, timer, callback); }); } } void ConsumerImpl::setNegativeAcknowledgeEnabledForTesting(bool enabled) { - negativeAcksTracker_.setEnabledForTesting(enabled); + negativeAcksTracker_->setEnabledForTesting(enabled); } void ConsumerImpl::trackMessage(const MessageId& messageId) { @@ -1621,8 +1647,8 @@ bool ConsumerImpl::isConnected() const { return !getCnx().expired() && state_ == uint64_t ConsumerImpl::getNumberOfConnectedConsumer() { return isConnected() ? 1 : 0; } -void ConsumerImpl::seekAsyncInternal(long requestId, SharedBuffer seek, const MessageId& seekId, - long timestamp, ResultCallback callback) { +void ConsumerImpl::seekAsyncInternal(long requestId, SharedBuffer seek, const SeekArg& seekArg, + ResultCallback callback) { ClientConnectionPtr cnx = getCnx().lock(); if (!cnx) { LOG_ERROR(getName() << " Client Connection not ready for Consumer"); @@ -1630,14 +1656,23 @@ void ConsumerImpl::seekAsyncInternal(long requestId, SharedBuffer seek, const Me return; } + auto expected = SeekStatus::NOT_STARTED; + if (!seekStatus_.compare_exchange_strong(expected, SeekStatus::IN_PROGRESS)) { + LOG_ERROR(getName() << " attempted to seek " << seekArg << " when the status is " + << static_cast(expected)); + callback(ResultNotAllowedError); + return; + } + const auto originalSeekMessageId = seekMessageId_.get(); - seekMessageId_ = seekId; - duringSeek_ = true; - if (timestamp > 0) { - LOG_INFO(getName() << " Seeking subscription to " << timestamp); + if (boost::get(&seekArg)) { + hasSoughtByTimestamp_.store(true, std::memory_order_release); } else { - LOG_INFO(getName() << " Seeking subscription to " << seekId); + seekMessageId_ = *boost::get(&seekArg); } + seekStatus_ = SeekStatus::IN_PROGRESS; + seekCallback_ = std::move(callback); + LOG_INFO(getName() << " Seeking subscription to " << seekArg); std::weak_ptr weakSelf{get_shared_this_ptr()}; @@ -1656,12 +1691,21 @@ void ConsumerImpl::seekAsyncInternal(long requestId, SharedBuffer seek, const Me Lock lock(mutexForMessageId_); lastDequedMessageId_ = MessageId::earliest(); lock.unlock(); + if (getCnx().expired()) { + // It's during reconnection, complete the seek future after connection is established + seekStatus_ = SeekStatus::COMPLETED; + } else { + if (!hasSoughtByTimestamp_.load(std::memory_order_acquire)) { + startMessageId_ = seekMessageId_.get(); + } + seekCallback_.release()(result); + } } else { LOG_ERROR(getName() << "Failed to seek: " << result); seekMessageId_ = originalSeekMessageId; - duringSeek_ = false; + seekStatus_ = SeekStatus::NOT_STARTED; + seekCallback_.release()(result); } - callback(result); }); } @@ -1690,9 +1734,11 @@ std::shared_ptr ConsumerImpl::get_shared_this_ptr() { } void ConsumerImpl::cancelTimers() noexcept { - boost::system::error_code ec; + ASIO_ERROR ec; batchReceiveTimer_->cancel(ec); checkExpiredChunkedTimer_->cancel(ec); + unAckedMessageTrackerPtr_->stop(); + consumerStatsBasePtr_->stop(); } void ConsumerImpl::processPossibleToDLQ(const MessageId& messageId, ProcessDLQCallBack cb) { @@ -1710,6 +1756,7 @@ void ConsumerImpl::processPossibleToDLQ(const MessageId& messageId, ProcessDLQCa ProducerConfiguration producerConfiguration; producerConfiguration.setSchema(config_.getSchema()); producerConfiguration.setBlockIfQueueFull(false); + producerConfiguration.setBatchingEnabled(false); producerConfiguration.impl_->initialSubscriptionName = deadLetterPolicy_.getInitialSubscriptionName(); ClientImplPtr client = client_.lock(); @@ -1777,7 +1824,7 @@ void ConsumerImpl::processPossibleToDLQ(const MessageId& messageId, ProcessDLQCa } if (result != ResultOk) { LOG_WARN("{" << self->topic() << "} {" << self->subscription_ << "} {" - << self->consumerName_ << "} Failed to acknowledge the message {" + << self->getConsumerName() << "} Failed to acknowledge the message {" << originMessageId << "} of the original topic but send to the DLQ successfully : " << result); @@ -1790,7 +1837,7 @@ void ConsumerImpl::processPossibleToDLQ(const MessageId& messageId, ProcessDLQCa }); } else { LOG_WARN("{" << self->topic() << "} {" << self->subscription_ << "} {" - << self->consumerName_ << "} Failed to send DLQ message to {" + << self->getConsumerName() << "} Failed to send DLQ message to {" << self->deadLetterPolicy_.getDeadLetterTopic() << "} for message id " << "{" << originMessageId << "} : " << res); cb(false); diff --git a/lib/ConsumerImpl.h b/lib/ConsumerImpl.h index 61d96b1c..35636ad0 100644 --- a/lib/ConsumerImpl.h +++ b/lib/ConsumerImpl.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -75,6 +76,13 @@ const static std::string SYSTEM_PROPERTY_REAL_TOPIC = "REAL_TOPIC"; const static std::string PROPERTY_ORIGIN_MESSAGE_ID = "ORIGIN_MESSAGE_ID"; const static std::string DLQ_GROUP_TOPIC_SUFFIX = "-DLQ"; +enum class SeekStatus : std::uint8_t +{ + NOT_STARTED, + IN_PROGRESS, + COMPLETED +}; + class ConsumerImpl : public ConsumerImplBase { public: ConsumerImpl(const ClientImplPtr client, const std::string& topic, const std::string& subscriptionName, @@ -131,6 +139,7 @@ class ConsumerImpl : public ConsumerImplBase { void hasMessageAvailableAsync(HasMessageAvailableCallback callback) override; virtual void disconnectConsumer(); + virtual void disconnectConsumer(const boost::optional& assignedBrokerUrl); Result fetchSingleMessageFromBroker(Message& msg); virtual bool isCumulativeAcknowledgementAllowed(ConsumerType consumerType); @@ -192,8 +201,19 @@ class ConsumerImpl : public ConsumerImplBase { const DeadlineTimerPtr& timer, BrokerGetLastMessageIdCallback callback); - boost::optional clearReceiveQueue(); - void seekAsyncInternal(long requestId, SharedBuffer seek, const MessageId& seekId, long timestamp, + void clearReceiveQueue(); + using SeekArg = boost::variant; + friend std::ostream& operator<<(std::ostream& os, const SeekArg& seekArg) { + auto ptr = boost::get(&seekArg); + if (ptr) { + os << *ptr; + } else { + os << *boost::get(&seekArg); + } + return os; + } + + void seekAsyncInternal(long requestId, SharedBuffer seek, const SeekArg& seekArg, ResultCallback callback); void processPossibleToDLQ(const MessageId& messageId, ProcessDLQCallBack cb); @@ -216,7 +236,6 @@ class ConsumerImpl : public ConsumerImplBase { std::atomic_int availablePermits_; const int receiverQueueRefillThreshold_; uint64_t consumerId_; - std::string consumerName_; const std::string consumerStr_; int32_t partitionIndex_ = -1; Promise consumerCreatedPromise_; @@ -224,7 +243,7 @@ class ConsumerImpl : public ConsumerImplBase { CompressionCodecProvider compressionCodecProvider_; UnAckedMessageTrackerPtr unAckedMessageTrackerPtr_; BrokerConsumerStatsImpl brokerConsumerStats_; - NegativeAcksTracker negativeAcksTracker_; + std::shared_ptr negativeAcksTracker_; AckGroupingTrackerPtr ackGroupingTrackerPtr_; MessageCryptoPtr msgCrypto_; @@ -239,9 +258,13 @@ class ConsumerImpl : public ConsumerImplBase { MessageId lastDequedMessageId_{MessageId::earliest()}; MessageId lastMessageIdInBroker_{MessageId::earliest()}; - std::atomic_bool duringSeek_{false}; + std::atomic seekStatus_{SeekStatus::NOT_STARTED}; + Synchronized seekCallback_{[](Result) {}}; Synchronized> startMessageId_; Synchronized seekMessageId_{MessageId::earliest()}; + std::atomic hasSoughtByTimestamp_{false}; + + bool duringSeek() const { return seekStatus_ != SeekStatus::NOT_STARTED; } class ChunkedMessageCtx { public: @@ -332,9 +355,24 @@ class ConsumerImpl : public ConsumerImplBase { const proto::MessageIdData& messageIdData, const ClientConnectionPtr& cnx, MessageId& messageId); - friend class PulsarFriend; + bool hasMoreMessages() const { + std::lock_guard lock{mutexForMessageId_}; + if (lastMessageIdInBroker_.entryId() == -1L) { + return false; + } - // these two declared friend to access setNegativeAcknowledgeEnabledForTesting + const auto inclusive = config_.isStartMessageIdInclusive(); + if (lastDequedMessageId_ == MessageId::earliest()) { + // If startMessageId_ is none, use latest so that this method will return false + const auto startMessageId = startMessageId_.get().value_or(MessageId::latest()); + return inclusive ? (lastMessageIdInBroker_ >= startMessageId) + : (lastMessageIdInBroker_ > startMessageId); + } else { + return lastMessageIdInBroker_ > lastDequedMessageId_; + } + } + + friend class PulsarFriend; friend class MultiTopicsConsumerImpl; FRIEND_TEST(ConsumerTest, testRedeliveryOfDecryptionFailedMessages); diff --git a/lib/ConsumerImplBase.cc b/lib/ConsumerImplBase.cc index 6c86aa44..851d41e8 100644 --- a/lib/ConsumerImplBase.cc +++ b/lib/ConsumerImplBase.cc @@ -33,7 +33,8 @@ ConsumerImplBase::ConsumerImplBase(ClientImplPtr client, const std::string& topi const ConsumerConfiguration& conf, ExecutorServicePtr listenerExecutor) : HandlerBase(client, topic, backoff), listenerExecutor_(listenerExecutor), - batchReceivePolicy_(conf.getBatchReceivePolicy()) { + batchReceivePolicy_(conf.getBatchReceivePolicy()), + consumerName_(conf.getConsumerName()) { auto userBatchReceivePolicy = conf.getBatchReceivePolicy(); if (userBatchReceivePolicy.getMaxNumMessages() > conf.getReceiverQueueSize()) { batchReceivePolicy_ = @@ -50,9 +51,9 @@ ConsumerImplBase::ConsumerImplBase(ClientImplPtr client, const std::string& topi void ConsumerImplBase::triggerBatchReceiveTimerTask(long timeoutMs) { if (timeoutMs > 0) { - batchReceiveTimer_->expires_from_now(boost::posix_time::milliseconds(timeoutMs)); + batchReceiveTimer_->expires_from_now(std::chrono::milliseconds(timeoutMs)); std::weak_ptr weakSelf{shared_from_this()}; - batchReceiveTimer_->async_wait([weakSelf](const boost::system::error_code& ec) { + batchReceiveTimer_->async_wait([weakSelf](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self && !ec) { self->doBatchReceiveTimeTask(); diff --git a/lib/ConsumerImplBase.h b/lib/ConsumerImplBase.h index 2f3420c6..1b7e86e1 100644 --- a/lib/ConsumerImplBase.h +++ b/lib/ConsumerImplBase.h @@ -82,6 +82,8 @@ class ConsumerImplBase : public HandlerBase { virtual const std::string& getName() const override = 0; virtual void hasMessageAvailableAsync(HasMessageAvailableCallback callback) = 0; + const std::string& getConsumerName() const noexcept { return consumerName_; } + protected: // overrided methods from HandlerBase Future connectionOpened(const ClientConnectionPtr& cnx) override { @@ -106,6 +108,8 @@ class ConsumerImplBase : public HandlerBase { virtual bool hasEnoughMessagesForBatchReceive() const = 0; private: + const std::string consumerName_; + virtual void setNegativeAcknowledgeEnabledForTesting(bool enabled) = 0; friend class MultiTopicsConsumerImpl; diff --git a/lib/CurlWrapper.h b/lib/CurlWrapper.h index 89b7919d..749a79c3 100644 --- a/lib/CurlWrapper.h +++ b/lib/CurlWrapper.h @@ -172,7 +172,10 @@ inline CurlWrapper::Result CurlWrapper::get(const std::string& url, const std::s if (responseCode == 307 || responseCode == 302 || responseCode == 301) { char* url; curl_easy_getinfo(handle_, CURLINFO_REDIRECT_URL, &url); - result.redirectUrl = url; + // `url` is null when the host of the redirect URL cannot be resolved + if (url) { + result.redirectUrl = url; + } } return result; } diff --git a/lib/ExecutorService.cc b/lib/ExecutorService.cc index a0dff0b6..794e3619 100644 --- a/lib/ExecutorService.cc +++ b/lib/ExecutorService.cc @@ -32,7 +32,7 @@ void ExecutorService::start() { auto self = shared_from_this(); std::thread t{[this, self] { LOG_DEBUG("Run io_service in a single thread"); - boost::system::error_code ec; + ASIO_ERROR ec; while (!closed_) { io_service_.restart(); IOService::work work{getIOService()}; @@ -63,22 +63,22 @@ ExecutorServicePtr ExecutorService::create() { } /* - * factory method of boost::asio::ip::tcp::socket associated with io_service_ instance + * factory method of ASIO::ip::tcp::socket associated with io_service_ instance * @ returns shared_ptr to this socket */ SocketPtr ExecutorService::createSocket() { try { - return SocketPtr(new boost::asio::ip::tcp::socket(io_service_)); - } catch (const boost::system::system_error &e) { + return SocketPtr(new ASIO::ip::tcp::socket(io_service_)); + } catch (const ASIO_SYSTEM_ERROR &e) { restart(); auto error = std::string("Failed to create socket: ") + e.what(); throw std::runtime_error(error); } } -TlsSocketPtr ExecutorService::createTlsSocket(SocketPtr &socket, boost::asio::ssl::context &ctx) { - return std::shared_ptr>( - new boost::asio::ssl::stream(*socket, ctx)); +TlsSocketPtr ExecutorService::createTlsSocket(SocketPtr &socket, ASIO::ssl::context &ctx) { + return std::shared_ptr>( + new ASIO::ssl::stream(*socket, ctx)); } /* @@ -87,8 +87,8 @@ TlsSocketPtr ExecutorService::createTlsSocket(SocketPtr &socket, boost::asio::ss */ TcpResolverPtr ExecutorService::createTcpResolver() { try { - return TcpResolverPtr(new boost::asio::ip::tcp::resolver(io_service_)); - } catch (const boost::system::system_error &e) { + return TcpResolverPtr(new ASIO::ip::tcp::resolver(io_service_)); + } catch (const ASIO_SYSTEM_ERROR &e) { restart(); auto error = std::string("Failed to create resolver: ") + e.what(); throw std::runtime_error(error); @@ -97,10 +97,10 @@ TcpResolverPtr ExecutorService::createTcpResolver() { DeadlineTimerPtr ExecutorService::createDeadlineTimer() { try { - return DeadlineTimerPtr(new boost::asio::deadline_timer(io_service_)); - } catch (const boost::system::system_error &e) { + return DeadlineTimerPtr(new ASIO::steady_timer(io_service_)); + } catch (const ASIO_SYSTEM_ERROR &e) { restart(); - auto error = std::string("Failed to create deadline_timer: ") + e.what(); + auto error = std::string("Failed to create steady_timer: ") + e.what(); throw std::runtime_error(error); } } @@ -133,10 +133,10 @@ void ExecutorService::postWork(std::function task) { io_service_.pos ExecutorServiceProvider::ExecutorServiceProvider(int nthreads) : executors_(nthreads), executorIdx_(0), mutex_() {} -ExecutorServicePtr ExecutorServiceProvider::get() { +ExecutorServicePtr ExecutorServiceProvider::get(size_t idx) { + idx %= executors_.size(); Lock lock(mutex_); - int idx = executorIdx_++ % executors_.size(); if (!executors_[idx]) { executors_[idx] = ExecutorService::create(); } diff --git a/lib/ExecutorService.h b/lib/ExecutorService.h index 4717ccb5..89d06d30 100644 --- a/lib/ExecutorService.h +++ b/lib/ExecutorService.h @@ -22,10 +22,15 @@ #include #include -#include +#ifdef USE_ASIO +#include +#include +#include +#else #include #include #include +#endif #include #include #include @@ -33,14 +38,15 @@ #include #include +#include "AsioTimer.h" + namespace pulsar { -typedef std::shared_ptr SocketPtr; -typedef std::shared_ptr > TlsSocketPtr; -typedef std::shared_ptr TcpResolverPtr; -typedef std::shared_ptr DeadlineTimerPtr; +typedef std::shared_ptr SocketPtr; +typedef std::shared_ptr > TlsSocketPtr; +typedef std::shared_ptr TcpResolverPtr; class PULSAR_PUBLIC ExecutorService : public std::enable_shared_from_this { public: - using IOService = boost::asio::io_service; + using IOService = ASIO::io_service; using SharedPtr = std::shared_ptr; static SharedPtr create(); @@ -51,7 +57,7 @@ class PULSAR_PUBLIC ExecutorService : public std::enable_shared_from_this ExecutorList; ExecutorList executors_; - int executorIdx_; + std::atomic_size_t executorIdx_; std::mutex mutex_; typedef std::unique_lock Lock; }; diff --git a/lib/Future.h b/lib/Future.h index 290ebc6f..22d43cbe 100644 --- a/lib/Future.h +++ b/lib/Future.h @@ -116,6 +116,8 @@ class Future { Result get(Type &result) { return state_->get(result); } + static Future failed(Result result); + private: InternalStatePtr state_; @@ -141,9 +143,16 @@ class Promise { Future getFuture() const { return Future{state_}; } private: - const InternalStatePtr state_; + InternalStatePtr state_; }; +template +inline Future Future::failed(Result result) { + Promise promise; + promise.setFailed(result); + return promise.getFuture(); +} + } // namespace pulsar #endif diff --git a/lib/HTTPLookupService.cc b/lib/HTTPLookupService.cc index 4ec72c1e..93b9db44 100644 --- a/lib/HTTPLookupService.cc +++ b/lib/HTTPLookupService.cc @@ -46,11 +46,11 @@ const static std::string ADMIN_PATH_V2 = "/admin/v2/"; const static std::string PARTITION_METHOD_NAME = "partitions"; const static int NUMBER_OF_LOOKUP_THREADS = 1; -HTTPLookupService::HTTPLookupService(ServiceNameResolver &serviceNameResolver, +HTTPLookupService::HTTPLookupService(const std::string &serviceUrl, const ClientConfiguration &clientConfiguration, const AuthenticationPtr &authData) : executorProvider_(std::make_shared(NUMBER_OF_LOOKUP_THREADS)), - serviceNameResolver_(serviceNameResolver), + serviceNameResolver_(serviceUrl), authenticationPtr_(authData), lookupTimeoutInSeconds_(clientConfiguration.getOperationTimeoutSeconds()), maxLookupRedirects_(clientConfiguration.getMaxLookupRedirects()), @@ -191,98 +191,77 @@ Result HTTPLookupService::sendHTTPRequest(std::string completeUrl, std::string & Result HTTPLookupService::sendHTTPRequest(std::string completeUrl, std::string &responseData, long &responseCode) { - uint16_t reqCount = 0; - Result retResult = ResultOk; - while (++reqCount <= maxLookupRedirects_) { - // Authorization data - AuthenticationDataPtr authDataContent; - Result authResult = authenticationPtr_->getAuthData(authDataContent); - if (authResult != ResultOk) { - LOG_ERROR("Failed to getAuthData: " << authResult); - return authResult; - } + // Authorization data + AuthenticationDataPtr authDataContent; + Result authResult = authenticationPtr_->getAuthData(authDataContent); + if (authResult != ResultOk) { + LOG_ERROR("Failed to getAuthData: " << authResult); + return authResult; + } - CurlWrapper curl; - if (!curl.init()) { - LOG_ERROR("Unable to curl_easy_init for url " << completeUrl); - return ResultLookupError; - } + CurlWrapper curl; + if (!curl.init()) { + LOG_ERROR("Unable to curl_easy_init for url " << completeUrl); + return ResultLookupError; + } - std::unique_ptr tlsContext; - if (isUseTls_) { - tlsContext.reset(new CurlWrapper::TlsContext); - tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_; - tlsContext->validateHostname = tlsValidateHostname_; - tlsContext->allowInsecure = tlsAllowInsecure_; - if (authDataContent->hasDataForTls()) { - tlsContext->certPath = authDataContent->getTlsCertificates(); - tlsContext->keyPath = authDataContent->getTlsPrivateKey(); - } else { - tlsContext->certPath = tlsCertificateFilePath_; - tlsContext->keyPath = tlsPrivateFilePath_; - } + std::unique_ptr tlsContext; + if (isUseTls_) { + tlsContext.reset(new CurlWrapper::TlsContext); + tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_; + tlsContext->validateHostname = tlsValidateHostname_; + tlsContext->allowInsecure = tlsAllowInsecure_; + if (authDataContent->hasDataForTls()) { + tlsContext->certPath = authDataContent->getTlsCertificates(); + tlsContext->keyPath = authDataContent->getTlsPrivateKey(); + } else { + tlsContext->certPath = tlsCertificateFilePath_; + tlsContext->keyPath = tlsPrivateFilePath_; } + } - LOG_INFO("Curl [" << reqCount << "] Lookup Request sent for " << completeUrl); - CurlWrapper::Options options; - options.timeoutInSeconds = lookupTimeoutInSeconds_; - options.userAgent = std::string("Pulsar-CPP-v") + PULSAR_VERSION_STR; - options.maxLookupRedirects = 1; // redirection is implemented by the outer loop - auto result = curl.get(completeUrl, authDataContent->getHttpHeaders(), options, tlsContext.get()); - const auto &error = result.error; - if (!error.empty()) { - LOG_ERROR(completeUrl << " failed: " << error); - return ResultConnectError; - } + LOG_INFO("Curl Lookup Request sent for " << completeUrl); + CurlWrapper::Options options; + options.timeoutInSeconds = lookupTimeoutInSeconds_; + options.userAgent = std::string("Pulsar-CPP-v") + PULSAR_VERSION_STR; + options.maxLookupRedirects = maxLookupRedirects_; + auto result = curl.get(completeUrl, authDataContent->getHttpHeaders(), options, tlsContext.get()); + const auto &error = result.error; + if (!error.empty()) { + LOG_ERROR(completeUrl << " failed: " << error); + return ResultConnectError; + } - responseData = result.responseData; - responseCode = result.responseCode; - auto res = result.code; - LOG_INFO("Response received for url " << completeUrl << " responseCode " << responseCode - << " curl res " << res); - - const auto &redirectUrl = result.redirectUrl; - switch (res) { - case CURLE_OK: - if (responseCode == 200) { - retResult = ResultOk; - } else if (!redirectUrl.empty()) { - LOG_INFO("Response from url " << completeUrl << " to new url " << redirectUrl); - completeUrl = redirectUrl; - retResult = ResultLookupError; - } else { - retResult = ResultLookupError; - } - break; - case CURLE_COULDNT_CONNECT: - LOG_ERROR("Response failed for url " << completeUrl << ". Error Code " << res); - retResult = ResultRetryable; - break; - case CURLE_COULDNT_RESOLVE_PROXY: - case CURLE_COULDNT_RESOLVE_HOST: - case CURLE_HTTP_RETURNED_ERROR: - LOG_ERROR("Response failed for url " << completeUrl << ". Error Code " << res); - retResult = ResultConnectError; - break; - case CURLE_READ_ERROR: - LOG_ERROR("Response failed for url " << completeUrl << ". Error Code " << res); - retResult = ResultReadError; - break; - case CURLE_OPERATION_TIMEDOUT: - LOG_ERROR("Response failed for url " << completeUrl << ". Error Code " << res); - retResult = ResultTimeout; - break; - default: - LOG_ERROR("Response failed for url " << completeUrl << ". Error Code " << res); - retResult = ResultLookupError; - break; - } - if (redirectUrl.empty()) { - break; - } + responseData = result.responseData; + responseCode = result.responseCode; + auto res = result.code; + if (res == CURLE_OK) { + LOG_INFO("Response received for url " << completeUrl << " responseCode " << responseCode); + } else if (res == CURLE_TOO_MANY_REDIRECTS) { + LOG_ERROR("Response received for url " << completeUrl << ": " << curl_easy_strerror(res) + << ", curl error: " << result.serverError + << ", redirect URL: " << result.redirectUrl); + } else { + LOG_ERROR("Response failed for url " << completeUrl << ": " << curl_easy_strerror(res) + << ", curl error: " << result.serverError); } - return retResult; + switch (res) { + case CURLE_OK: + return ResultOk; + case CURLE_COULDNT_CONNECT: + return ResultRetryable; + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_HTTP_RETURNED_ERROR: + return ResultConnectError; + case CURLE_READ_ERROR: + return ResultReadError; + case CURLE_OPERATION_TIMEDOUT: + return ResultTimeout; + default: + return ResultLookupError; + } } LookupDataResultPtr HTTPLookupService::parsePartitionData(const std::string &json) { diff --git a/lib/HTTPLookupService.h b/lib/HTTPLookupService.h index cf0b0ad1..17dd110e 100644 --- a/lib/HTTPLookupService.h +++ b/lib/HTTPLookupService.h @@ -40,7 +40,7 @@ class HTTPLookupService : public LookupService, public std::enable_shared_from_t typedef Promise LookupPromise; ExecutorServiceProviderPtr executorProvider_; - ServiceNameResolver& serviceNameResolver_; + ServiceNameResolver serviceNameResolver_; AuthenticationPtr authenticationPtr_; int lookupTimeoutInSeconds_; const int maxLookupRedirects_; @@ -64,7 +64,7 @@ class HTTPLookupService : public LookupService, public std::enable_shared_from_t Result sendHTTPRequest(std::string completeUrl, std::string& responseData, long& responseCode); public: - HTTPLookupService(ServiceNameResolver&, const ClientConfiguration&, const AuthenticationPtr&); + HTTPLookupService(const std::string&, const ClientConfiguration&, const AuthenticationPtr&); LookupResultFuture getBroker(const TopicName& topicName) override; @@ -74,6 +74,8 @@ class HTTPLookupService : public LookupService, public std::enable_shared_from_t Future getTopicsOfNamespaceAsync( const NamespaceNamePtr& nsName, CommandGetTopicsOfNamespace_Mode mode) override; + + ServiceNameResolver& getServiceNameResolver() override { return serviceNameResolver_; } }; } // namespace pulsar diff --git a/lib/HandlerBase.cc b/lib/HandlerBase.cc index 986063e0..46b918f6 100644 --- a/lib/HandlerBase.cc +++ b/lib/HandlerBase.cc @@ -18,6 +18,7 @@ */ #include "HandlerBase.h" +#include "Backoff.h" #include "ClientConnection.h" #include "ClientImpl.h" #include "ExecutorService.h" @@ -32,17 +33,24 @@ namespace pulsar { HandlerBase::HandlerBase(const ClientImplPtr& client, const std::string& topic, const Backoff& backoff) : topic_(std::make_shared(topic)), client_(client), + connectionKeySuffix_(client->getConnectionPool().generateRandomIndex()), executor_(client->getIOExecutorProvider()->get()), mutex_(), creationTimestamp_(TimeUtils::now()), - operationTimeut_(seconds(client->conf().getOperationTimeoutSeconds())), + operationTimeut_(std::chrono::seconds(client->conf().getOperationTimeoutSeconds())), state_(NotStarted), backoff_(backoff), epoch_(0), timer_(executor_->createDeadlineTimer()), - reconnectionPending_(false) {} - -HandlerBase::~HandlerBase() { timer_->cancel(); } + creationTimer_(executor_->createDeadlineTimer()), + reconnectionPending_(false), + redirectedClusterURI_("") {} + +HandlerBase::~HandlerBase() { + ASIO_ERROR ignored; + timer_->cancel(ignored); + creationTimer_->cancel(ignored); +} void HandlerBase::start() { // guard against concurrent state changes such as closing @@ -50,6 +58,16 @@ void HandlerBase::start() { if (state_.compare_exchange_strong(state, Pending)) { grabCnx(); } + creationTimer_->expires_from_now(operationTimeut_); + std::weak_ptr weakSelf{shared_from_this()}; + creationTimer_->async_wait([this, weakSelf](const ASIO_ERROR& error) { + auto self = weakSelf.lock(); + if (self && !error) { + connectionFailed(ResultTimeout); + ASIO_ERROR ignored; + timer_->cancel(ignored); + } + }); } ClientConnectionWeakPtr HandlerBase::getCnx() const { @@ -66,7 +84,18 @@ void HandlerBase::setCnx(const ClientConnectionPtr& cnx) { connection_ = cnx; } -void HandlerBase::grabCnx() { +void HandlerBase::grabCnx() { grabCnx(boost::none); } + +Future HandlerBase::getConnection( + const ClientImplPtr& client, const boost::optional& assignedBrokerUrl) { + if (assignedBrokerUrl && client->getLookupCount() > 0) { + return client->connect(getRedirectedClusterURI(), assignedBrokerUrl.get(), connectionKeySuffix_); + } else { + return client->getConnection(getRedirectedClusterURI(), topic(), connectionKeySuffix_); + } +} + +void HandlerBase::grabCnx(const boost::optional& assignedBrokerUrl) { bool expectedState = false; if (!reconnectionPending_.compare_exchange_strong(expectedState, true)) { LOG_INFO(getName() << "Ignoring reconnection attempt since there's already a pending reconnection"); @@ -83,18 +112,19 @@ void HandlerBase::grabCnx() { ClientImplPtr client = client_.lock(); if (!client) { LOG_WARN(getName() << "Client is invalid when calling grabCnx()"); - connectionFailed(ResultConnectError); + connectionFailed(ResultAlreadyClosed); reconnectionPending_ = false; return; } auto self = shared_from_this(); - client->getConnection(topic()).addListener([this, self](Result result, const ClientConnectionPtr& cnx) { + auto cnxFuture = getConnection(client, assignedBrokerUrl); + cnxFuture.addListener([this, self](Result result, const ClientConnectionPtr& cnx) { if (result == ResultOk) { LOG_DEBUG(getName() << "Connected to broker: " << cnx->cnxString()); connectionOpened(cnx).addListener([this, self](Result result, bool) { // Do not use bool, only Result. reconnectionPending_ = false; - if (isResultRetryable(result)) { + if (result != ResultOk && isResultRetryable(result)) { scheduleReconnection(); } }); @@ -138,23 +168,23 @@ void HandlerBase::handleDisconnection(Result result, const ClientConnectionPtr& break; } } - -void HandlerBase::scheduleReconnection() { +void HandlerBase::scheduleReconnection() { scheduleReconnection(boost::none); } +void HandlerBase::scheduleReconnection(const boost::optional& assignedBrokerUrl) { const auto state = state_.load(); if (state == Pending || state == Ready) { - TimeDuration delay = backoff_.next(); + TimeDuration delay = assignedBrokerUrl ? std::chrono::milliseconds(0) : backoff_.next(); - LOG_INFO(getName() << "Schedule reconnection in " << (delay.total_milliseconds() / 1000.0) << " s"); + LOG_INFO(getName() << "Schedule reconnection in " << (toMillis(delay) / 1000.0) << " s"); timer_->expires_from_now(delay); // passing shared_ptr here since time_ will get destroyed, so tasks will be cancelled // so we will not run into the case where grabCnx is invoked on out of scope handler auto name = getName(); std::weak_ptr weakSelf{shared_from_this()}; - timer_->async_wait([name, weakSelf](const boost::system::error_code& ec) { + timer_->async_wait([name, weakSelf, assignedBrokerUrl](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self) { - self->handleTimeout(ec); + self->handleTimeout(ec, assignedBrokerUrl); } else { LOG_WARN(name << "Cancel the reconnection since the handler is destroyed"); } @@ -162,13 +192,13 @@ void HandlerBase::scheduleReconnection() { } } -void HandlerBase::handleTimeout(const boost::system::error_code& ec) { +void HandlerBase::handleTimeout(const ASIO_ERROR& ec, const boost::optional& assignedBrokerUrl) { if (ec) { LOG_DEBUG(getName() << "Ignoring timer cancelled event, code[" << ec << "]"); return; } else { epoch_++; - grabCnx(); + grabCnx(assignedBrokerUrl); } } @@ -180,4 +210,13 @@ Result HandlerBase::convertToTimeoutIfNecessary(Result result, ptime startTimest } } +void HandlerBase::setRedirectedClusterURI(const std::string& serviceUrl) { + Lock lock(mutex_); + redirectedClusterURI_ = serviceUrl; +} +const std::string& HandlerBase::getRedirectedClusterURI() { + Lock lock(mutex_); + return redirectedClusterURI_; +} + } // namespace pulsar diff --git a/lib/HandlerBase.h b/lib/HandlerBase.h index 937b308d..b68dce32 100644 --- a/lib/HandlerBase.h +++ b/lib/HandlerBase.h @@ -20,20 +20,18 @@ #define _PULSAR_HANDLER_BASE_HEADER_ #include -#include +#include #include #include #include +#include "AsioTimer.h" #include "Backoff.h" #include "Future.h" +#include "TimeUtils.h" namespace pulsar { -using namespace boost::posix_time; -using boost::posix_time::milliseconds; -using boost::posix_time::seconds; - class ClientImpl; using ClientImplPtr = std::shared_ptr; using ClientImplWeakPtr = std::weak_ptr; @@ -42,7 +40,6 @@ using ClientConnectionPtr = std::shared_ptr; using ClientConnectionWeakPtr = std::weak_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; class HandlerBase : public std::enable_shared_from_this { public: @@ -55,13 +52,26 @@ class HandlerBase : public std::enable_shared_from_this { ClientConnectionWeakPtr getCnx() const; void setCnx(const ClientConnectionPtr& cnx); void resetCnx() { setCnx(nullptr); } + void setRedirectedClusterURI(const std::string& serviceUrl); + const std::string& getRedirectedClusterURI(); protected: + /* + * tries reconnection and sets connection_ to valid object + * @param assignedBrokerUrl assigned broker url to directly connect to without lookup + */ + void grabCnx(const boost::optional& assignedBrokerUrl); + /* * tries reconnection and sets connection_ to valid object */ void grabCnx(); + /* + * Schedule reconnection after backoff time + * @param assignedBrokerUrl assigned broker url to directly connect to without lookup + */ + void scheduleReconnection(const boost::optional& assignedBrokerUrl); /* * Schedule reconnection after backoff time */ @@ -90,15 +100,23 @@ class HandlerBase : public std::enable_shared_from_this { const std::string& topic() const { return *topic_; } const std::shared_ptr& getTopicPtr() const { return topic_; } + long firstRequestIdAfterConnect() const { + return firstRequestIdAfterConnect_.load(std::memory_order_acquire); + } + private: const std::shared_ptr topic_; + Future getConnection(const ClientImplPtr& client, + const boost::optional& assignedBrokerUrl); + void handleDisconnection(Result result, const ClientConnectionPtr& cnx); - void handleTimeout(const boost::system::error_code& ec); + void handleTimeout(const ASIO_ERROR& ec, const boost::optional& assignedBrokerUrl); protected: ClientImplWeakPtr client_; + const size_t connectionKeySuffix_; ExecutorServicePtr executor_; mutable std::mutex mutex_; std::mutex pendingReceiveMutex_; @@ -126,12 +144,20 @@ class HandlerBase : public std::enable_shared_from_this { Result convertToTimeoutIfNecessary(Result result, ptime startTimestamp) const; + void setFirstRequestIdAfterConnect(long requestId) { + firstRequestIdAfterConnect_.store(requestId, std::memory_order_release); + } + private: DeadlineTimerPtr timer_; + DeadlineTimerPtr creationTimer_; mutable std::mutex connectionMutex_; std::atomic reconnectionPending_; ClientConnectionWeakPtr connection_; + std::string redirectedClusterURI_; + std::atomic firstRequestIdAfterConnect_{-1L}; + friend class ClientConnection; friend class PulsarFriend; }; diff --git a/lib/Int64SerDes.h b/lib/Int64SerDes.h index dbc5d8a7..f1f5eef3 100644 --- a/lib/Int64SerDes.h +++ b/lib/Int64SerDes.h @@ -20,7 +20,12 @@ #include -#include // for ntohl +// for ntohl +#ifdef USE_ASIO +#include +#else +#include +#endif namespace pulsar { diff --git a/lib/LogUtils.cc b/lib/LogUtils.cc index b210f1bc..8739bb82 100644 --- a/lib/LogUtils.cc +++ b/lib/LogUtils.cc @@ -25,6 +25,7 @@ namespace pulsar { +static std::atomic s_defaultLoggerFactory(new ConsoleLoggerFactory()); static std::atomic s_loggerFactory(nullptr); void LogUtils::setLoggerFactory(std::unique_ptr loggerFactory) { @@ -37,10 +38,10 @@ void LogUtils::setLoggerFactory(std::unique_ptr loggerFactory) { LoggerFactory* LogUtils::getLoggerFactory() { if (s_loggerFactory.load() == nullptr) { - std::unique_ptr newFactory(new ConsoleLoggerFactory()); - setLoggerFactory(std::move(newFactory)); + return s_defaultLoggerFactory.load(); + } else { + return s_loggerFactory.load(); } - return s_loggerFactory.load(); } std::string LogUtils::getLoggerName(const std::string& path) { diff --git a/lib/LogUtils.h b/lib/LogUtils.h index c8abd138..3df1a7ab 100644 --- a/lib/LogUtils.h +++ b/lib/LogUtils.h @@ -34,16 +34,19 @@ namespace pulsar { #define PULSAR_UNLIKELY(expr) (expr) #endif -#define DECLARE_LOG_OBJECT() \ - static pulsar::Logger* logger() { \ - static thread_local std::unique_ptr threadSpecificLogPtr; \ - pulsar::Logger* ptr = threadSpecificLogPtr.get(); \ - if (PULSAR_UNLIKELY(!ptr)) { \ - std::string logger = pulsar::LogUtils::getLoggerName(__FILE__); \ - threadSpecificLogPtr.reset(pulsar::LogUtils::getLoggerFactory()->getLogger(logger)); \ - ptr = threadSpecificLogPtr.get(); \ - } \ - return ptr; \ +#define DECLARE_LOG_OBJECT() \ + static pulsar::Logger* logger() { \ + static thread_local uintptr_t loggerFactoryPtr = 0; \ + static thread_local std::unique_ptr threadSpecificLogPtr; \ + pulsar::Logger* ptr = threadSpecificLogPtr.get(); \ + if (PULSAR_UNLIKELY(loggerFactoryPtr != (uintptr_t)pulsar::LogUtils::getLoggerFactory()) || \ + PULSAR_UNLIKELY(!ptr)) { \ + std::string logger = pulsar::LogUtils::getLoggerName(__FILE__); \ + threadSpecificLogPtr.reset(pulsar::LogUtils::getLoggerFactory()->getLogger(logger)); \ + ptr = threadSpecificLogPtr.get(); \ + loggerFactoryPtr = (uintptr_t)pulsar::LogUtils::getLoggerFactory(); \ + } \ + return ptr; \ } #define LOG_DEBUG(message) \ diff --git a/lib/LookupService.h b/lib/LookupService.h index 35f47308..684984fc 100644 --- a/lib/LookupService.h +++ b/lib/LookupService.h @@ -29,6 +29,7 @@ #include "Future.h" #include "LookupDataResult.h" #include "ProtoApiEnums.h" +#include "ServiceNameResolver.h" namespace pulsar { using NamespaceTopicsPtr = std::shared_ptr>; @@ -42,6 +43,7 @@ class LookupService { struct LookupResult { std::string logicalAddress; std::string physicalAddress; + bool proxyThroughServiceUrl; friend std::ostream& operator<<(std::ostream& os, const LookupResult& lookupResult) { return os << "logical address: " << lookupResult.logicalAddress @@ -85,6 +87,8 @@ class LookupService { virtual Future getSchema(const TopicNamePtr& topicName, const std::string& version = "") = 0; + virtual ServiceNameResolver& getServiceNameResolver() = 0; + virtual ~LookupService() {} virtual void close() {} diff --git a/lib/MultiTopicsConsumerImpl.cc b/lib/MultiTopicsConsumerImpl.cc index abc54c80..14847851 100644 --- a/lib/MultiTopicsConsumerImpl.cc +++ b/lib/MultiTopicsConsumerImpl.cc @@ -18,6 +18,7 @@ */ #include "MultiTopicsConsumerImpl.h" +#include #include #include "ClientImpl.h" @@ -27,7 +28,6 @@ #include "LookupService.h" #include "MessageImpl.h" #include "MessagesImpl.h" -#include "MultiResultCallback.h" #include "MultiTopicsBrokerConsumerStatsImpl.h" #include "TopicName.h" #include "UnAckedMessageTrackerDisabled.h" @@ -37,6 +37,9 @@ DECLARE_LOG_OBJECT() using namespace pulsar; +using std::chrono::milliseconds; +using std::chrono::seconds; + MultiTopicsConsumerImpl::MultiTopicsConsumerImpl(ClientImplPtr client, TopicNamePtr topicName, int numPartitions, const std::string& subscriptionName, const ConsumerConfiguration& conf, @@ -86,10 +89,11 @@ MultiTopicsConsumerImpl::MultiTopicsConsumerImpl(ClientImplPtr client, const std } else { unAckedMessageTrackerPtr_.reset(new UnAckedMessageTrackerDisabled()); } + unAckedMessageTrackerPtr_->start(); auto partitionsUpdateInterval = static_cast(client->conf().getPartitionsUpdateInterval()); if (partitionsUpdateInterval > 0) { partitionsUpdateTimer_ = listenerExecutor_->createDeadlineTimer(); - partitionsUpdateInterval_ = boost::posix_time::seconds(partitionsUpdateInterval); + partitionsUpdateInterval_ = seconds(partitionsUpdateInterval); lookupServicePtr_ = client->getLookup(); } @@ -471,7 +475,7 @@ void MultiTopicsConsumerImpl::closeAsync(ResultCallback originalCallback) { }; const auto state = state_.load(); if (state == Closing || state == Closed) { - callback(ResultAlreadyClosed); + callback(ResultOk); return; } @@ -484,7 +488,7 @@ void MultiTopicsConsumerImpl::closeAsync(ResultCallback originalCallback) { if (consumers.empty()) { LOG_DEBUG("TopicsConsumer have no consumers to close " << " topic" << topic() << " subscription - " << subscriptionName_); - callback(ResultAlreadyClosed); + callback(ResultOk); return; } @@ -516,6 +520,9 @@ void MultiTopicsConsumerImpl::closeAsync(ResultCallback originalCallback) { } void MultiTopicsConsumerImpl::messageReceived(Consumer consumer, const Message& msg) { + if (PULSAR_UNLIKELY(duringSeek_.load(std::memory_order_acquire))) { + return; + } LOG_DEBUG("Received Message from one of the topic - " << consumer.getTopic() << " message:" << msg.getDataAsString()); msg.impl_->setTopicName(consumer.impl_->getTopicPtr()); @@ -902,9 +909,37 @@ void MultiTopicsConsumerImpl::seekAsync(uint64_t timestamp, ResultCallback callb return; } - MultiResultCallback multiResultCallback(callback, consumers_.size()); - consumers_.forEachValue([×tamp, &multiResultCallback](ConsumerImplPtr consumer) { - consumer->seekAsync(timestamp, multiResultCallback); + duringSeek_.store(true, std::memory_order_release); + consumers_.forEachValue([](const ConsumerImplPtr& consumer) { consumer->pauseMessageListener(); }); + unAckedMessageTrackerPtr_->clear(); + incomingMessages_.clear(); + incomingMessagesSize_ = 0L; + + auto weakSelf = weak_from_this(); + auto numConsumersLeft = std::make_shared>(consumers_.size()); + auto wrappedCallback = [this, weakSelf, callback, numConsumersLeft](Result result) { + auto self = weakSelf.lock(); + if (PULSAR_UNLIKELY(!self)) { + callback(result); + return; + } + if (result != ResultOk) { + *numConsumersLeft = 0; // skip the following callbacks + callback(result); + return; + } + if (--*numConsumersLeft > 0) { + return; + } + duringSeek_.store(false, std::memory_order_release); + listenerExecutor_->postWork([this, self] { + consumers_.forEachValue( + [](const ConsumerImplPtr& consumer) { consumer->resumeMessageListener(); }); + }); + callback(ResultOk); + }; + consumers_.forEachValue([timestamp, &wrappedCallback](const ConsumerImplPtr& consumer) { + consumer->seekAsync(timestamp, wrappedCallback); }); } @@ -935,7 +970,7 @@ uint64_t MultiTopicsConsumerImpl::getNumberOfConnectedConsumer() { void MultiTopicsConsumerImpl::runPartitionUpdateTask() { partitionsUpdateTimer_->expires_from_now(partitionsUpdateInterval_); auto weakSelf = weak_from_this(); - partitionsUpdateTimer_->async_wait([weakSelf](const boost::system::error_code& ec) { + partitionsUpdateTimer_->async_wait([weakSelf](const ASIO_ERROR& ec) { // If two requests call runPartitionUpdateTask at the same time, the timer will fail, and it // cannot continue at this time, and the request needs to be ignored. auto self = weakSelf.lock(); @@ -1086,7 +1121,7 @@ void MultiTopicsConsumerImpl::beforeConnectionChange(ClientConnection& cnx) { void MultiTopicsConsumerImpl::cancelTimers() noexcept { if (partitionsUpdateTimer_) { - boost::system::error_code ec; + ASIO_ERROR ec; partitionsUpdateTimer_->cancel(ec); } } diff --git a/lib/MultiTopicsConsumerImpl.h b/lib/MultiTopicsConsumerImpl.h index d4127f63..9d71a044 100644 --- a/lib/MultiTopicsConsumerImpl.h +++ b/lib/MultiTopicsConsumerImpl.h @@ -32,6 +32,7 @@ #include "LookupDataResult.h" #include "SynchronizedHashMap.h" #include "TestUtil.h" +#include "TimeUtils.h" #include "UnboundedBlockingQueue.h" namespace pulsar { @@ -119,7 +120,7 @@ class MultiTopicsConsumerImpl : public ConsumerImplBase { std::atomic_int incomingMessagesSize_ = {0}; MessageListener messageListener_; DeadlineTimerPtr partitionsUpdateTimer_; - boost::posix_time::time_duration partitionsUpdateInterval_; + TimeDuration partitionsUpdateInterval_; LookupServicePtr lookupServicePtr_; std::shared_ptr> numberTopicPartitions_; std::atomic failedResult{ResultOk}; @@ -130,6 +131,7 @@ class MultiTopicsConsumerImpl : public ConsumerImplBase { const Commands::SubscriptionMode subscriptionMode_; boost::optional startMessageId_; ConsumerInterceptorsPtr interceptors_; + std::atomic_bool duringSeek_{false}; /* methods */ void handleSinglePartitionConsumerCreated(Result result, ConsumerImplBaseWeakPtr consumerImplBaseWeakPtr, diff --git a/lib/NamespaceName.cc b/lib/NamespaceName.cc index f493db25..d635480c 100644 --- a/lib/NamespaceName.cc +++ b/lib/NamespaceName.cc @@ -93,7 +93,7 @@ std::shared_ptr NamespaceName::getNamespaceObject() { return std::shared_ptr(this); } -bool NamespaceName::operator==(const NamespaceName& namespaceName) { +bool NamespaceName::operator==(const NamespaceName& namespaceName) const { return this->namespace_.compare(namespaceName.namespace_) == 0; } diff --git a/lib/NamespaceName.h b/lib/NamespaceName.h index ce451a20..d1f5765b 100644 --- a/lib/NamespaceName.h +++ b/lib/NamespaceName.h @@ -37,7 +37,7 @@ class PULSAR_PUBLIC NamespaceName : public ServiceUnitId { static std::shared_ptr get(const std::string& property, const std::string& cluster, const std::string& namespaceName); static std::shared_ptr get(const std::string& property, const std::string& namespaceName); - bool operator==(const NamespaceName& namespaceName); + bool operator==(const NamespaceName& namespaceName) const; bool isV2(); std::string toString(); diff --git a/lib/NegativeAcksTracker.cc b/lib/NegativeAcksTracker.cc index 5c3ef3f8..e443496d 100644 --- a/lib/NegativeAcksTracker.cc +++ b/lib/NegativeAcksTracker.cc @@ -40,20 +40,25 @@ NegativeAcksTracker::NegativeAcksTracker(ClientImplPtr client, ConsumerImpl &con nackDelay_ = std::chrono::milliseconds(std::max(conf.getNegativeAckRedeliveryDelayMs(), MIN_NACK_DELAY_MILLIS)); - timerInterval_ = boost::posix_time::milliseconds((long)(nackDelay_.count() / 3)); - LOG_DEBUG("Created negative ack tracker with delay: " << nackDelay_.count() - << " ms - Timer interval: " << timerInterval_); + timerInterval_ = std::chrono::milliseconds((long)(nackDelay_.count() / 3)); + LOG_DEBUG("Created negative ack tracker with delay: " << nackDelay_.count() << " ms - Timer interval: " + << timerInterval_.count()); } void NegativeAcksTracker::scheduleTimer() { if (closed_) { return; } + std::weak_ptr weakSelf{shared_from_this()}; timer_->expires_from_now(timerInterval_); - timer_->async_wait(std::bind(&NegativeAcksTracker::handleTimer, this, std::placeholders::_1)); + timer_->async_wait([weakSelf](const ASIO_ERROR &ec) { + if (auto self = weakSelf.lock()) { + self->handleTimer(ec); + } + }); } -void NegativeAcksTracker::handleTimer(const boost::system::error_code &ec) { +void NegativeAcksTracker::handleTimer(const ASIO_ERROR &ec) { if (ec) { // Ignore cancelled events return; @@ -102,7 +107,7 @@ void NegativeAcksTracker::add(const MessageId &m) { void NegativeAcksTracker::close() { closed_ = true; - boost::system::error_code ec; + ASIO_ERROR ec; timer_->cancel(ec); std::lock_guard lock(mutex_); nackedMessages_.clear(); diff --git a/lib/NegativeAcksTracker.h b/lib/NegativeAcksTracker.h index 029f7d24..472e9763 100644 --- a/lib/NegativeAcksTracker.h +++ b/lib/NegativeAcksTracker.h @@ -23,12 +23,13 @@ #include #include -#include #include #include #include #include +#include "AsioDefines.h" +#include "AsioTimer.h" #include "TestUtil.h" namespace pulsar { @@ -36,11 +37,10 @@ namespace pulsar { class ConsumerImpl; class ClientImpl; using ClientImplPtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; -class NegativeAcksTracker { +class NegativeAcksTracker : public std::enable_shared_from_this { public: NegativeAcksTracker(ClientImplPtr client, ConsumerImpl &consumer, const ConsumerConfiguration &conf); @@ -56,13 +56,13 @@ class NegativeAcksTracker { private: void scheduleTimer(); - void handleTimer(const boost::system::error_code &ec); + void handleTimer(const ASIO_ERROR &ec); ConsumerImpl &consumer_; std::mutex mutex_; std::chrono::milliseconds nackDelay_; - boost::posix_time::milliseconds timerInterval_; + std::chrono::milliseconds timerInterval_; typedef typename std::chrono::steady_clock Clock; std::map nackedMessages_; diff --git a/lib/OpSendMsg.h b/lib/OpSendMsg.h index a1319e10..46fd9c1c 100644 --- a/lib/OpSendMsg.h +++ b/lib/OpSendMsg.h @@ -23,8 +23,6 @@ #include #include -#include - #include "ChunkMessageIdImpl.h" #include "PulsarApi.pb.h" #include "SharedBuffer.h" @@ -53,7 +51,7 @@ struct OpSendMsg { const int32_t numChunks; const uint32_t messagesCount; const uint64_t messagesSize; - const boost::posix_time::ptime timeout; + const ptime timeout; const SendCallback sendCallback; std::vector> trackerCallbacks; ChunkMessageIdListPtr chunkMessageIdList; @@ -98,7 +96,7 @@ struct OpSendMsg { numChunks(metadata.num_chunks_from_msg()), messagesCount(messagesCount), messagesSize(messagesSize), - timeout(TimeUtils::now() + boost::posix_time::milliseconds(sendTimeoutMs)), + timeout(TimeUtils::now() + std::chrono::milliseconds(sendTimeoutMs)), sendCallback(std::move(callback)), chunkMessageIdList(std::move(chunkMessageIdList)), sendArgs(new SendArguments(producerId, metadata.sequence_id(), metadata, payload)) {} diff --git a/lib/PartitionedProducerImpl.cc b/lib/PartitionedProducerImpl.cc index f7957d61..4178096c 100644 --- a/lib/PartitionedProducerImpl.cc +++ b/lib/PartitionedProducerImpl.cc @@ -58,7 +58,7 @@ PartitionedProducerImpl::PartitionedProducerImpl(ClientImplPtr client, const Top if (partitionsUpdateInterval > 0) { listenerExecutor_ = client->getListenerExecutorProvider()->get(); partitionsUpdateTimer_ = listenerExecutor_->createDeadlineTimer(); - partitionsUpdateInterval_ = boost::posix_time::seconds(partitionsUpdateInterval); + partitionsUpdateInterval_ = std::chrono::seconds(partitionsUpdateInterval); lookupServicePtr_ = client->getLookup(); } } @@ -69,7 +69,7 @@ MessageRoutingPolicyPtr PartitionedProducerImpl::getMessageRouter() { return std::make_shared( conf_.getHashingScheme(), conf_.getBatchingEnabled(), conf_.getBatchingMaxMessages(), conf_.getBatchingMaxAllowedSizeInBytes(), - boost::posix_time::milliseconds(conf_.getBatchingMaxPublishDelayMs())); + std::chrono::milliseconds(conf_.getBatchingMaxPublishDelayMs())); case ProducerConfiguration::CustomPartition: return conf_.getMessageRouterPtr(); case ProducerConfiguration::UseSinglePartition: @@ -92,10 +92,12 @@ unsigned int PartitionedProducerImpl::getNumPartitionsWithLock() const { return getNumPartitions(); } -ProducerImplPtr PartitionedProducerImpl::newInternalProducer(unsigned int partition, bool lazy) { +ProducerImplPtr PartitionedProducerImpl::newInternalProducer(unsigned int partition, bool lazy, + bool retryOnCreationError) { using namespace std::placeholders; auto client = client_.lock(); - auto producer = std::make_shared(client, *topicName_, conf_, interceptors_, partition); + auto producer = std::make_shared(client, *topicName_, conf_, interceptors_, partition, + retryOnCreationError); if (!client) { return producer; } @@ -127,13 +129,13 @@ void PartitionedProducerImpl::start() { for (unsigned int i = 0; i < getNumPartitions(); i++) { bool lazy = (short)i != partition; - producers_.push_back(newInternalProducer(i, lazy)); + producers_.push_back(newInternalProducer(i, lazy, false)); } producers_[partition]->start(); } else { for (unsigned int i = 0; i < getNumPartitions(); i++) { - producers_.push_back(newInternalProducer(i, false)); + producers_.push_back(newInternalProducer(i, false, false)); } for (ProducerList::const_iterator prod = producers_.begin(); prod != producers_.end(); prod++) { @@ -198,7 +200,9 @@ void PartitionedProducerImpl::createLazyPartitionProducer(unsigned int partition // override void PartitionedProducerImpl::sendAsync(const Message& msg, SendCallback callback) { if (state_ != Ready) { - callback(ResultAlreadyClosed, msg.getMessageId()); + if (callback) { + callback(ResultAlreadyClosed, msg.getMessageId()); + } return; } @@ -209,7 +213,9 @@ void PartitionedProducerImpl::sendAsync(const Message& msg, SendCallback callbac LOG_ERROR("Got Invalid Partition for message from Router Policy, Partition - " << partition); // change me: abort or notify failure in callback? // change to appropriate error if callback - callback(ResultUnknownError, msg.getMessageId()); + if (callback) { + callback(ResultUnknownError, msg.getMessageId()); + } return; } // find a producer for that partition, index should start from 0 @@ -223,7 +229,19 @@ void PartitionedProducerImpl::sendAsync(const Message& msg, SendCallback callbac producersLock.unlock(); // send message on that partition - producer->sendAsync(msg, callback); + if (!conf_.getLazyStartPartitionedProducers() || producer->ready()) { + producer->sendAsync(msg, std::move(callback)); + } else { + // Wrapping the callback into a lambda has overhead, so we check if the producer is ready first + producer->getProducerCreatedFuture().addListener( + [msg, callback](Result result, ProducerImplBaseWeakPtr weakProducer) { + if (result == ResultOk) { + weakProducer.lock()->sendAsync(msg, std::move(callback)); + } else if (callback) { + callback(result, {}); + } + }); + } } // override @@ -404,7 +422,7 @@ void PartitionedProducerImpl::flushAsync(FlushCallback callback) { void PartitionedProducerImpl::runPartitionUpdateTask() { auto weakSelf = weak_from_this(); partitionsUpdateTimer_->expires_from_now(partitionsUpdateInterval_); - partitionsUpdateTimer_->async_wait([weakSelf](const boost::system::error_code& ec) { + partitionsUpdateTimer_->async_wait([weakSelf](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (self) { self->getPartitionMetadata(); @@ -445,7 +463,7 @@ void PartitionedProducerImpl::handleGetPartitions(Result result, for (unsigned int i = currentNumPartitions; i < newNumPartitions; i++) { ProducerImplPtr producer; try { - producer = newInternalProducer(i, lazy); + producer = newInternalProducer(i, lazy, true); } catch (const std::runtime_error& e) { LOG_ERROR("Failed to create producer for partition " << i << ": " << e.what()); producers.clear(); @@ -506,7 +524,7 @@ uint64_t PartitionedProducerImpl::getNumberOfConnectedProducer() { void PartitionedProducerImpl::cancelTimers() noexcept { if (partitionsUpdateTimer_) { - boost::system::error_code ec; + ASIO_ERROR ec; partitionsUpdateTimer_->cancel(ec); } } diff --git a/lib/PartitionedProducerImpl.h b/lib/PartitionedProducerImpl.h index 25ba9c33..610c74ed 100644 --- a/lib/PartitionedProducerImpl.h +++ b/lib/PartitionedProducerImpl.h @@ -20,21 +20,21 @@ #include #include -#include #include #include #include +#include "AsioTimer.h" #include "LookupDataResult.h" #include "ProducerImplBase.h" #include "ProducerInterceptors.h" +#include "TimeUtils.h" namespace pulsar { class ClientImpl; using ClientImplPtr = std::shared_ptr; using ClientImplWeakPtr = std::weak_ptr; -using DeadlineTimerPtr = std::shared_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; class LookupService; @@ -128,14 +128,14 @@ class PartitionedProducerImpl : public ProducerImplBase, ExecutorServicePtr listenerExecutor_; DeadlineTimerPtr partitionsUpdateTimer_; - boost::posix_time::time_duration partitionsUpdateInterval_; + TimeDuration partitionsUpdateInterval_; LookupServicePtr lookupServicePtr_; ProducerInterceptorsPtr interceptors_; unsigned int getNumPartitions() const; unsigned int getNumPartitionsWithLock() const; - ProducerImplPtr newInternalProducer(unsigned int partition, bool lazy); + ProducerImplPtr newInternalProducer(unsigned int partition, bool lazy, bool retryOnCreationError); MessageRoutingPolicyPtr getMessageRouter(); void runPartitionUpdateTask(); void getPartitionMetadata(); diff --git a/lib/PatternMultiTopicsConsumerImpl.cc b/lib/PatternMultiTopicsConsumerImpl.cc index e100a1c3..4fc7bb61 100644 --- a/lib/PatternMultiTopicsConsumerImpl.cc +++ b/lib/PatternMultiTopicsConsumerImpl.cc @@ -27,6 +27,8 @@ DECLARE_LOG_OBJECT() using namespace pulsar; +using std::chrono::seconds; + PatternMultiTopicsConsumerImpl::PatternMultiTopicsConsumerImpl( ClientImplPtr client, const std::string pattern, CommandGetTopicsOfNamespace_Mode getTopicsMode, const std::vector& topics, const std::string& subscriptionName, @@ -47,12 +49,17 @@ const PULSAR_REGEX_NAMESPACE::regex PatternMultiTopicsConsumerImpl::getPattern() void PatternMultiTopicsConsumerImpl::resetAutoDiscoveryTimer() { autoDiscoveryRunning_ = false; autoDiscoveryTimer_->expires_from_now(seconds(conf_.getPatternAutoDiscoveryPeriod())); - autoDiscoveryTimer_->async_wait( - std::bind(&PatternMultiTopicsConsumerImpl::autoDiscoveryTimerTask, this, std::placeholders::_1)); + + auto weakSelf = weak_from_this(); + autoDiscoveryTimer_->async_wait([weakSelf](const ASIO_ERROR& err) { + if (auto self = weakSelf.lock()) { + self->autoDiscoveryTimerTask(err); + } + }); } -void PatternMultiTopicsConsumerImpl::autoDiscoveryTimerTask(const boost::system::error_code& err) { - if (err == boost::asio::error::operation_aborted) { +void PatternMultiTopicsConsumerImpl::autoDiscoveryTimerTask(const ASIO_ERROR& err) { + if (err == ASIO::error::operation_aborted) { LOG_DEBUG(getName() << "Timer cancelled: " << err.message()); return; } else if (err) { @@ -222,8 +229,12 @@ void PatternMultiTopicsConsumerImpl::start() { if (conf_.getPatternAutoDiscoveryPeriod() > 0) { autoDiscoveryTimer_->expires_from_now(seconds(conf_.getPatternAutoDiscoveryPeriod())); - autoDiscoveryTimer_->async_wait( - std::bind(&PatternMultiTopicsConsumerImpl::autoDiscoveryTimerTask, this, std::placeholders::_1)); + auto weakSelf = weak_from_this(); + autoDiscoveryTimer_->async_wait([weakSelf](const ASIO_ERROR& err) { + if (auto self = weakSelf.lock()) { + self->autoDiscoveryTimerTask(err); + } + }); } } @@ -238,6 +249,6 @@ void PatternMultiTopicsConsumerImpl::closeAsync(ResultCallback callback) { } void PatternMultiTopicsConsumerImpl::cancelTimers() noexcept { - boost::system::error_code ec; + ASIO_ERROR ec; autoDiscoveryTimer_->cancel(ec); } diff --git a/lib/PatternMultiTopicsConsumerImpl.h b/lib/PatternMultiTopicsConsumerImpl.h index f13750a9..f272df22 100644 --- a/lib/PatternMultiTopicsConsumerImpl.h +++ b/lib/PatternMultiTopicsConsumerImpl.h @@ -22,6 +22,7 @@ #include #include +#include "AsioTimer.h" #include "LookupDataResult.h" #include "MultiTopicsConsumerImpl.h" #include "NamespaceName.h" @@ -56,7 +57,7 @@ class PatternMultiTopicsConsumerImpl : public MultiTopicsConsumerImpl { const PULSAR_REGEX_NAMESPACE::regex getPattern(); - void autoDiscoveryTimerTask(const boost::system::error_code& err); + void autoDiscoveryTimerTask(const ASIO_ERROR& err); // filter input `topics` with given `pattern`, return matched topics. Do not match topic domain. static NamespaceTopicsPtr topicsPatternFilter(const std::vector& topics, @@ -74,7 +75,7 @@ class PatternMultiTopicsConsumerImpl : public MultiTopicsConsumerImpl { const std::string patternString_; const PULSAR_REGEX_NAMESPACE::regex pattern_; const CommandGetTopicsOfNamespace_Mode getTopicsMode_; - typedef std::shared_ptr TimerPtr; + typedef std::shared_ptr TimerPtr; TimerPtr autoDiscoveryTimer_; bool autoDiscoveryRunning_; NamespaceNamePtr namespaceName_; @@ -86,6 +87,10 @@ class PatternMultiTopicsConsumerImpl : public MultiTopicsConsumerImpl { void onTopicsRemoved(NamespaceTopicsPtr removedTopics, ResultCallback callback); void handleOneTopicAdded(const Result result, const std::string& topic, std::shared_ptr> topicsNeedCreate, ResultCallback callback); + + std::weak_ptr weak_from_this() noexcept { + return std::static_pointer_cast(shared_from_this()); + } }; } // namespace pulsar diff --git a/lib/PeriodicTask.cc b/lib/PeriodicTask.cc index 6046eae2..9fde012a 100644 --- a/lib/PeriodicTask.cc +++ b/lib/PeriodicTask.cc @@ -18,6 +18,8 @@ */ #include "PeriodicTask.h" +#include + namespace pulsar { void PeriodicTask::start() { @@ -27,7 +29,7 @@ void PeriodicTask::start() { state_ = Ready; if (periodMs_ >= 0) { std::weak_ptr weakSelf{shared_from_this()}; - timer_->expires_from_now(boost::posix_time::millisec(periodMs_)); + timer_->expires_from_now(std::chrono::milliseconds(periodMs_)); timer_->async_wait([weakSelf](const ErrorCode& ec) { auto self = weakSelf.lock(); if (self) { @@ -48,7 +50,7 @@ void PeriodicTask::stop() noexcept { } void PeriodicTask::handleTimeout(const ErrorCode& ec) { - if (state_ != Ready || ec.value() == boost::system::errc::operation_canceled) { + if (state_ != Ready || ec == ASIO::error::operation_aborted) { return; } @@ -57,7 +59,7 @@ void PeriodicTask::handleTimeout(const ErrorCode& ec) { // state_ may be changed in handleTimeout, so we check state_ again if (state_ == Ready) { auto self = shared_from_this(); - timer_->expires_from_now(boost::posix_time::millisec(periodMs_)); + timer_->expires_from_now(std::chrono::milliseconds(periodMs_)); timer_->async_wait([this, self](const ErrorCode& ec) { handleTimeout(ec); }); } } diff --git a/lib/PeriodicTask.h b/lib/PeriodicTask.h index 5de81ae4..bc186348 100644 --- a/lib/PeriodicTask.h +++ b/lib/PeriodicTask.h @@ -36,7 +36,7 @@ namespace pulsar { */ class PeriodicTask : public std::enable_shared_from_this { public: - using ErrorCode = boost::system::error_code; + using ErrorCode = ASIO_ERROR; using CallbackType = std::function; enum State : std::uint8_t diff --git a/lib/ProducerImpl.cc b/lib/ProducerImpl.cc index a66fbfbd..4399ce5f 100644 --- a/lib/ProducerImpl.cc +++ b/lib/ProducerImpl.cc @@ -20,7 +20,7 @@ #include -#include +#include #include "BatchMessageContainer.h" #include "BatchMessageKeyBasedContainer.h" @@ -46,9 +46,11 @@ namespace pulsar { DECLARE_LOG_OBJECT() +using std::chrono::milliseconds; + ProducerImpl::ProducerImpl(ClientImplPtr client, const TopicName& topicName, const ProducerConfiguration& conf, const ProducerInterceptorsPtr& interceptors, - int32_t partition) + int32_t partition, bool retryOnCreationError) : HandlerBase(client, (partition < 0) ? topicName.toString() : topicName.getTopicPartitionName(partition), Backoff(milliseconds(client->getClientConfig().getInitialBackoffIntervalMs()), milliseconds(client->getClientConfig().getMaxBackoffIntervalMs()), @@ -60,20 +62,17 @@ ProducerImpl::ProducerImpl(ClientImplPtr client, const TopicName& topicName, userProvidedProducerName_(false), producerStr_("[" + topic() + ", " + producerName_ + "] "), producerId_(client->newProducerId()), - msgSequenceGenerator_(0), batchTimer_(executor_->createDeadlineTimer()), + lastSequenceIdPublished_(conf.getInitialSequenceId()), + msgSequenceGenerator_(lastSequenceIdPublished_ + 1), sendTimer_(executor_->createDeadlineTimer()), dataKeyRefreshTask_(*executor_, 4 * 60 * 60 * 1000), memoryLimitController_(client->getMemoryLimitController()), chunkingEnabled_(conf_.isChunkingEnabled() && topicName.isPersistent() && !conf_.getBatchingEnabled()), - interceptors_(interceptors) { + interceptors_(interceptors), + retryOnCreationError_(retryOnCreationError) { LOG_DEBUG("ProducerName - " << producerName_ << " Created producer on topic " << topic() << " id: " << producerId_); - - int64_t initialSequenceId = conf.getInitialSequenceId(); - lastSequenceIdPublished_ = initialSequenceId; - msgSequenceGenerator_ = initialSequenceId + 1; - if (!producerName_.empty()) { userProvidedProducerName_ = true; } @@ -145,8 +144,12 @@ Future ProducerImpl::connectionOpened(const ClientConnectionPtr& c return promise.getFuture(); } + LOG_INFO("Creating producer for topic:" << topic() << ", producerName:" << producerName_ << " on " + << cnx->cnxString()); ClientImplPtr client = client_.lock(); - int requestId = client->newRequestId(); + cnx->registerProducer(producerId_, shared_from_this()); + + long requestId = client->newRequestId(); SharedBuffer cmd = Commands::newProducer(topic(), producerId_, producerName_, requestId, conf_.getProperties(), conf_.getSchema(), epoch_, @@ -156,6 +159,7 @@ Future ProducerImpl::connectionOpened(const ClientConnectionPtr& c // Keep a reference to ensure object is kept alive. auto self = shared_from_this(); + setFirstRequestIdAfterConnect(requestId); cnx->sendRequestWithId(cmd, requestId) .addListener([this, self, cnx, promise](Result result, const ResponseData& responseData) { Result handleResult = handleCreateProducer(cnx, result, responseData); @@ -177,7 +181,7 @@ void ProducerImpl::connectionFailed(Result result) { // if producers are lazy, then they should always try to restart // so don't change the state and allow reconnections return; - } else if (producerCreatedPromise_.setFailed(result)) { + } else if (!isResultRetryable(result) && producerCreatedPromise_.setFailed(result)) { state_ = Failed; } } @@ -215,7 +219,6 @@ Result ProducerImpl::handleCreateProducer(const ClientConnectionPtr& cnx, Result // set the cnx pointer so that new messages will be sent immediately LOG_INFO(getName() << "Created producer on broker " << cnx->cnxString()); - cnx->registerProducer(producerId_, shared_from_this()); producerName_ = responseData.producerName; schemaVersion_ = responseData.schemaVersion; producerStr_ = "[" + topic() + ", " + producerName_ + "] "; @@ -277,7 +280,7 @@ Result ProducerImpl::handleCreateProducer(const ClientConnectionPtr& cnx, Result lock.unlock(); producerCreatedPromise_.setFailed(result); handleResult = result; - } else if (producerCreatedPromise_.isComplete()) { + } else if (producerCreatedPromise_.isComplete() || retryOnCreationError_) { if (result == ResultProducerBlockedQuotaExceededException) { LOG_WARN(getName() << "Backlog is exceeded on topic. Sending exception to producer"); failPendingMessages(ResultProducerBlockedQuotaExceededException, false); @@ -380,29 +383,37 @@ void ProducerImpl::setMessageMetadata(const Message& msg, const uint64_t& sequen void ProducerImpl::flushAsync(FlushCallback callback) { if (state_ != Ready) { - callback(ResultAlreadyClosed); + if (callback) { + callback(ResultAlreadyClosed); + } return; } + + auto addCallbackToLastOp = [this, &callback] { + if (pendingMessagesQueue_.empty()) { + return false; + } + pendingMessagesQueue_.back()->addTrackerCallback(callback); + return true; + }; + if (batchMessageContainer_) { Lock lock(mutex_); - auto failures = batchMessageAndSend(callback); - if (!pendingMessagesQueue_.empty()) { - auto& opSendMsg = pendingMessagesQueue_.back(); - lock.unlock(); - failures.complete(); - opSendMsg->addTrackerCallback(callback); - } else { - lock.unlock(); - failures.complete(); - callback(ResultOk); + + if (batchMessageContainer_->isEmpty()) { + if (!addCallbackToLastOp() && callback) { + lock.unlock(); + callback(ResultOk); + } + return; } + + auto failures = batchMessageAndSend(callback); + lock.unlock(); + failures.complete(); } else { Lock lock(mutex_); - if (!pendingMessagesQueue_.empty()) { - auto& opSendMsg = pendingMessagesQueue_.back(); - lock.unlock(); - opSendMsg->addTrackerCallback(callback); - } else { + if (!addCallbackToLastOp() && callback) { lock.unlock(); callback(ResultOk); } @@ -460,7 +471,7 @@ void ProducerImpl::sendAsync(const Message& msg, SendCallback callback) { Producer producer = Producer(shared_from_this()); auto interceptorMessage = interceptors_->beforeSend(producer, msg); - const auto now = boost::posix_time::microsec_clock::universal_time(); + const auto now = TimeUtils::now(); auto self = shared_from_this(); sendAsyncWithStatsUpdate(interceptorMessage, [this, self, now, callback, producer, interceptorMessage]( Result result, const MessageId& messageId) { @@ -559,10 +570,9 @@ void ProducerImpl::sendAsyncWithStatsUpdate(const Message& msg, SendCallback&& c bool isFirstMessage = batchMessageContainer_->isFirstMessageToAdd(msg); bool isFull = batchMessageContainer_->add(msg, callback); if (isFirstMessage) { - batchTimer_->expires_from_now( - boost::posix_time::milliseconds(conf_.getBatchingMaxPublishDelayMs())); + batchTimer_->expires_from_now(milliseconds(conf_.getBatchingMaxPublishDelayMs())); auto weakSelf = weak_from_this(); - batchTimer_->async_wait([this, weakSelf](const boost::system::error_code& ec) { + batchTimer_->async_wait([this, weakSelf](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (!self) { return; @@ -624,7 +634,6 @@ void ProducerImpl::sendAsyncWithStatsUpdate(const Message& msg, SendCallback&& c const uint32_t msgHeadersAndPayloadSize = msgMetadataSize + payloadSize; if (msgHeadersAndPayloadSize > maxMessageSize) { lock.unlock(); - releaseSemaphoreForSendOp(*op); LOG_WARN(getName() << " - compressed Message size " << msgHeadersAndPayloadSize << " cannot exceed " << maxMessageSize << " bytes unless chunking is enabled"); @@ -819,14 +828,14 @@ Future ProducerImpl::getProducerCreatedFuture() uint64_t ProducerImpl::getProducerId() const { return producerId_; } -void ProducerImpl::handleSendTimeout(const boost::system::error_code& err) { +void ProducerImpl::handleSendTimeout(const ASIO_ERROR& err) { const auto state = state_.load(); if (state != Pending && state != Ready) { return; } Lock lock(mutex_); - if (err == boost::asio::error::operation_aborted) { + if (err == ASIO::error::operation_aborted) { LOG_DEBUG(getName() << "Timer cancelled: " << err.message()); return; } else if (err) { @@ -842,8 +851,8 @@ void ProducerImpl::handleSendTimeout(const boost::system::error_code& err) { } else { // If there is at least one message, calculate the diff between the message timeout and // the current time. - time_duration diff = pendingMessagesQueue_.front()->timeout - TimeUtils::now(); - if (diff.total_milliseconds() <= 0) { + auto diff = pendingMessagesQueue_.front()->timeout - TimeUtils::now(); + if (toMillis(diff) <= 0) { // The diff is less than or equal to zero, meaning that the message has been expired. LOG_DEBUG(getName() << "Timer expired. Calling timeout callbacks."); pendingMessages = getPendingCallbacksWhenFailed(); @@ -851,7 +860,7 @@ void ProducerImpl::handleSendTimeout(const boost::system::error_code& err) { asyncWaitSendTimeout(milliseconds(conf_.getSendTimeout())); } else { // The diff is greater than zero, set the timeout to the diff value - LOG_DEBUG(getName() << "Timer hasn't expired yet, setting new timeout " << diff); + LOG_DEBUG(getName() << "Timer hasn't expired yet, setting new timeout " << diff.count()); asyncWaitSendTimeout(diff); } } @@ -965,12 +974,15 @@ bool ProducerImpl::encryptMessage(proto::MessageMetadata& metadata, SharedBuffer encryptedPayload); } -void ProducerImpl::disconnectProducer() { - LOG_DEBUG("Broker notification of Closed producer: " << producerId_); +void ProducerImpl::disconnectProducer(const boost::optional& assignedBrokerUrl) { + LOG_INFO("Broker notification of Closed producer: " + << producerId_ << (assignedBrokerUrl ? (" assignedBrokerUrl: " + assignedBrokerUrl.get()) : "")); resetCnx(); - scheduleReconnection(); + scheduleReconnection(assignedBrokerUrl); } +void ProducerImpl::disconnectProducer() { disconnectProducer(boost::none); } + void ProducerImpl::start() { HandlerBase::start(); @@ -995,7 +1007,7 @@ void ProducerImpl::shutdown() { void ProducerImpl::cancelTimers() noexcept { dataKeyRefreshTask_.stop(); - boost::system::error_code ec; + ASIO_ERROR ec; batchTimer_->cancel(ec); sendTimer_->cancel(ec); } @@ -1021,7 +1033,7 @@ void ProducerImpl::asyncWaitSendTimeout(DurationType expiryTime) { sendTimer_->expires_from_now(expiryTime); auto weakSelf = weak_from_this(); - sendTimer_->async_wait([weakSelf](const boost::system::error_code& err) { + sendTimer_->async_wait([weakSelf](const ASIO_ERROR& err) { auto self = weakSelf.lock(); if (self) { std::static_pointer_cast(self)->handleSendTimeout(err); diff --git a/lib/ProducerImpl.h b/lib/ProducerImpl.h index 91b95443..f650b1f9 100644 --- a/lib/ProducerImpl.h +++ b/lib/ProducerImpl.h @@ -19,6 +19,13 @@ #ifndef LIB_PRODUCERIMPL_H_ #define LIB_PRODUCERIMPL_H_ +#include "TimeUtils.h" +#ifdef USE_ASIO +#include +#else +#include +#endif +#include #include #include #include @@ -29,6 +36,7 @@ #if defined(_MSC_VER) || defined(__APPLE__) #include "OpSendMsg.h" #endif +#include "AsioDefines.h" #include "PendingFailures.h" #include "PeriodicTask.h" #include "ProducerImplBase.h" @@ -38,7 +46,7 @@ namespace pulsar { class BatchMessageContainerBase; class ClientImpl; using ClientImplPtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; +using DeadlineTimerPtr = std::shared_ptr; class MessageCrypto; using MessageCryptoPtr = std::shared_ptr; class ProducerImpl; @@ -65,7 +73,8 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { public: ProducerImpl(ClientImplPtr client, const TopicName& topic, const ProducerConfiguration& producerConfiguration, - const ProducerInterceptorsPtr& interceptors, int32_t partition = -1); + const ProducerInterceptorsPtr& interceptors, int32_t partition = -1, + bool retryOnCreationError = false); ~ProducerImpl(); // overrided methods from ProducerImplBase @@ -89,6 +98,7 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { bool ackReceived(uint64_t sequenceId, MessageId& messageId); + virtual void disconnectProducer(const boost::optional& assignedBrokerUrl); virtual void disconnectProducer(); uint64_t getProducerId() const; @@ -103,6 +113,8 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { ProducerImplWeakPtr weak_from_this() noexcept { return shared_from_this(); } + bool ready() const { return producerCreatedPromise_.isComplete(); } + protected: ProducerStatsBasePtr producerStatsBasePtr_; @@ -133,7 +145,7 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { void resendMessages(ClientConnectionPtr cnx); - void refreshEncryptionKey(const boost::system::error_code& ec); + void refreshEncryptionKey(const ASIO_ERROR& ec); bool encryptMessage(proto::MessageMetadata& metadata, SharedBuffer& payload, SharedBuffer& encryptedPayload); @@ -169,18 +181,18 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { bool userProvidedProducerName_; std::string producerStr_; uint64_t producerId_; - int64_t msgSequenceGenerator_; std::unique_ptr batchMessageContainer_; DeadlineTimerPtr batchTimer_; PendingFailures batchMessageAndSend(const FlushCallback& flushCallback = nullptr); - volatile int64_t lastSequenceIdPublished_; + std::atomic lastSequenceIdPublished_; + std::atomic msgSequenceGenerator_; std::string schemaVersion_; DeadlineTimerPtr sendTimer_; - void handleSendTimeout(const boost::system::error_code& err); - using DurationType = typename boost::asio::deadline_timer::duration_type; + void handleSendTimeout(const ASIO_ERROR& err); + using DurationType = TimeDuration; void asyncWaitSendTimeout(DurationType expiryTime); Promise producerCreatedPromise_; @@ -199,6 +211,8 @@ class ProducerImpl : public HandlerBase, public ProducerImplBase { boost::optional topicEpoch; ProducerInterceptorsPtr interceptors_; + + bool retryOnCreationError_; }; struct ProducerImplCmp { diff --git a/lib/ResultUtils.h b/lib/ResultUtils.h index b5ec6cdb..dfba7eb0 100644 --- a/lib/ResultUtils.h +++ b/lib/ResultUtils.h @@ -18,12 +18,39 @@ */ #pragma once +#include #include +#include + namespace pulsar { inline bool isResultRetryable(Result result) { - return result == ResultRetryable || result == ResultDisconnected; + assert(result != ResultOk); + if (result == ResultRetryable || result == ResultDisconnected) { + return true; + } + + static const std::unordered_set fatalResults{ResultConnectError, + ResultTimeout, + ResultAuthenticationError, + ResultAuthorizationError, + ResultInvalidUrl, + ResultInvalidConfiguration, + ResultIncompatibleSchema, + ResultTopicNotFound, + ResultOperationNotSupported, + ResultNotAllowedError, + ResultChecksumError, + ResultCryptoError, + ResultConsumerAssignError, + ResultProducerBusy, + ResultConsumerBusy, + ResultLookupError, + ResultTooManyLookupRequestException, + ResultProducerBlockedQuotaExceededException, + ResultProducerBlockedQuotaExceededError}; + return fatalResults.find(static_cast(result)) == fatalResults.cend(); } } // namespace pulsar diff --git a/lib/RetryableLookupService.h b/lib/RetryableLookupService.h index b8e3e0d7..8bc40bf3 100644 --- a/lib/RetryableLookupService.h +++ b/lib/RetryableLookupService.h @@ -18,6 +18,8 @@ */ #pragma once +#include + #include "LookupDataResult.h" #include "LookupService.h" #include "NamespaceName.h" @@ -74,6 +76,10 @@ class RetryableLookupService : public LookupService { }); } + ServiceNameResolver& getServiceNameResolver() override { + return lookupService_->getServiceNameResolver(); + } + private: const std::shared_ptr lookupService_; RetryableOperationCachePtr lookupCache_; @@ -81,15 +87,15 @@ class RetryableLookupService : public LookupService { RetryableOperationCachePtr namespaceLookupCache_; RetryableOperationCachePtr getSchemaCache_; - RetryableLookupService(std::shared_ptr lookupService, int timeoutSeconds, + RetryableLookupService(std::shared_ptr lookupService, TimeDuration timeout, ExecutorServiceProviderPtr executorProvider) : lookupService_(lookupService), - lookupCache_(RetryableOperationCache::create(executorProvider, timeoutSeconds)), + lookupCache_(RetryableOperationCache::create(executorProvider, timeout)), partitionLookupCache_( - RetryableOperationCache::create(executorProvider, timeoutSeconds)), + RetryableOperationCache::create(executorProvider, timeout)), namespaceLookupCache_( - RetryableOperationCache::create(executorProvider, timeoutSeconds)), - getSchemaCache_(RetryableOperationCache::create(executorProvider, timeoutSeconds)) {} + RetryableOperationCache::create(executorProvider, timeout)), + getSchemaCache_(RetryableOperationCache::create(executorProvider, timeout)) {} }; } // namespace pulsar diff --git a/lib/RetryableOperation.h b/lib/RetryableOperation.h index d026e424..dba190f4 100644 --- a/lib/RetryableOperation.h +++ b/lib/RetryableOperation.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ #include "Future.h" #include "LogUtils.h" #include "ResultUtils.h" +#include "TimeUtils.h" namespace pulsar { @@ -39,13 +41,12 @@ class RetryableOperation : public std::enable_shared_from_this()>&& func, int timeoutSeconds, - DeadlineTimerPtr timer) + RetryableOperation(const std::string& name, std::function()>&& func, + TimeDuration timeout, DeadlineTimerPtr timer) : name_(name), func_(std::move(func)), - timeout_(boost::posix_time::seconds(timeoutSeconds)), - backoff_(boost::posix_time::milliseconds(100), timeout_ + timeout_, - boost::posix_time::milliseconds(0)), + timeout_(timeout), + backoff_(std::chrono::milliseconds(100), timeout_ + timeout_, std::chrono::milliseconds(0)), timer_(timer) {} public: @@ -67,7 +68,7 @@ class RetryableOperation : public std::enable_shared_from_thiscancel(ec); } @@ -100,7 +101,7 @@ class RetryableOperation : public std::enable_shared_from_thisexpires_from_now(delay); auto nextRemainingTime = remainingTime - delay; - LOG_INFO("Reschedule " << name_ << " for " << delay.total_milliseconds() - << " ms, remaining time: " << nextRemainingTime.total_milliseconds() - << " ms"); - timer_->async_wait([this, weakSelf, nextRemainingTime](const boost::system::error_code& ec) { + LOG_INFO("Reschedule " << name_ << " for " << toMillis(delay) + << " ms, remaining time: " << toMillis(nextRemainingTime) << " ms"); + timer_->async_wait([this, weakSelf, nextRemainingTime](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (!self) { return; } if (ec) { - if (ec == boost::asio::error::operation_aborted) { + if (ec == ASIO::error::operation_aborted) { LOG_DEBUG("Timer for " << name_ << " is cancelled"); promise_.setFailed(ResultTimeout); } else { LOG_WARN("Timer for " << name_ << " failed: " << ec.message()); } } else { - LOG_DEBUG("Run operation " << name_ << ", remaining time: " - << nextRemainingTime.total_milliseconds() << " ms"); + LOG_DEBUG("Run operation " << name_ << ", remaining time: " << toMillis(nextRemainingTime) + << " ms"); runImpl(nextRemainingTime); } }); diff --git a/lib/RetryableOperationCache.h b/lib/RetryableOperationCache.h index 70fa9140..e42460dd 100644 --- a/lib/RetryableOperationCache.h +++ b/lib/RetryableOperationCache.h @@ -18,6 +18,7 @@ */ #pragma once +#include #include #include @@ -40,8 +41,8 @@ class RetryableOperationCache : public std::enable_shared_from_this; @@ -69,7 +70,7 @@ class RetryableOperationCache : public std::enable_shared_from_this::create(key, std::move(func), timeoutSeconds_, timer); + auto operation = RetryableOperation::create(key, std::move(func), timeout_, timer); auto future = operation->run(); operations_[key] = operation; lock.unlock(); @@ -106,7 +107,7 @@ class RetryableOperationCache : public std::enable_shared_from_this>> operations_; mutable std::mutex mutex_; diff --git a/lib/RoundRobinMessageRouter.cc b/lib/RoundRobinMessageRouter.cc index e33f8d95..1bc0b306 100644 --- a/lib/RoundRobinMessageRouter.cc +++ b/lib/RoundRobinMessageRouter.cc @@ -18,8 +18,7 @@ */ #include "RoundRobinMessageRouter.h" -#include -#include +#include #include "Hash.h" #include "TimeUtils.h" @@ -27,8 +26,7 @@ namespace pulsar { RoundRobinMessageRouter::RoundRobinMessageRouter(ProducerConfiguration::HashingScheme hashingScheme, bool batchingEnabled, uint32_t maxBatchingMessages, - uint32_t maxBatchingSize, - boost::posix_time::time_duration maxBatchingDelay) + uint32_t maxBatchingSize, TimeDuration maxBatchingDelay) : MessageRouterBase(hashingScheme), batchingEnabled_(batchingEnabled), maxBatchingMessages_(maxBatchingMessages), @@ -37,8 +35,8 @@ RoundRobinMessageRouter::RoundRobinMessageRouter(ProducerConfiguration::HashingS lastPartitionChange_(TimeUtils::currentTimeMillis()), msgCounter_(0), cumulativeBatchSize_(0) { - boost::random::mt19937 rng(time(nullptr)); - boost::random::uniform_int_distribution dist; + std::mt19937 rng(time(nullptr)); + std::uniform_int_distribution dist; currentPartitionCursor_ = dist(rng); } @@ -75,7 +73,7 @@ int RoundRobinMessageRouter::getPartition(const Message& msg, const TopicMetadat int64_t now = TimeUtils::currentTimeMillis(); if (messageCount >= maxBatchingMessages_ || (messageSize >= maxBatchingSize_ - batchSize) || - (now - lastPartitionChange >= maxBatchingDelay_.total_milliseconds())) { + (now - lastPartitionChange >= toMillis(maxBatchingDelay_))) { uint32_t currentPartitionCursor = ++currentPartitionCursor_; lastPartitionChange_ = now; cumulativeBatchSize_ = messageSize; diff --git a/lib/RoundRobinMessageRouter.h b/lib/RoundRobinMessageRouter.h index 753573a1..03cfac80 100644 --- a/lib/RoundRobinMessageRouter.h +++ b/lib/RoundRobinMessageRouter.h @@ -23,16 +23,16 @@ #include #include -#include #include "MessageRouterBase.h" +#include "TimeUtils.h" namespace pulsar { class PULSAR_PUBLIC RoundRobinMessageRouter : public MessageRouterBase { public: RoundRobinMessageRouter(ProducerConfiguration::HashingScheme hashingScheme, bool batchingEnabled, uint32_t maxBatchingMessages, uint32_t maxBatchingSize, - boost::posix_time::time_duration maxBatchingDelay); + TimeDuration maxBatchingDelay); virtual ~RoundRobinMessageRouter(); virtual int getPartition(const Message& msg, const TopicMetadata& topicMetadata); @@ -40,7 +40,7 @@ class PULSAR_PUBLIC RoundRobinMessageRouter : public MessageRouterBase { const bool batchingEnabled_; const uint32_t maxBatchingMessages_; const uint32_t maxBatchingSize_; - const boost::posix_time::time_duration maxBatchingDelay_; + const TimeDuration maxBatchingDelay_; std::atomic currentPartitionCursor_; std::atomic lastPartitionChange_; diff --git a/lib/ServiceNameResolver.h b/lib/ServiceNameResolver.h index 8457d0e1..e6b2523b 100644 --- a/lib/ServiceNameResolver.h +++ b/lib/ServiceNameResolver.h @@ -36,14 +36,17 @@ class ServiceNameResolver { ServiceNameResolver(const ServiceNameResolver&) = delete; ServiceNameResolver& operator=(const ServiceNameResolver&) = delete; - bool useTls() const noexcept { - return serviceUri_.getScheme() == PulsarScheme::PULSAR_SSL || - serviceUri_.getScheme() == PulsarScheme::HTTPS; + bool useTls() const noexcept { return useTls(serviceUri_); } + + static bool useTls(const ServiceURI& serviceUri) noexcept { + return serviceUri.getScheme() == PulsarScheme::PULSAR_SSL || + serviceUri.getScheme() == PulsarScheme::HTTPS; } - bool useHttp() const noexcept { - return serviceUri_.getScheme() == PulsarScheme::HTTP || - serviceUri_.getScheme() == PulsarScheme::HTTPS; + bool useHttp() const noexcept { return useTls(serviceUri_); } + + static bool useHttp(const ServiceURI& serviceUri) noexcept { + return serviceUri.getScheme() == PulsarScheme::HTTP || serviceUri.getScheme() == PulsarScheme::HTTPS; } const std::string& resolveHost() { diff --git a/lib/SharedBuffer.h b/lib/SharedBuffer.h index 7ee26184..26fc59ed 100644 --- a/lib/SharedBuffer.h +++ b/lib/SharedBuffer.h @@ -22,12 +22,19 @@ #include #include +#ifdef USE_ASIO +#include +#include +#else #include #include +#endif #include #include #include +#include "AsioDefines.h" + namespace pulsar { class SharedBuffer { @@ -144,13 +151,13 @@ class SharedBuffer { inline bool writable() const { return writableBytes() > 0; } - boost::asio::const_buffers_1 const_asio_buffer() const { - return boost::asio::const_buffers_1(ptr_ + readIdx_, readableBytes()); + ASIO::const_buffers_1 const_asio_buffer() const { + return ASIO::const_buffers_1(ptr_ + readIdx_, readableBytes()); } - boost::asio::mutable_buffers_1 asio_buffer() { + ASIO::mutable_buffers_1 asio_buffer() { assert(data_); - return boost::asio::buffer(ptr_ + writeIdx_, writableBytes()); + return ASIO::buffer(ptr_ + writeIdx_, writableBytes()); } void write(const char* data, uint32_t size) { @@ -239,17 +246,17 @@ class CompositeSharedBuffer { } // Implement the ConstBufferSequence requirements. - typedef boost::asio::const_buffer value_type; - typedef boost::asio::const_buffer* iterator; - typedef const boost::asio::const_buffer* const_iterator; + typedef ASIO::const_buffer value_type; + typedef ASIO::const_buffer* iterator; + typedef const ASIO::const_buffer* const_iterator; - const boost::asio::const_buffer* begin() const { return &(asioBuffers_.at(0)); } + const ASIO::const_buffer* begin() const { return &(asioBuffers_.at(0)); } - const boost::asio::const_buffer* end() const { return begin() + Size; } + const ASIO::const_buffer* end() const { return begin() + Size; } private: std::array sharedBuffers_; - std::array asioBuffers_; + std::array asioBuffers_; }; typedef CompositeSharedBuffer<2> PairSharedBuffer; diff --git a/lib/Synchronized.h b/lib/Synchronized.h index a98c08da..5449a9fe 100644 --- a/lib/Synchronized.h +++ b/lib/Synchronized.h @@ -30,6 +30,11 @@ class Synchronized { return value_; } + T&& release() { + std::lock_guard lock(mutex_); + return std::move(value_); + } + Synchronized& operator=(const T& value) { std::lock_guard lock(mutex_); value_ = value; diff --git a/lib/TimeUtils.h b/lib/TimeUtils.h index a55773d9..53f537a2 100644 --- a/lib/TimeUtils.h +++ b/lib/TimeUtils.h @@ -21,19 +21,24 @@ #include #include -#include #include namespace pulsar { -using namespace boost::posix_time; -using boost::posix_time::milliseconds; -using boost::posix_time::seconds; +using ptime = decltype(std::chrono::high_resolution_clock::now()); +using TimeDuration = std::chrono::nanoseconds; + +inline decltype(std::chrono::milliseconds(0).count()) toMillis(TimeDuration duration) { + return std::chrono::duration_cast(duration).count(); +} class PULSAR_PUBLIC TimeUtils { public: - static ptime now(); - static int64_t currentTimeMillis(); + static ptime now() { return std::chrono::high_resolution_clock::now(); } + + static int64_t currentTimeMillis() { + return toMillis(std::chrono::system_clock::now().time_since_epoch()); + } }; // This class processes a timeout with the following semantics: diff --git a/lib/TopicName.cc b/lib/TopicName.cc index 5b892fc8..487eee53 100644 --- a/lib/TopicName.cc +++ b/lib/TopicName.cc @@ -164,7 +164,7 @@ std::string TopicName::getLocalName() { return localName_; } std::string TopicName::getEncodedLocalName() const { return getEncodedName(localName_); } -bool TopicName::operator==(const TopicName& other) { +bool TopicName::operator==(const TopicName& other) const { return (this->topicName_.compare(other.topicName_) == 0); } diff --git a/lib/TopicName.h b/lib/TopicName.h index 8cc9cb53..bee8138a 100644 --- a/lib/TopicName.h +++ b/lib/TopicName.h @@ -65,7 +65,7 @@ class PULSAR_PUBLIC TopicName : public ServiceUnitId { NamespaceNamePtr getNamespaceName(); int getPartitionIndex() const noexcept { return partition_; } static std::shared_ptr get(const std::string& topicName); - bool operator==(const TopicName& other); + bool operator==(const TopicName& other) const; static std::string getEncodedName(const std::string& nameBeforeEncoding); static std::string removeDomain(const std::string& topicName); static bool containsDomain(const std::string& topicName); diff --git a/lib/UnAckedMessageTrackerEnabled.cc b/lib/UnAckedMessageTrackerEnabled.cc index ff1b928f..e371af99 100644 --- a/lib/UnAckedMessageTrackerEnabled.cc +++ b/lib/UnAckedMessageTrackerEnabled.cc @@ -34,12 +34,12 @@ void UnAckedMessageTrackerEnabled::timeoutHandler() { timeoutHandlerHelper(); ExecutorServicePtr executorService = client_->getIOExecutorProvider()->get(); timer_ = executorService->createDeadlineTimer(); - timer_->expires_from_now(boost::posix_time::milliseconds(tickDurationInMs_)); - timer_->async_wait([&](const boost::system::error_code& ec) { - if (ec) { - LOG_DEBUG("Ignoring timer cancelled event, code[" << ec << "]"); - } else { - timeoutHandler(); + timer_->expires_from_now(std::chrono::milliseconds(tickDurationInMs_)); + std::weak_ptr weakSelf{shared_from_this()}; + timer_->async_wait([weakSelf](const ASIO_ERROR& ec) { + auto self = weakSelf.lock(); + if (self && !ec) { + self->timeoutHandler(); } }); } @@ -91,10 +91,10 @@ UnAckedMessageTrackerEnabled::UnAckedMessageTrackerEnabled(long timeoutMs, long std::set msgIds; timePartitions.push_back(msgIds); } - - timeoutHandler(); } +void UnAckedMessageTrackerEnabled::start() { timeoutHandler(); } + bool UnAckedMessageTrackerEnabled::add(const MessageId& msgId) { std::lock_guard acquire(lock_); auto id = discardBatch(msgId); @@ -172,9 +172,10 @@ void UnAckedMessageTrackerEnabled::clear() { } } -UnAckedMessageTrackerEnabled::~UnAckedMessageTrackerEnabled() { +void UnAckedMessageTrackerEnabled::stop() { + ASIO_ERROR ec; if (timer_) { - timer_->cancel(); + timer_->cancel(ec); } } } /* namespace pulsar */ diff --git a/lib/UnAckedMessageTrackerEnabled.h b/lib/UnAckedMessageTrackerEnabled.h index 1453460c..83edc4cb 100644 --- a/lib/UnAckedMessageTrackerEnabled.h +++ b/lib/UnAckedMessageTrackerEnabled.h @@ -18,12 +18,13 @@ */ #ifndef LIB_UNACKEDMESSAGETRACKERENABLED_H_ #define LIB_UNACKEDMESSAGETRACKERENABLED_H_ -#include #include #include +#include #include #include +#include "AsioTimer.h" #include "TestUtil.h" #include "UnAckedMessageTrackerInterface.h" @@ -32,21 +33,22 @@ namespace pulsar { class ClientImpl; class ConsumerImplBase; using ClientImplPtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; -class UnAckedMessageTrackerEnabled : public UnAckedMessageTrackerInterface { +class UnAckedMessageTrackerEnabled : public std::enable_shared_from_this, + public UnAckedMessageTrackerInterface { public: - ~UnAckedMessageTrackerEnabled(); UnAckedMessageTrackerEnabled(long timeoutMs, ClientImplPtr, ConsumerImplBase&); UnAckedMessageTrackerEnabled(long timeoutMs, long tickDuration, ClientImplPtr, ConsumerImplBase&); - bool add(const MessageId& msgId); - bool remove(const MessageId& msgId); - void remove(const MessageIdList& msgIds); - void removeMessagesTill(const MessageId& msgId); - void removeTopicMessage(const std::string& topic); + void start() override; + void stop() override; + bool add(const MessageId& msgId) override; + bool remove(const MessageId& msgId) override; + void remove(const MessageIdList& msgIds) override; + void removeMessagesTill(const MessageId& msgId) override; + void removeTopicMessage(const std::string& topic) override; void timeoutHandler(); - void clear(); + void clear() override; protected: void timeoutHandlerHelper(); diff --git a/lib/UnAckedMessageTrackerInterface.h b/lib/UnAckedMessageTrackerInterface.h index d1fe7893..4df8819e 100644 --- a/lib/UnAckedMessageTrackerInterface.h +++ b/lib/UnAckedMessageTrackerInterface.h @@ -28,6 +28,8 @@ class UnAckedMessageTrackerInterface { public: virtual ~UnAckedMessageTrackerInterface() {} UnAckedMessageTrackerInterface() {} + virtual void start() {} + virtual void stop() {} virtual bool add(const MessageId& m) = 0; virtual bool remove(const MessageId& m) = 0; virtual void remove(const MessageIdList& msgIds) = 0; diff --git a/lib/auth/AuthOauth2.cc b/lib/auth/AuthOauth2.cc index 799843bc..c0745570 100644 --- a/lib/auth/AuthOauth2.cc +++ b/lib/auth/AuthOauth2.cc @@ -342,8 +342,13 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() { CurlWrapper::Options options; options.postFields = std::move(postData); - auto result = - curl.get(tokenEndPoint_, "Content-Type: application/x-www-form-urlencoded", options, nullptr); + std::unique_ptr tlsContext; + if (!tlsTrustCertsFilePath_.empty()) { + tlsContext.reset(new CurlWrapper::TlsContext); + tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_; + } + auto result = curl.get(tokenEndPoint_, "Content-Type: application/x-www-form-urlencoded", options, + tlsContext.get()); if (!result.error.empty()) { LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_ << ": " << result.error); return resultPtr; diff --git a/lib/auth/athenz/ZTSClient.cc b/lib/auth/athenz/ZTSClient.cc index 230713ea..35387d96 100644 --- a/lib/auth/athenz/ZTSClient.cc +++ b/lib/auth/athenz/ZTSClient.cc @@ -44,8 +44,6 @@ namespace ptree = boost::property_tree; #pragma clang diagnostic ignored "-Wunknown-warning-option" #endif -#include - #if defined(__clang__) #pragma clang diagnostic pop #endif diff --git a/lib/c/c_ClientConfiguration.cc b/lib/c/c_ClientConfiguration.cc index ef19f3ac..96a1bf7b 100644 --- a/lib/c/c_ClientConfiguration.cc +++ b/lib/c/c_ClientConfiguration.cc @@ -189,3 +189,12 @@ void pulsar_client_configuration_set_memory_limit(pulsar_client_configuration_t unsigned long long pulsar_client_configuration_get_memory_limit(pulsar_client_configuration_t *conf) { return conf->conf.getMemoryLimit(); } + +void pulsar_client_configuration_set_listener_name(pulsar_client_configuration_t *conf, + const char *listenerName) { + conf->conf.setListenerName(listenerName); +} + +const char *pulsar_client_configuration_get_listener_name(pulsar_client_configuration_t *conf) { + return conf->conf.getListenerName().c_str(); +} diff --git a/lib/lz4/lz4.cc b/lib/lz4/lz4.cc index d63b977a..2f98fb38 100644 --- a/lib/lz4/lz4.cc +++ b/lib/lz4/lz4.cc @@ -1175,9 +1175,9 @@ FORCE_INLINE int LZ4_decompress_generic( s = *ip++; length += s; } while (likely((endOnInput) ? ip < iend - RUN_MASK : 1) && (s == 255)); - if ((safeDecode) && unlikely((size_t)(op + length) < (size_t)(op))) + if ((safeDecode) && unlikely(length >= (size_t)(oend - op))) goto _output_error; /* overflow detection */ - if ((safeDecode) && unlikely((size_t)(ip + length) < (size_t)(ip))) + if ((safeDecode) && unlikely(length >= (size_t)(iend - ip))) goto _output_error; /* overflow detection */ } @@ -1220,7 +1220,7 @@ FORCE_INLINE int LZ4_decompress_generic( s = *ip++; length += s; } while (s == 255); - if ((safeDecode) && unlikely((size_t)(op + length) < (size_t)op)) + if ((safeDecode) && unlikely(length >= (size_t)(oend - op))) goto _output_error; /* overflow detection */ } length += MINMATCH; diff --git a/lib/stats/ConsumerStatsBase.h b/lib/stats/ConsumerStatsBase.h index 6e2e71b8..e8e17c78 100644 --- a/lib/stats/ConsumerStatsBase.h +++ b/lib/stats/ConsumerStatsBase.h @@ -28,6 +28,7 @@ namespace pulsar { class ConsumerStatsBase { public: virtual void start() {} + virtual void stop() {} virtual void receivedMessage(Message&, Result) = 0; virtual void messageAcknowledged(Result, CommandAck_AckType, uint32_t ackNums = 1) = 0; virtual ~ConsumerStatsBase() {} diff --git a/lib/stats/ConsumerStatsImpl.cc b/lib/stats/ConsumerStatsImpl.cc index 056dbf6e..0eefabdc 100644 --- a/lib/stats/ConsumerStatsImpl.cc +++ b/lib/stats/ConsumerStatsImpl.cc @@ -46,7 +46,7 @@ ConsumerStatsImpl::ConsumerStatsImpl(const ConsumerStatsImpl& stats) totalAckedMsgMap_(stats.totalAckedMsgMap_), statsIntervalInSeconds_(stats.statsIntervalInSeconds_) {} -void ConsumerStatsImpl::flushAndReset(const boost::system::error_code& ec) { +void ConsumerStatsImpl::flushAndReset(const ASIO_ERROR& ec) { if (ec) { LOG_DEBUG("Ignoring timer cancelled event, code[" << ec << "]"); return; @@ -85,9 +85,9 @@ void ConsumerStatsImpl::messageAcknowledged(Result res, CommandAck_AckType ackTy } void ConsumerStatsImpl::scheduleTimer() { - timer_->expires_from_now(boost::posix_time::seconds(statsIntervalInSeconds_)); + timer_->expires_from_now(std::chrono::seconds(statsIntervalInSeconds_)); std::weak_ptr weakSelf{shared_from_this()}; - timer_->async_wait([this, weakSelf](const boost::system::error_code& ec) { + timer_->async_wait([this, weakSelf](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (!self) { return; diff --git a/lib/stats/ConsumerStatsImpl.h b/lib/stats/ConsumerStatsImpl.h index 44f927f5..3333ea85 100644 --- a/lib/stats/ConsumerStatsImpl.h +++ b/lib/stats/ConsumerStatsImpl.h @@ -20,17 +20,16 @@ #ifndef PULSAR_CONSUMER_STATS_IMPL_H_ #define PULSAR_CONSUMER_STATS_IMPL_H_ -#include #include #include #include #include #include "ConsumerStatsBase.h" +#include "lib/AsioTimer.h" #include "lib/ExecutorService.h" namespace pulsar { -using DeadlineTimerPtr = std::shared_ptr; class ExecutorService; using ExecutorServicePtr = std::shared_ptr; @@ -58,8 +57,12 @@ class ConsumerStatsImpl : public std::enable_shared_from_this public: ConsumerStatsImpl(std::string, ExecutorServicePtr, unsigned int); ConsumerStatsImpl(const ConsumerStatsImpl& stats); - void flushAndReset(const boost::system::error_code&); + void flushAndReset(const ASIO_ERROR&); void start() override; + void stop() override { + ASIO_ERROR error; + timer_->cancel(error); + } void receivedMessage(Message&, Result) override; void messageAcknowledged(Result, CommandAck_AckType, uint32_t ackNums) override; virtual ~ConsumerStatsImpl(); diff --git a/lib/stats/ProducerStatsBase.h b/lib/stats/ProducerStatsBase.h index fe0ba0a5..b24266e8 100644 --- a/lib/stats/ProducerStatsBase.h +++ b/lib/stats/ProducerStatsBase.h @@ -22,14 +22,14 @@ #include #include -#include +#include "lib/TimeUtils.h" namespace pulsar { class ProducerStatsBase { public: virtual void start() {} virtual void messageSent(const Message& msg) = 0; - virtual void messageReceived(Result, const boost::posix_time::ptime&) = 0; + virtual void messageReceived(Result, const ptime&) = 0; virtual ~ProducerStatsBase(){}; }; diff --git a/lib/stats/ProducerStatsDisabled.h b/lib/stats/ProducerStatsDisabled.h index df1df0f8..df1da783 100644 --- a/lib/stats/ProducerStatsDisabled.h +++ b/lib/stats/ProducerStatsDisabled.h @@ -25,7 +25,7 @@ namespace pulsar { class ProducerStatsDisabled : public ProducerStatsBase { public: virtual void messageSent(const Message& msg){}; - virtual void messageReceived(Result, const boost::posix_time::ptime&){}; + virtual void messageReceived(Result, const ptime&){}; }; } // namespace pulsar #endif // PULSAR_PRODUCER_STATS_DISABLED_HEADER diff --git a/lib/stats/ProducerStatsImpl.cc b/lib/stats/ProducerStatsImpl.cc index 3d3629db..15e9e67e 100644 --- a/lib/stats/ProducerStatsImpl.cc +++ b/lib/stats/ProducerStatsImpl.cc @@ -20,9 +20,11 @@ #include "ProducerStatsImpl.h" #include +#include #include "lib/ExecutorService.h" #include "lib/LogUtils.h" +#include "lib/TimeUtils.h" #include "lib/Utils.h" namespace pulsar { @@ -65,7 +67,7 @@ ProducerStatsImpl::ProducerStatsImpl(const ProducerStatsImpl& stats) void ProducerStatsImpl::start() { scheduleTimer(); } -void ProducerStatsImpl::flushAndReset(const boost::system::error_code& ec) { +void ProducerStatsImpl::flushAndReset(const ASIO_ERROR& ec) { if (ec) { LOG_DEBUG("Ignoring timer cancelled event, code[" << ec << "]"); return; @@ -93,9 +95,10 @@ void ProducerStatsImpl::messageSent(const Message& msg) { totalBytesSent_ += msg.getLength(); } -void ProducerStatsImpl::messageReceived(Result res, const boost::posix_time::ptime& publishTime) { - boost::posix_time::ptime currentTime = boost::posix_time::microsec_clock::universal_time(); - double diffInMicros = (currentTime - publishTime).total_microseconds(); +void ProducerStatsImpl::messageReceived(Result res, const ptime& publishTime) { + auto currentTime = TimeUtils::now(); + double diffInMicros = + std::chrono::duration_cast(currentTime - publishTime).count(); std::lock_guard lock(mutex_); totalLatencyAccumulator_(diffInMicros); latencyAccumulator_(diffInMicros); @@ -106,9 +109,9 @@ void ProducerStatsImpl::messageReceived(Result res, const boost::posix_time::pti ProducerStatsImpl::~ProducerStatsImpl() { timer_->cancel(); } void ProducerStatsImpl::scheduleTimer() { - timer_->expires_from_now(boost::posix_time::seconds(statsIntervalInSeconds_)); + timer_->expires_from_now(std::chrono::seconds(statsIntervalInSeconds_)); std::weak_ptr weakSelf{shared_from_this()}; - timer_->async_wait([this, weakSelf](const boost::system::error_code& ec) { + timer_->async_wait([this, weakSelf](const ASIO_ERROR& ec) { auto self = weakSelf.lock(); if (!self) { return; diff --git a/lib/stats/ProducerStatsImpl.h b/lib/stats/ProducerStatsImpl.h index 8cd10992..5d445c60 100644 --- a/lib/stats/ProducerStatsImpl.h +++ b/lib/stats/ProducerStatsImpl.h @@ -30,20 +30,18 @@ #include #include #include -#include -#include #include #include #include #include #include "ProducerStatsBase.h" +#include "lib/AsioTimer.h" namespace pulsar { class ExecutorService; using ExecutorServicePtr = std::shared_ptr; -using DeadlineTimerPtr = std::shared_ptr; typedef boost::accumulators::accumulator_set< double, @@ -83,11 +81,11 @@ class ProducerStatsImpl : public std::enable_shared_from_this void start() override; - void flushAndReset(const boost::system::error_code&); + void flushAndReset(const ASIO_ERROR&); void messageSent(const Message&) override; - void messageReceived(Result, const boost::posix_time::ptime&) override; + void messageReceived(Result, const ptime&) override; ~ProducerStatsImpl(); diff --git a/perf/BuildPerf.cmake b/perf/BuildPerf.cmake new file mode 100644 index 00000000..8b5bc6f7 --- /dev/null +++ b/perf/BuildPerf.cmake @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +add_executable(perfProducer PerfProducer.cc) +target_link_libraries(perfProducer pulsarShared Boost::program_options) + +add_executable(perfConsumer PerfConsumer.cc) +target_link_libraries(perfConsumer pulsarShared Boost::program_options) diff --git a/perf/CMakeLists.txt b/perf/CMakeLists.txt index 586f9b0f..74215c7d 100644 --- a/perf/CMakeLists.txt +++ b/perf/CMakeLists.txt @@ -17,21 +17,8 @@ # under the License. # -# Test tools -add_definitions(-D_GLIBCXX_USE_NANOSLEEP) - -set(PERF_PRODUCER_SOURCES - PerfProducer.cc -) - -set(PERF_CONSUMER_SOURCES - PerfConsumer.cc -) - -add_executable(perfProducer ${PERF_PRODUCER_SOURCES}) -add_executable(perfConsumer ${PERF_CONSUMER_SOURCES}) - -set(TOOL_LIBS ${CLIENT_LIBS} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY}) - -target_link_libraries(perfProducer pulsarShared ${TOOL_LIBS}) -target_link_libraries(perfConsumer pulsarShared ${TOOL_LIBS}) +if (INTEGRATE_VCPKG) + include(BuildPerf.cmake) +else () + include(LegacyBuildPerf.cmake) +endif () diff --git a/perf/LegacyBuildPerf.cmake b/perf/LegacyBuildPerf.cmake new file mode 100644 index 00000000..586f9b0f --- /dev/null +++ b/perf/LegacyBuildPerf.cmake @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Test tools +add_definitions(-D_GLIBCXX_USE_NANOSLEEP) + +set(PERF_PRODUCER_SOURCES + PerfProducer.cc +) + +set(PERF_CONSUMER_SOURCES + PerfConsumer.cc +) + +add_executable(perfProducer ${PERF_PRODUCER_SOURCES}) +add_executable(perfConsumer ${PERF_CONSUMER_SOURCES}) + +set(TOOL_LIBS ${CLIENT_LIBS} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY}) + +target_link_libraries(perfProducer pulsarShared ${TOOL_LIBS}) +target_link_libraries(perfConsumer pulsarShared ${TOOL_LIBS}) diff --git a/perf/PerfProducer.cc b/perf/PerfProducer.cc index aeda8e84..cbfef688 100644 --- a/perf/PerfProducer.cc +++ b/perf/PerfProducer.cc @@ -160,7 +160,7 @@ void startPerfProducer(const Arguments& args, pulsar::ProducerConfiguration& pro limiter = std::make_shared(args.rate); } - producerList.resize(args.numTopics * args.numProducers); + producerList.resize((size_t)args.numTopics * args.numProducers); for (int i = 0; i < args.numTopics; i++) { std::string topic = (args.numTopics == 1) ? args.topic : args.topic + "-" + std::to_string(i); LOG_INFO("Adding " << args.numProducers << " producers on topic " << topic); diff --git a/pkg/apk/Dockerfile b/pkg/apk/Dockerfile index 182c7d78..d7d87181 100644 --- a/pkg/apk/Dockerfile +++ b/pkg/apk/Dockerfile @@ -43,13 +43,12 @@ ADD .build/dep-version.py /usr/local/bin # Download and compile boost RUN BOOST_VERSION=$(dep-version.py boost) && \ - BOOST_VERSION_UNDESRSCORE=$(echo $BOOST_VERSION | sed 's/\./_/g') && \ - curl -O -L https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION}/source/boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz && \ - tar xfz boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz && \ - cd boost_${BOOST_VERSION_UNDESRSCORE} && \ + curl -O -L https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}.tar.gz && \ + tar zxf boost-${BOOST_VERSION}.tar.gz && \ + cd boost-${BOOST_VERSION} && \ ./bootstrap.sh --with-libraries=regex && \ ./b2 -d0 address-model=64 cxxflags=-fPIC link=static threading=multi variant=release install && \ - rm -rf /boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz /boost_${BOOST_VERSION_UNDESRSCORE} + rm -rf /boost-${BOOST_VERSION}.tar.gz /boost-${BOOST_VERSION} # Download and compile protobuf RUN PROTOBUF_VERSION=$(dep-version.py protobuf) && \ @@ -105,7 +104,7 @@ RUN CURL_VERSION=$(dep-version.py curl) && \ curl -O -L https://github.com/curl/curl/releases/download/curl-${CURL_VERSION_UNDERSCORE}/curl-${CURL_VERSION}.tar.gz && \ tar xfz curl-${CURL_VERSION}.tar.gz && \ cd curl-${CURL_VERSION} && \ - CFLAGS=-fPIC ./configure --with-ssl=/usr/local/ssl/ --without-zstd && \ + CFLAGS=-fPIC ./configure --with-ssl=/usr/local/ssl/ --without-zstd --without-libpsl && \ make -j8 && make install && \ rm -rf /curl-${CURL_VERSION}.tar.gz /curl-${CURL_VERSION} diff --git a/pkg/deb/Dockerfile b/pkg/deb/Dockerfile index aa22a805..502b0934 100644 --- a/pkg/deb/Dockerfile +++ b/pkg/deb/Dockerfile @@ -41,13 +41,12 @@ ADD .build/dep-version.py /usr/local/bin # Download and compile boost RUN BOOST_VERSION=$(dep-version.py boost) && \ - BOOST_VERSION_UNDESRSCORE=$(echo $BOOST_VERSION | sed 's/\./_/g') && \ - curl -O -L https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION}/source/boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz && \ - tar xfz boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz && \ - cd boost_${BOOST_VERSION_UNDESRSCORE} && \ + curl -O -L https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}.tar.gz && \ + tar zxf boost-${BOOST_VERSION}.tar.gz && \ + cd boost-${BOOST_VERSION} && \ ./bootstrap.sh --with-libraries=regex && \ ./b2 -d0 address-model=64 cxxflags=-fPIC link=static threading=multi variant=release install && \ - rm -rf /boost_${BOOST_VERSION_UNDESRSCORE}.tar.gz /boost_${BOOST_VERSION_UNDESRSCORE} + rm -rf /boost-${BOOST_VERSION}.tar.gz /boost-${BOOST_VERSION} RUN CMAKE_VERSION=$(dep-version.py cmake) && \ curl -O -L https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${PLATFORM}.tar.gz && \ @@ -110,7 +109,7 @@ RUN CURL_VERSION=$(dep-version.py curl) && \ curl -O -L https://github.com/curl/curl/releases/download/curl-${CURL_VERSION_UNDERSCORE}/curl-${CURL_VERSION}.tar.gz && \ tar xfz curl-${CURL_VERSION}.tar.gz && \ cd curl-${CURL_VERSION} && \ - CFLAGS=-fPIC ./configure --with-ssl=/usr/local/ssl/ --without-zstd && \ + CFLAGS=-fPIC ./configure --with-ssl=/usr/local/ssl/ --without-zstd --without-libpsl && \ make -j8 && make install && \ rm -rf /curl-${CURL_VERSION}.tar.gz /curl-${CURL_VERSION} diff --git a/pkg/mac/build-static-library.sh b/pkg/mac/build-static-library.sh new file mode 100755 index 00000000..4b97ac73 --- /dev/null +++ b/pkg/mac/build-static-library.sh @@ -0,0 +1,197 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -ex +cd `dirname $0` + +pip3 install pyyaml + +MACOSX_DEPLOYMENT_TARGET=10.15 +if [[ -z ${ARCH} ]]; then + ARCH=`uname -m` +fi + +BUILD_DIR=$PWD/.build +INSTALL_DIR=$PWD/.install +PREFIX=$BUILD_DIR/install +mkdir -p $BUILD_DIR +cp -f ../../build-support/dep-version.py $BUILD_DIR/ +cp -f ../../dependencies.yaml $BUILD_DIR/ + +pushd $BUILD_DIR + +BOOST_VERSION=$(./dep-version.py boost) +ZLIB_VERSION=$(./dep-version.py zlib) +OPENSSL_VERSION=$(./dep-version.py openssl) +PROTOBUF_VERSION=$(./dep-version.py protobuf) +ZSTD_VERSION=$(./dep-version.py zstd) +SNAPPY_VERSION=$(./dep-version.py snappy) +CURL_VERSION=$(./dep-version.py curl) + +if [ ! -f boost/.done ]; then + echo "Building Boost $BOOST_VERSION" + curl -O -L https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}.tar.gz + tar zxf boost-${BOOST_VERSION}.tar.gz + mkdir -p $PREFIX/include + pushd boost-${BOOST_VERSION} + ./bootstrap.sh + ./b2 headers + cp -rf boost $PREFIX/include/ + popd + mkdir -p boost + touch boost/.done +else + echo "Using cached Boost $BOOST_VERSION" +fi + +if [ ! -f zlib-${ZLIB_VERSION}/.done ]; then + echo "Building ZLib $ZLIB_VERSION" + curl -O -L https://zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz + tar zxf zlib-${ZLIB_VERSION}.tar.gz + pushd zlib-$ZLIB_VERSION + CFLAGS="-fPIC -O3 -arch ${ARCH} -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" ./configure --prefix=$PREFIX + make -j16 + make install + touch .done + popd +else + echo "Using cached ZLib $ZLIB_VERSION" +fi + +OPENSSL_VERSION_UNDERSCORE=$(echo $OPENSSL_VERSION | sed 's/\./_/g') +if [ ! -f openssl-OpenSSL_${OPENSSL_VERSION_UNDERSCORE}.done ]; then + echo "Building OpenSSL $OPENSSL_VERSION" + curl -O -L https://github.com/openssl/openssl/archive/OpenSSL_$OPENSSL_VERSION_UNDERSCORE.tar.gz + tar zxf OpenSSL_$OPENSSL_VERSION_UNDERSCORE.tar.gz + + pushd openssl-OpenSSL_${OPENSSL_VERSION_UNDERSCORE} + if [[ $ARCH = 'arm64' ]]; then + PLATFORM=darwin64-arm64-cc + else + PLATFORM=darwin64-x86_64-cc + fi + CFLAGS="-fPIC -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" \ + ./Configure --prefix=$PREFIX no-shared no-unit-test $PLATFORM + make -j8 >/dev/null + make install_sw >/dev/null + popd + + touch openssl-OpenSSL_${OPENSSL_VERSION_UNDERSCORE}.done +else + echo "Using cached OpenSSL $OPENSSL_VERSION" +fi + +if [ ! -f protobuf-${PROTOBUF_VERSION}/.done ]; then + echo "Building Protobuf $PROTOBUF_VERSION" + curl -O -L https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-cpp-${PROTOBUF_VERSION}.tar.gz + tar zxf protobuf-cpp-${PROTOBUF_VERSION}.tar.gz + pushd protobuf-${PROTOBUF_VERSION} + pushd cmake/ + # Build protoc that can run on both x86 and arm architectures + cmake -B build -DCMAKE_CXX_FLAGS="-fPIC -arch x86_64 -arch arm64 -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" \ + -Dprotobuf_BUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX=$PREFIX + cmake --build build -j16 --target install + popd + + # Retain the library for one architecture so that `ar` can work on the library + pushd $PREFIX/lib + mv libprotobuf.a libprotobuf_universal.a + lipo libprotobuf_universal.a -thin ${ARCH} -output libprotobuf.a + popd + touch .done + popd +else + echo "Using cached Protobuf $PROTOBUF_VERSION" +fi + +if [ ! -f zstd-${ZSTD_VERSION}/.done ]; then + echo "Building ZStd $ZSTD_VERSION" + curl -O -L https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION}/zstd-${ZSTD_VERSION}.tar.gz + tar zxf zstd-${ZSTD_VERSION}.tar.gz + pushd zstd-${ZSTD_VERSION} + CFLAGS="-fPIC -O3 -arch ${ARCH} -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" PREFIX=$PREFIX \ + make -j16 -C lib install-static install-includes + touch .done + popd +else + echo "Using cached ZStd $ZSTD_VERSION" +fi + +if [ ! -f snappy-${SNAPPY_VERSION}/.done ]; then + echo "Building Snappy $SNAPPY_VERSION" + curl -O -L https://github.com/google/snappy/archive/refs/tags/${SNAPPY_VERSION}.tar.gz + tar zxf ${SNAPPY_VERSION}.tar.gz + pushd snappy-${SNAPPY_VERSION} + # Without this patch, snappy 1.10 will report a sign-compare error, which cannot be suppressed with the -Wno-sign-compare option in CI + curl -O -L https://raw.githubusercontent.com/microsoft/vcpkg/2024.02.14/ports/snappy/no-werror.patch + patch /dev/null 2>&1 ; do sleep 1; done +until curl http://localhost:8081/metrics > /dev/null 2>&1 ; do sleep 1; done +sleep 5 +$CMAKE_BUILD_DIRECTORY/tests/ExtensibleLoadManagerTest +docker compose -f tests/blue-green/docker-compose.yml down +docker compose -f tests/extensibleLM/docker-compose.yml down + # Run OAuth2 tests docker compose -f tests/oauth2/docker-compose.yml up -d # Wait until the namespace is created, currently there is no good way to check it # because it's hard to configure OAuth2 authentication via CLI. sleep 15 -$CMAKE_BUILD_DIRECTORY/tests/Oauth2Test +$CMAKE_BUILD_DIRECTORY/tests/Oauth2Test --gtest_filter='-*testTlsTrustFilePath' +if [[ -f /etc/ssl/certs/ca-certificates.crt ]]; then + sudo mv /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/my-cert.crt +fi +$CMAKE_BUILD_DIRECTORY/tests/Oauth2Test --gtest_filter='*testTlsTrustFilePath' +if [[ -f /etc/ssl/certs/my-cert.crt ]]; then + sudo mv /etc/ssl/certs/my-cert.crt /etc/ssl/certs/ca-certificates.crt +fi docker compose -f tests/oauth2/docker-compose.yml down # Run BrokerMetadata tests diff --git a/test-conf/standalone-ssl-mim.conf b/test-conf/standalone-ssl-mim.conf index 359645ea..198a9002 100644 --- a/test-conf/standalone-ssl-mim.conf +++ b/test-conf/standalone-ssl-mim.conf @@ -307,3 +307,8 @@ maxMessageSize=1024000 # Disable consistent hashing to fix flaky `KeySharedConsumerTest#testMultiTopics`. subscriptionKeySharedUseConsistentHashing=false + +# It's true by default since 2.11. After https://github.com/apache/pulsar/pull/21445, we must configure +# brokerClientAuthenticationPlugin and brokerClientAuthenticationParameters correctly when enabling topic +# level policies. Otherwise, no topic could be loaded. +topicLevelPoliciesEnabled=false diff --git a/test-conf/standalone-ssl.conf b/test-conf/standalone-ssl.conf index 4b150076..36ad9b56 100644 --- a/test-conf/standalone-ssl.conf +++ b/test-conf/standalone-ssl.conf @@ -19,10 +19,6 @@ ### --- General broker settings --- ### -# Disable system topic -systemTopicEnabled=false -topicLevelPoliciesEnabled=false - # Zookeeper quorum connection string zookeeperServers= @@ -317,3 +313,8 @@ subscriptionKeySharedUseConsistentHashing=false # Enable batch index ACK acknowledgmentAtBatchIndexLevelEnabled=true + +# It's true by default since 2.11. After https://github.com/apache/pulsar/pull/21445, we must configure +# brokerClientAuthenticationPlugin and brokerClientAuthenticationParameters correctly when enabling topic +# level policies. Otherwise, no topic could be loaded. +topicLevelPoliciesEnabled=false diff --git a/tests/AuthBasicTest.cc b/tests/AuthBasicTest.cc index 0b8b7f0c..0e181727 100644 --- a/tests/AuthBasicTest.cc +++ b/tests/AuthBasicTest.cc @@ -25,13 +25,17 @@ using namespace pulsar; +#ifndef TEST_CONF_DIR +#error "TEST_CONF_DIR is not specified" +#endif + static const std::string serviceUrl = "pulsar://localhost:6650"; static const std::string serviceUrlHttp = "http://localhost:8080"; static const std::string serviceUrlTls = "pulsar+ssl://localhost:6651"; static const std::string serviceUrlHttps = "https://localhost:8443"; -static const std::string caPath = "../test-conf/cacert.pem"; -static const std::string clientCertificatePath = "../test-conf/client-cert.pem"; -static const std::string clientPrivateKeyPath = "../test-conf/client-key.pem"; +static const std::string caPath = TEST_CONF_DIR "/cacert.pem"; +static const std::string clientCertificatePath = TEST_CONF_DIR "/client-cert.pem"; +static const std::string clientPrivateKeyPath = TEST_CONF_DIR "/client-key.pem"; TEST(AuthPluginBasic, testBasic) { ClientConfiguration config = ClientConfiguration(); diff --git a/tests/AuthPluginTest.cc b/tests/AuthPluginTest.cc index 2d23bf9f..b091f973 100644 --- a/tests/AuthPluginTest.cc +++ b/tests/AuthPluginTest.cc @@ -21,9 +21,14 @@ #include #include +#ifdef USE_ASIO +#include +#else #include +#endif #include +#include "lib/AsioDefines.h" #include "lib/Future.h" #include "lib/Latch.h" #include "lib/LogUtils.h" @@ -37,15 +42,19 @@ int globalTestTlsMessagesCounter = 0; static const std::string serviceUrlTls = "pulsar+ssl://localhost:6651"; static const std::string serviceUrlHttps = "https://localhost:8443"; -static const std::string caPath = "../test-conf/cacert.pem"; -static const std::string clientPublicKeyPath = "../test-conf/client-cert.pem"; -static const std::string clientPrivateKeyPath = "../test-conf/client-key.pem"; +#ifndef TEST_CONF_DIR +#error "TEST_CONF_DIR is not specified" +#endif + +static const std::string caPath = TEST_CONF_DIR "/cacert.pem"; +static const std::string clientPublicKeyPath = TEST_CONF_DIR "/client-cert.pem"; +static const std::string clientPrivateKeyPath = TEST_CONF_DIR "/client-key.pem"; // Man in middle certificate which tries to act as a broker by sending its own valid certificate static const std::string mimServiceUrlTls = "pulsar+ssl://localhost:6653"; static const std::string mimServiceUrlHttps = "https://localhost:8444"; -static const std::string mimCaPath = "../test-conf/hn-verification/cacert.pem"; +static const std::string mimCaPath = TEST_CONF_DIR "/hn-verification/cacert.pem"; static void sendCallBackTls(Result r, const MessageId& msgId) { ASSERT_EQ(r, ResultOk); @@ -283,10 +292,9 @@ namespace testAthenz { std::string principalToken; void mockZTS(Latch& latch, int port) { LOG_INFO("-- MockZTS started"); - boost::asio::io_service io; - boost::asio::ip::tcp::iostream stream; - boost::asio::ip::tcp::acceptor acceptor(io, - boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); + ASIO::io_service io; + ASIO::ip::tcp::iostream stream; + ASIO::ip::tcp::acceptor acceptor(io, ASIO::ip::tcp::endpoint(ASIO::ip::tcp::v4(), port)); LOG_INFO("-- MockZTS waiting for connnection"); latch.countdown(); @@ -468,12 +476,16 @@ TEST(AuthPluginTest, testOauth2WrongSecret) { TEST(AuthPluginTest, testOauth2CredentialFile) { // test success get token from oauth2 server. pulsar::AuthenticationDataPtr data; - std::string params = R"({ + const char* paramsTemplate = R"({ "type": "client_credentials", "issuer_url": "https://dev-kt-aa9ne.us.auth0.com", - "private_key": "../test-conf/cpp_credentials_file.json", + "private_key": "%s/cpp_credentials_file.json", "audience": "https://dev-kt-aa9ne.us.auth0.com/api/v2/"})"; + char params[4096]; + int numWritten = snprintf(params, sizeof(params), paramsTemplate, TEST_CONF_DIR); + ASSERT_TRUE(numWritten < sizeof(params)); + int expectedTokenLength = 3379; LOG_INFO("PARAMS: " << params); pulsar::AuthenticationPtr auth = pulsar::AuthOauth2::create(params); @@ -573,3 +585,19 @@ TEST(AuthPluginTest, testOauth2Failure) { ASSERT_EQ(client5.createProducer(topic, producer), ResultAuthenticationError); client5.close(); } + +TEST(AuthPluginTest, testInvalidPlugin) { + Client client("pulsar://localhost:6650", ClientConfiguration{}.setAuth(AuthFactory::create("invalid"))); + Producer producer; + ASSERT_EQ(ResultAuthenticationError, client.createProducer("my-topic", producer)); + client.close(); +} + +TEST(AuthPluginTest, testTlsConfigError) { + Client client(serviceUrlTls, ClientConfiguration{} + .setAuth(AuthTls::create(clientPublicKeyPath, clientPrivateKeyPath)) + .setTlsTrustCertsFilePath("invalid")); + Producer producer; + ASSERT_EQ(ResultAuthenticationError, client.createProducer("my-topic", producer)); + client.close(); +} diff --git a/tests/AuthTokenTest.cc b/tests/AuthTokenTest.cc index 8bdd2685..7595f44e 100644 --- a/tests/AuthTokenTest.cc +++ b/tests/AuthTokenTest.cc @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -37,7 +36,11 @@ using namespace pulsar; static const std::string serviceUrl = "pulsar://localhost:6650"; static const std::string serviceUrlHttp = "http://localhost:8080"; -static const std::string tokenPath = "../.test-token.txt"; +#ifndef TOKEN_PATH +#error "TOKEN_PATH is not specified" +#endif + +static const std::string tokenPath = TOKEN_PATH; static std::string getToken() { std::ifstream file(tokenPath); diff --git a/tests/BackoffTest.cc b/tests/BackoffTest.cc index d066b944..9c30a639 100644 --- a/tests/BackoffTest.cc +++ b/tests/BackoffTest.cc @@ -17,51 +17,59 @@ * under the License. */ #include +#include #include #include "PulsarFriend.h" #include "lib/Backoff.h" #include "lib/ClientConnection.h" +#include "lib/TimeUtils.h" #include "lib/stats/ProducerStatsImpl.h" using namespace pulsar; -using boost::posix_time::milliseconds; -using boost::posix_time::seconds; +using std::chrono::milliseconds; +using std::chrono::seconds; static bool checkExactAndDecrementTimer(Backoff& backoff, const unsigned int& t2) { - const unsigned int& t1 = backoff.next().total_milliseconds(); - boost::posix_time::ptime& firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); + auto t1 = toMillis(backoff.next()); + auto& firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); firstBackOffTime -= milliseconds(t2); return t1 == t2; } static bool withinTenPercentAndDecrementTimer(Backoff& backoff, const unsigned int& t2) { - const unsigned int& t1 = backoff.next().total_milliseconds(); - boost::posix_time::ptime& firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); + auto t1 = toMillis(backoff.next()); + auto& firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); firstBackOffTime -= milliseconds(t2); return (t1 >= t2 * 0.9 && t1 <= t2); } +TEST(BackoffTest, testCurrentTimeMillis) { + auto t1 = TimeUtils::currentTimeMillis(); + auto t2 = 1000L * time(nullptr); + ASSERT_TRUE(t1 - t2 < 1000L) << "t1: " << t1 << ", t2: " << t2; +} + TEST(BackoffTest, mandatoryStopTestNegativeTest) { Backoff backoff(milliseconds(100), seconds(60), milliseconds(1900)); - ASSERT_EQ(backoff.next().total_milliseconds(), 100); - backoff.next().total_milliseconds(); // 200 - backoff.next().total_milliseconds(); // 400 - backoff.next().total_milliseconds(); // 800 + ASSERT_EQ(toMillis(backoff.next()), 100); + backoff.next(); // 200 + backoff.next(); // 400 + backoff.next(); // 800 ASSERT_FALSE(withinTenPercentAndDecrementTimer(backoff, 400)); } TEST(BackoffTest, firstBackoffTimerTest) { Backoff backoff(milliseconds(100), seconds(60), milliseconds(1900)); - ASSERT_EQ(backoff.next().total_milliseconds(), 100); - boost::posix_time::ptime firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); + ASSERT_EQ(toMillis(backoff.next()), 100); + auto firstBackOffTime = PulsarFriend::getFirstBackoffTime(backoff); std::this_thread::sleep_for(std::chrono::milliseconds(300)); TimeDuration diffBackOffTime = PulsarFriend::getFirstBackoffTime(backoff) - firstBackOffTime; ASSERT_EQ(diffBackOffTime, milliseconds(0)); // no change since reset not called backoff.reset(); - ASSERT_EQ(backoff.next().total_milliseconds(), 100); + ASSERT_EQ(toMillis(backoff.next()), 100); diffBackOffTime = PulsarFriend::getFirstBackoffTime(backoff) - firstBackOffTime; ASSERT_TRUE(diffBackOffTime >= milliseconds(300) && diffBackOffTime < seconds(1)); } diff --git a/tests/BasicEndToEndTest.cc b/tests/BasicEndToEndTest.cc index 9ca2ab0c..42c072ad 100644 --- a/tests/BasicEndToEndTest.cc +++ b/tests/BasicEndToEndTest.cc @@ -54,6 +54,10 @@ DECLARE_LOG_OBJECT() using namespace pulsar; +#ifndef TEST_CONF_DIR +#error "TEST_CONF_DIR is not specified" +#endif + std::mutex mutex_; static int globalCount = 0; static long globalResendMessageCount = 0; @@ -240,7 +244,7 @@ TEST(BasicEndToEndTest, testProduceConsume) { consumer.receive(receivedMsg); ASSERT_EQ(content, receivedMsg.getDataAsString()); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } @@ -401,7 +405,7 @@ TEST(BasicEndToEndTest, testMultipleClientsMultipleSubscriptions) { ASSERT_EQ(ResultOk, producer1.close()); ASSERT_EQ(ResultOk, consumer1.close()); - ASSERT_EQ(ResultAlreadyClosed, consumer1.close()); + ASSERT_EQ(ResultOk, consumer1.close()); ASSERT_EQ(ResultConsumerNotInitialized, consumer2.close()); ASSERT_EQ(ResultOk, client1.close()); @@ -633,7 +637,7 @@ TEST(BasicEndToEndTest, testCompressionLZ4) { ASSERT_EQ(content2, receivedMsg.getDataAsString()); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } @@ -671,7 +675,7 @@ TEST(BasicEndToEndTest, testCompressionZLib) { ASSERT_EQ(content2, receivedMsg.getDataAsString()); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } @@ -746,7 +750,7 @@ TEST(BasicEndToEndTest, testConsumerClose) { Consumer consumer; ASSERT_EQ(ResultOk, client.subscribe(topicName, subName, consumer)); ASSERT_EQ(consumer.close(), ResultOk); - ASSERT_EQ(consumer.close(), ResultAlreadyClosed); + ASSERT_EQ(consumer.close(), ResultOk); } TEST(BasicEndToEndTest, testDuplicateConsumerCreationOnPartitionedTopic) { @@ -1341,9 +1345,9 @@ TEST(BasicEndToEndTest, testRSAEncryption) { std::string subName = "my-sub-name"; Producer producer; - std::string PUBLIC_CERT_FILE_PATH = "../test-conf/public-key.client-rsa.pem"; + std::string PUBLIC_CERT_FILE_PATH = TEST_CONF_DIR "/public-key.client-rsa.pem"; - std::string PRIVATE_CERT_FILE_PATH = "../test-conf//private-key.client-rsa.pem"; + std::string PRIVATE_CERT_FILE_PATH = TEST_CONF_DIR "/private-key.client-rsa.pem"; std::shared_ptr keyReader = std::make_shared(PUBLIC_CERT_FILE_PATH, PRIVATE_CERT_FILE_PATH); @@ -1394,7 +1398,7 @@ TEST(BasicEndToEndTest, testRSAEncryption) { } ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); } ASSERT_EQ(ResultOk, client.close()); @@ -1407,9 +1411,9 @@ TEST(BasicEndToEndTest, testEncryptionFailure) { std::string subName = "my-sub-name"; Producer producer; - std::string PUBLIC_CERT_FILE_PATH = "../test-conf/public-key.client-rsa-test.pem"; + std::string PUBLIC_CERT_FILE_PATH = TEST_CONF_DIR "/public-key.client-rsa-test.pem"; - std::string PRIVATE_CERT_FILE_PATH = "../test-conf//private-key.client-rsa-test.pem"; + std::string PRIVATE_CERT_FILE_PATH = TEST_CONF_DIR "/private-key.client-rsa-test.pem"; std::shared_ptr keyReader = std::make_shared(PUBLIC_CERT_FILE_PATH, PRIVATE_CERT_FILE_PATH); @@ -1449,9 +1453,9 @@ TEST(BasicEndToEndTest, testEncryptionFailure) { // 2. Add valid key { - PUBLIC_CERT_FILE_PATH = "../test-conf/public-key.client-rsa.pem"; + PUBLIC_CERT_FILE_PATH = TEST_CONF_DIR "/public-key.client-rsa.pem"; - PRIVATE_CERT_FILE_PATH = "../test-conf/private-key.client-rsa.pem"; + PRIVATE_CERT_FILE_PATH = TEST_CONF_DIR "/private-key.client-rsa.pem"; keyReader = std::make_shared(PUBLIC_CERT_FILE_PATH, PRIVATE_CERT_FILE_PATH); ProducerConfiguration prodConf; @@ -1613,7 +1617,7 @@ TEST(BasicEndToEndTest, testSeek) { ASSERT_EQ(expected.str(), msgReceived.getDataAsString()); ASSERT_EQ(ResultOk, consumer.acknowledge(msgReceived)); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } @@ -1690,7 +1694,7 @@ TEST(BasicEndToEndTest, testSeekOnPartitionedTopic) { ASSERT_EQ(expected.str(), msgReceived.getDataAsString()); ASSERT_EQ(ResultOk, consumer.acknowledge(msgReceived)); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } @@ -3969,6 +3973,7 @@ TEST(BasicEndToEndTest, testUnAckedMessageTrackerEnabledIndividualAck) { auto tracker0 = std::make_shared(unAckedMessagesTimeoutMs, clientImplPtr, consumerImpl0); + tracker0->start(); ASSERT_EQ(tracker0->getUnAckedMessagesTimeoutMs(), unAckedMessagesTimeoutMs); ASSERT_EQ(tracker0->getTickDurationInMs(), unAckedMessagesTimeoutMs); @@ -4044,6 +4049,7 @@ TEST(BasicEndToEndTest, testUnAckedMessageTrackerEnabledCumulativeAck) { } auto tracker = std::make_shared(unAckedMessagesTimeoutMs, clientImplPtr, consumerImpl0); + tracker->start(); for (auto idx = 0; idx < numMsg; ++idx) { ASSERT_TRUE(tracker->add(recvMsgId[idx])); } diff --git a/tests/BuildTests.cmake b/tests/BuildTests.cmake new file mode 100644 index 00000000..0fe74300 --- /dev/null +++ b/tests/BuildTests.cmake @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set(LIB_AUTOGEN_DIR ${AUTOGEN_DIR}/tests) +file(MAKE_DIRECTORY ${LIB_AUTOGEN_DIR}) +include_directories(${LIB_AUTOGEN_DIR}) + +find_package(GTest CONFIG REQUIRED) +set(GTEST_TARGETS GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main) + +add_library(TESTS_PROTO_OBJECTS OBJECT + ${CMAKE_CURRENT_SOURCE_DIR}/PaddingDemo.proto + ${CMAKE_CURRENT_SOURCE_DIR}/proto/ExternalTest.proto + ${CMAKE_CURRENT_SOURCE_DIR}/proto/Test.proto + ) +target_link_libraries(TESTS_PROTO_OBJECTS PUBLIC protobuf::libprotobuf) +protobuf_generate( + TARGET TESTS_PROTO_OBJECTS + IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto" "${CMAKE_CURRENT_SOURCE_DIR}" + PROTOC_OUT_DIR ${LIB_AUTOGEN_DIR}) + +file(GLOB TEST_SOURCES *.cc c/*.cc) +add_executable(pulsar-tests ${TEST_SOURCES}) +target_include_directories(pulsar-tests PRIVATE ${AUTOGEN_DIR}/lib) +target_compile_options(pulsar-tests PRIVATE -DTOKEN_PATH="${TOKEN_PATH}" -DTEST_CONF_DIR="${TEST_CONF_DIR}") +target_link_libraries(pulsar-tests PRIVATE pulsarStatic TESTS_PROTO_OBJECTS ${GTEST_TARGETS}) + +if (UNIX) + add_executable(ConnectionFailTest unix/ConnectionFailTest.cc HttpHelper.cc) + target_link_libraries(ConnectionFailTest pulsarStatic ${GTEST_TARGETS}) +endif () + +add_executable(BrokerMetadataTest brokermetadata/BrokerMetadataTest.cc) +target_link_libraries(BrokerMetadataTest pulsarStatic ${GTEST_TARGETS}) + +add_executable(Oauth2Test oauth2/Oauth2Test.cc) +target_compile_options(Oauth2Test PRIVATE -DTEST_CONF_DIR="${TEST_CONF_DIR}") +target_link_libraries(Oauth2Test pulsarStatic ${GTEST_TARGETS}) + +add_executable(ChunkDedupTest chunkdedup/ChunkDedupTest.cc HttpHelper.cc) +target_link_libraries(ChunkDedupTest pulsarStatic ${GTEST_TARGETS}) + +add_executable(ExtensibleLoadManagerTest extensibleLM/ExtensibleLoadManagerTest.cc HttpHelper.cc) +target_link_libraries(ExtensibleLoadManagerTest PRIVATE pulsarStatic ${GTEST_TARGETS}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b3731964..f895d34e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,60 +17,8 @@ # under the License. # -if (NOT PROTOC_PATH) - set(PROTOC_PATH protoc) -endif() - -set(LIB_AUTOGEN_DIR ${AUTOGEN_DIR}/tests) -file(MAKE_DIRECTORY ${LIB_AUTOGEN_DIR}) -include_directories(${LIB_AUTOGEN_DIR}) - -set(PROTO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto) -set(PROTO_SOURCES ${LIB_AUTOGEN_DIR}/Test.pb.cc ${LIB_AUTOGEN_DIR}/ExternalTest.pb.cc) -add_custom_command( - OUTPUT ${PROTO_SOURCES} - COMMAND ${PROTOC_PATH} -I ${PROTO_DIR} ${PROTO_DIR}/Test.proto ${PROTO_DIR}/ExternalTest.proto --cpp_out=${LIB_AUTOGEN_DIR}) - -set(PROTO_SOURCE_PADDING ${LIB_AUTOGEN_DIR}/PaddingDemo.pb.cc) -add_custom_command( - OUTPUT ${PROTO_SOURCE_PADDING} - COMMAND ${PROTOC_PATH} -I . ./PaddingDemo.proto --cpp_out=${LIB_AUTOGEN_DIR} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - -set(PROTO_SOURCES ${PROTO_SOURCES} ${PROTO_SOURCE_PADDING}) - -include_directories(${LIB_AUTOGEN_DIR}) - -find_library(GMOCK_LIBRARY_PATH gmock) -find_library(GTEST_LIBRARY_PATH gtest) -find_library(GMOCKD_LIBRARY_PATH gmockd) -find_library(GTESTD_LIBRARY_PATH gtestd) -if (NOT GMOCKD_LIBRARY_PATH) - set(GMOCKD_LIBRARY_PATH ${GMOCK_LIBRARY_PATH}) -endif() -if (NOT GTESTD_LIBRARY_PATH) - set(GTESTD_LIBRARY_PATH ${GTEST_LIBRARY_PATH}) -endif() - -file(GLOB TEST_SOURCES *.cc c/*.cc) - -add_executable(pulsar-tests ${TEST_SOURCES} ${PROTO_SOURCES}) - -target_include_directories(pulsar-tests PRIVATE ${AUTOGEN_DIR}/lib) - -target_link_libraries(pulsar-tests ${CLIENT_LIBS} pulsarStatic $<$:${GMOCKD_LIBRARY_PATH}> $<$:${GTESTD_LIBRARY_PATH}> $<$>:${GMOCK_LIBRARY_PATH}> $<$>:${GTEST_LIBRARY_PATH}>) - -if (UNIX) - add_executable(ConnectionFailTest unix/ConnectionFailTest.cc HttpHelper.cc) - target_link_libraries(ConnectionFailTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) +if (INTEGRATE_VCPKG) + include(BuildTests.cmake) +else () + include(LegacyBuildTests.cmake) endif () - -add_executable(BrokerMetadataTest brokermetadata/BrokerMetadataTest.cc) -target_link_libraries(BrokerMetadataTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) - -add_executable(Oauth2Test oauth2/Oauth2Test.cc) -target_compile_options(Oauth2Test PRIVATE "-DTEST_ROOT_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}\"") -target_link_libraries(Oauth2Test ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) - -add_executable(ChunkDedupTest chunkdedup/ChunkDedupTest.cc) -target_link_libraries(ChunkDedupTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) diff --git a/tests/ClientTest.cc b/tests/ClientTest.cc index 983e5d91..b5142860 100644 --- a/tests/ClientTest.cc +++ b/tests/ClientTest.cc @@ -21,13 +21,12 @@ #include #include -#include -#include #include #include #include -#include "HttpHelper.h" +#include "MockClientImpl.h" +#include "PulsarAdminHelper.h" #include "PulsarFriend.h" #include "WaitUtils.h" #include "lib/ClientConnection.h" @@ -38,6 +37,7 @@ DECLARE_LOG_OBJECT() using namespace pulsar; +using testing::AtLeast; static std::string lookupUrl = "pulsar://localhost:6650"; static std::string adminUrl = "http://localhost:8080/"; @@ -250,7 +250,7 @@ TEST(ClientTest, testWrongListener) { Client client(lookupUrl, ClientConfiguration().setListenerName("test")); Producer producer; - ASSERT_EQ(ResultServiceUnitNotReady, client.createProducer(topic, producer)); + ASSERT_EQ(ResultConnectError, client.createProducer(topic, producer)); ASSERT_EQ(ResultProducerNotInitialized, producer.close()); ASSERT_EQ(PulsarFriend::getProducers(client).size(), 0); ASSERT_EQ(ResultOk, client.close()); @@ -259,7 +259,7 @@ TEST(ClientTest, testWrongListener) { // creation of Consumer or Reader could fail with ResultConnectError. client = Client(lookupUrl, ClientConfiguration().setListenerName("test")); Consumer consumer; - ASSERT_EQ(ResultServiceUnitNotReady, client.subscribe(topic, "sub", consumer)); + ASSERT_EQ(ResultConnectError, client.subscribe(topic, "sub", consumer)); ASSERT_EQ(ResultConsumerNotInitialized, consumer.close()); ASSERT_EQ(PulsarFriend::getConsumers(client).size(), 0); @@ -268,7 +268,7 @@ TEST(ClientTest, testWrongListener) { client = Client(lookupUrl, ClientConfiguration().setListenerName("test")); Consumer multiTopicsConsumer; - ASSERT_EQ(ResultServiceUnitNotReady, + ASSERT_EQ(ResultConnectError, client.subscribe({topic + "-partition-0", topic + "-partition-1", topic + "-partition-2"}, "sub", multiTopicsConsumer)); @@ -280,7 +280,7 @@ TEST(ClientTest, testWrongListener) { // Currently Reader can only read a non-partitioned topic in C++ client Reader reader; - ASSERT_EQ(ResultServiceUnitNotReady, + ASSERT_EQ(ResultConnectError, client.createReader(topic + "-partition-0", MessageId::earliest(), {}, reader)); ASSERT_EQ(ResultConsumerNotInitialized, reader.close()); ASSERT_EQ(PulsarFriend::getConsumers(client).size(), 0); @@ -335,18 +335,13 @@ class PulsarWrapper { // When `subscription` is empty, get client versions of the producers. // Otherwise, get client versions of the consumers under the subscribe. static std::vector getClientVersions(const std::string &topic, std::string subscription = "") { - const auto url = adminUrl + "admin/v2/persistent/public/default/" + topic + "/stats"; - std::string responseData; - int res = makeGetRequest(url, responseData); - if (res != 200) { - LOG_ERROR(url << " failed: " << res); + boost::property_tree::ptree root; + const auto error = getTopicStats(topic, root); + if (!error.empty()) { + LOG_ERROR(error); return {}; } - std::stringstream stream; - stream << responseData; - boost::property_tree::ptree root; - boost::property_tree::read_json(stream, root); std::vector versions; if (subscription.empty()) { for (auto &child : root.get_child("publishers")) { @@ -400,3 +395,112 @@ TEST(ClientTest, testClientVersion) { client.close(); } + +TEST(ClientTest, testConnectionClose) { + std::vector clients; + clients.emplace_back(lookupUrl); + clients.emplace_back(lookupUrl, ClientConfiguration().setConnectionsPerBroker(5)); + + const auto topic = "client-test-connection-close"; + for (auto &client : clients) { + auto testClose = [&client](ClientConnectionWeakPtr weakCnx) { + auto cnx = weakCnx.lock(); + ASSERT_TRUE(cnx); + + auto numConnections = PulsarFriend::getConnections(client).size(); + LOG_INFO("Connection refcnt: " << cnx.use_count() << " before close"); + auto executor = PulsarFriend::getExecutor(*cnx); + // Simulate the close() happens in the event loop + executor->postWork([cnx, &client, numConnections] { + cnx->close(); + ASSERT_EQ(PulsarFriend::getConnections(client).size(), numConnections - 1); + LOG_INFO("Connection refcnt: " << cnx.use_count() << " after close"); + }); + cnx.reset(); + + // The ClientConnection could still be referred in a socket callback, wait until all these + // callbacks being cancelled due to the socket close. + ASSERT_TRUE(waitUntil( + std::chrono::seconds(1), [weakCnx] { return weakCnx.expired(); }, 1)); + }; + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer(topic, producer)); + testClose(PulsarFriend::getProducerImpl(producer).getCnx()); + producer.close(); + + Consumer consumer; + ASSERT_EQ(ResultOk, client.subscribe("client-test-connection-close", "sub", consumer)); + testClose(PulsarFriend::getConsumerImpl(consumer).getCnx()); + consumer.close(); + + client.close(); + } +} + +TEST(ClientTest, testRetryUntilSucceed) { + auto clientImpl = std::make_shared(lookupUrl); + constexpr int kFailCount = 3; + EXPECT_CALL(*clientImpl, getConnection).Times((kFailCount + 1) * 2); + std::atomic_int count{0}; + ON_CALL(*clientImpl, getConnection) + .WillByDefault([&clientImpl, &count](const std::string &redirectedClusterURI, + const std::string &topic, size_t index) { + if (count++ < kFailCount) { + return GetConnectionFuture::failed(ResultRetryable); + } + return clientImpl->getConnectionReal(topic, index); + }); + + auto topic = "client-test-retry-until-succeed"; + ASSERT_EQ(ResultOk, clientImpl->createProducer(topic).result); + count = 0; + ASSERT_EQ(ResultOk, clientImpl->subscribe(topic).result); + ASSERT_EQ(ResultOk, clientImpl->close()); +} + +TEST(ClientTest, testRetryTimeout) { + auto clientImpl = + std::make_shared(lookupUrl, ClientConfiguration().setOperationTimeoutSeconds(2)); + EXPECT_CALL(*clientImpl, getConnection).Times(AtLeast(2 * 2)); + ON_CALL(*clientImpl, getConnection) + .WillByDefault([](const std::string &redirectedClusterURI, const std::string &topic, size_t index) { + return GetConnectionFuture::failed(ResultRetryable); + }); + + auto topic = "client-test-retry-timeout"; + { + MockClientImpl::SyncOpResult result = clientImpl->createProducer(topic); + ASSERT_EQ(ResultTimeout, result.result); + ASSERT_TRUE(result.timeMs >= 2000 && result.timeMs < 2100) << "producer: " << result.timeMs << " ms"; + } + { + MockClientImpl::SyncOpResult result = clientImpl->subscribe(topic); + ASSERT_EQ(ResultTimeout, result.result); + ASSERT_TRUE(result.timeMs >= 2000 && result.timeMs < 2100) << "consumer: " << result.timeMs << " ms"; + } + + ASSERT_EQ(ResultOk, clientImpl->close()); +} + +TEST(ClientTest, testNoRetry) { + auto clientImpl = + std::make_shared(lookupUrl, ClientConfiguration().setOperationTimeoutSeconds(100)); + EXPECT_CALL(*clientImpl, getConnection).Times(2); + ON_CALL(*clientImpl, getConnection) + .WillByDefault([](const std::string &redirectedClusterURI, const std::string &, size_t) { + return GetConnectionFuture::failed(ResultAuthenticationError); + }); + + auto topic = "client-test-no-retry"; + { + MockClientImpl::SyncOpResult result = clientImpl->createProducer(topic); + ASSERT_EQ(ResultAuthenticationError, result.result); + ASSERT_TRUE(result.timeMs < 1000) << "producer: " << result.timeMs << " ms"; + } + { + MockClientImpl::SyncOpResult result = clientImpl->subscribe(topic); + LOG_INFO("It takes " << result.timeMs << " ms to subscribe"); + ASSERT_EQ(ResultAuthenticationError, result.result); + ASSERT_TRUE(result.timeMs < 1000) << "consumer: " << result.timeMs << " ms"; + } +} diff --git a/tests/ConnectionTest.cc b/tests/ConnectionTest.cc new file mode 100644 index 00000000..e0d063e9 --- /dev/null +++ b/tests/ConnectionTest.cc @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + */ +#include +#include + +#include + +#include "lib/ClientConnection.h" +#include "lib/ClientConnectionAdaptor.h" + +using namespace pulsar; + +class MockClientConnection { + public: + MOCK_METHOD(void, close, (Result)); + + void checkServerError(ServerError error, const std::string& message) { + ::pulsar::adaptor::checkServerError(*this, error, message); + } +}; + +// These error messages come from +// https://github.com/apache/pulsar/blob/a702e5a582eaa8292720f9e25fc2dcf858078813/pulsar-broker/src/main/java/org/apache/pulsar/broker/lookup/TopicLookupBase.java#L334-L351 +static const std::vector retryableErrorMessages{ + "Namespace bundle public/default/0x00000000_0xffffffff is being unloaded", + "org.apache.zookeeper.KeeperException$OperationTimeoutException: KeeperErrorCode = OperationTimeout for " + "/namespace/public/default/0x00000000_0xffffffff", + "Failed to acquire ownership for namespace bundle public/default/0x00000000_0xffffffff"}; + +TEST(ConnectionTest, testCheckServerError) { + MockClientConnection conn; + EXPECT_CALL(conn, close(ResultDisconnected)).Times(0); + for (auto&& msg : retryableErrorMessages) { + conn.checkServerError(pulsar::proto::ServiceNotReady, msg); + } +} diff --git a/tests/ConsumerConfigurationTest.cc b/tests/ConsumerConfigurationTest.cc index f3785011..482f0fc5 100644 --- a/tests/ConsumerConfigurationTest.cc +++ b/tests/ConsumerConfigurationTest.cc @@ -314,7 +314,7 @@ TEST(ConsumerConfigurationTest, testSubscriptionInitialPosition) { ASSERT_EQ(content1, receivedMsg.getDataAsString()); ASSERT_EQ(ResultOk, consumer.unsubscribe()); - ASSERT_EQ(ResultAlreadyClosed, consumer.close()); + ASSERT_EQ(ResultOk, consumer.close()); ASSERT_EQ(ResultOk, producer.close()); ASSERT_EQ(ResultOk, client.close()); } diff --git a/tests/ConsumerTest.cc b/tests/ConsumerTest.cc index 79175080..f97457fe 100644 --- a/tests/ConsumerTest.cc +++ b/tests/ConsumerTest.cc @@ -29,8 +29,8 @@ #include #include -#include "HttpHelper.h" #include "NoOpsCryptoKeyReader.h" +#include "PulsarAdminHelper.h" #include "PulsarFriend.h" #include "SynchronizedQueue.h" #include "WaitUtils.h" @@ -50,6 +50,10 @@ static const std::string adminUrl = "http://localhost:8080/"; DECLARE_LOG_OBJECT() +#ifndef TEST_CONF_DIR +#error "TEST_CONF_DIR is not specified" +#endif + namespace pulsar { class ConsumerStateEventListener : public ConsumerEventListener { @@ -951,7 +955,7 @@ TEST(ConsumerTest, testGetLastMessageIdBlockWhenConnectionDisconnected) { auto elapsed = TimeUtils::now() - start; // getLastMessageIdAsync should be blocked until operationTimeout when the connection is disconnected. - ASSERT_GE(elapsed.seconds(), operationTimeout); + ASSERT_GE(std::chrono::duration_cast(elapsed).count(), operationTimeout); } TEST(ConsumerTest, testRedeliveryOfDecryptionFailedMessages) { @@ -960,8 +964,8 @@ TEST(ConsumerTest, testRedeliveryOfDecryptionFailedMessages) { std::string topicName = "testRedeliveryOfDecryptionFailedMessages" + std::to_string(time(nullptr)); std::string subName = "sub-test"; - std::string PUBLIC_CERT_FILE_PATH = "../test-conf/public-key.client-rsa.pem"; - std::string PRIVATE_CERT_FILE_PATH = "../test-conf/private-key.client-rsa.pem"; + std::string PUBLIC_CERT_FILE_PATH = TEST_CONF_DIR "/public-key.client-rsa.pem"; + std::string PRIVATE_CERT_FILE_PATH = TEST_CONF_DIR "/private-key.client-rsa.pem"; std::shared_ptr keyReader = std::make_shared(PUBLIC_CERT_FILE_PATH, PRIVATE_CERT_FILE_PATH); @@ -989,6 +993,7 @@ TEST(ConsumerTest, testRedeliveryOfDecryptionFailedMessages) { auto consumer2ImplPtr = PulsarFriend::getConsumerImplPtr(consumer2); consumer2ImplPtr->unAckedMessageTrackerPtr_.reset(new UnAckedMessageTrackerEnabled( 100, 100, PulsarFriend::getClientImplPtr(client), static_cast(*consumer2ImplPtr))); + consumer2ImplPtr->unAckedMessageTrackerPtr_->start(); ConsumerConfiguration consConfig3; consConfig3.setConsumerType(pulsar::ConsumerShared); @@ -999,6 +1004,7 @@ TEST(ConsumerTest, testRedeliveryOfDecryptionFailedMessages) { auto consumer3ImplPtr = PulsarFriend::getConsumerImplPtr(consumer3); consumer3ImplPtr->unAckedMessageTrackerPtr_.reset(new UnAckedMessageTrackerEnabled( 100, 100, PulsarFriend::getClientImplPtr(client), static_cast(*consumer3ImplPtr))); + consumer3ImplPtr->unAckedMessageTrackerPtr_->start(); int numberOfMessages = 20; std::string msgContent = "msg-content"; @@ -1218,7 +1224,7 @@ TEST(ConsumerTest, testNegativeAcksTrackerClose) { consumer.close(); auto consumerImplPtr = PulsarFriend::getConsumerImplPtr(consumer); - ASSERT_TRUE(consumerImplPtr->negativeAcksTracker_.nackedMessages_.empty()); + ASSERT_TRUE(consumerImplPtr->negativeAcksTracker_->nackedMessages_.empty()); client.close(); } @@ -1401,4 +1407,87 @@ TEST(ConsumerTest, testNoListenerThreadBlocking) { client.close(); } +TEST(ConsumerTest, testCloseAfterUnsubscribe) { + Client client{lookupUrl}; + Consumer consumer; + ASSERT_EQ(ResultOk, client.subscribe("test-close-after-unsubscribe", "sub", consumer)); + ASSERT_EQ(ResultOk, consumer.unsubscribe()); + ASSERT_EQ(ResultOk, consumer.close()); +} + +TEST(ConsumerTest, testCloseAgainBeforeCloseDone) { + Client client{lookupUrl}; + Consumer consumer; + ASSERT_EQ(ResultOk, client.subscribe("test-close-again-before-close-done", "sub", consumer)); + auto done = std::make_shared(false); + auto result = std::make_shared>(ResultOk); + consumer.closeAsync([done, result](Result innerResult) { + result->store(innerResult); + done->store(true); + }); + ASSERT_EQ(ResultOk, consumer.close()); + ASSERT_FALSE(*done); + waitUntil(std::chrono::seconds(3), [done] { return done->load(); }); + ASSERT_EQ(ResultOk, *result); + ASSERT_TRUE(*done); +} + +inline std::string getConsumerName(const std::string& topic) { + boost::property_tree::ptree root; + const auto error = getTopicStats(topic, root); + if (!error.empty()) { + LOG_INFO(error); + return {}; + } + return root.get_child("subscriptions") + .get_child("sub") + .get_child("consumers") + .front() + .second.get("consumerName"); +} + +TEST(ConsumerTest, testConsumerName) { + Client client{lookupUrl}; + Consumer consumer; + ASSERT_TRUE(consumer.getConsumerName().empty()); + const auto topic1 = "consumer-test-consumer-name-1"; + const auto topic2 = "consumer-test-consumer-name-2"; + + // Default consumer name + ASSERT_EQ(ResultOk, client.subscribe(topic1, "sub", consumer)); + LOG_INFO("Random consumer name: " << consumer.getConsumerName()); + ASSERT_FALSE(consumer.getConsumerName().empty()); // a random name + ASSERT_EQ(consumer.getConsumerName(), getConsumerName(topic1)); + consumer.close(); + + // Single-topic consumer + ConsumerConfiguration conf; + const std::string consumerName = "custom-consumer"; + conf.setConsumerName(consumerName); + ASSERT_EQ(ResultOk, client.subscribe(topic1, "sub", conf, consumer)); + ASSERT_EQ(consumerName, consumer.getConsumerName()); + ASSERT_EQ(consumerName, getConsumerName(topic1)); + consumer.close(); + + // Multi-topics consumer + ASSERT_EQ(ResultOk, client.subscribe(std::vector{topic1, topic2}, "sub", conf, consumer)); + ASSERT_EQ(consumerName, consumer.getConsumerName()); + ASSERT_EQ(consumerName, getConsumerName(topic1)); + ASSERT_EQ(consumerName, getConsumerName(topic2)); + + client.close(); +} + +TEST(ConsumerTest, testSNIProxyConnect) { + ClientConfiguration clientConfiguration; + clientConfiguration.setProxyServiceUrl(lookupUrl); + clientConfiguration.setProxyProtocol(ClientConfiguration::SNI); + + Client client(lookupUrl, clientConfiguration); + const std::string topic = "testSNIProxy-" + std::to_string(time(nullptr)); + + Consumer consumer; + ASSERT_EQ(ResultOk, client.subscribe(topic, "test-sub", consumer)); + client.close(); +} } // namespace pulsar diff --git a/tests/CustomLoggerTest.cc b/tests/CustomLoggerTest.cc index dde4056d..22fbb4e6 100644 --- a/tests/CustomLoggerTest.cc +++ b/tests/CustomLoggerTest.cc @@ -107,3 +107,13 @@ TEST(CustomLoggerTest, testConsoleLoggerFactory) { ASSERT_FALSE(logger->isEnabled(Logger::LEVEL_WARN)); ASSERT_TRUE(logger->isEnabled(Logger::LEVEL_ERROR)); } + +TEST(CustomLoggerTest, testSetAndGetLoggerFactory) { + LoggerFactory *oldFactory = LogUtils::getLoggerFactory(); + LoggerFactory *newFactory = new ConsoleLoggerFactory(Logger::LEVEL_ERROR); + std::unique_ptr newFactoryPtr(newFactory); + LogUtils::setLoggerFactory(std::move(newFactoryPtr)); + ASSERT_NE(oldFactory, LogUtils::getLoggerFactory()); + ASSERT_EQ(newFactory, LogUtils::getLoggerFactory()); + LogUtils::resetLoggerFactory(); +} diff --git a/tests/DeadLetterQueueTest.cc b/tests/DeadLetterQueueTest.cc index 1a747cc3..06cd1964 100644 --- a/tests/DeadLetterQueueTest.cc +++ b/tests/DeadLetterQueueTest.cc @@ -270,6 +270,8 @@ TEST_P(DeadLetterQueueTest, testSendDLQTriggerByRedeliverUnacknowledgedMessages) ASSERT_EQ(msg.getPartitionKey(), "p-key"); ASSERT_EQ(msg.getOrderingKey(), "o-key"); ASSERT_EQ(msg.getProperty("pk-1"), "pv-1"); + ASSERT_EQ(msg.getMessageId().batchSize(), 0); + ASSERT_EQ(msg.getMessageId().batchIndex(), -1); ASSERT_TRUE(msg.getProperty(SYSTEM_PROPERTY_REAL_TOPIC).find(topic_)); ASSERT_FALSE(msg.getProperty(PROPERTY_ORIGIN_MESSAGE_ID).empty()); } diff --git a/tests/LegacyBuildTests.cmake b/tests/LegacyBuildTests.cmake new file mode 100644 index 00000000..c6f4e96c --- /dev/null +++ b/tests/LegacyBuildTests.cmake @@ -0,0 +1,80 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if (NOT PROTOC_PATH) + set(PROTOC_PATH protoc) +endif() + +set(LIB_AUTOGEN_DIR ${AUTOGEN_DIR}/tests) +file(MAKE_DIRECTORY ${LIB_AUTOGEN_DIR}) +include_directories(${LIB_AUTOGEN_DIR}) + +set(PROTO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto) +set(PROTO_SOURCES ${LIB_AUTOGEN_DIR}/Test.pb.cc ${LIB_AUTOGEN_DIR}/ExternalTest.pb.cc) +add_custom_command( + OUTPUT ${PROTO_SOURCES} + COMMAND ${PROTOC_PATH} -I ${PROTO_DIR} ${PROTO_DIR}/Test.proto ${PROTO_DIR}/ExternalTest.proto --cpp_out=${LIB_AUTOGEN_DIR}) + +set(PROTO_SOURCE_PADDING ${LIB_AUTOGEN_DIR}/PaddingDemo.pb.cc) +add_custom_command( + OUTPUT ${PROTO_SOURCE_PADDING} + COMMAND ${PROTOC_PATH} -I . ./PaddingDemo.proto --cpp_out=${LIB_AUTOGEN_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +set(PROTO_SOURCES ${PROTO_SOURCES} ${PROTO_SOURCE_PADDING}) + +include_directories(${LIB_AUTOGEN_DIR}) + +find_library(GMOCK_LIBRARY_PATH gmock) +find_library(GTEST_LIBRARY_PATH gtest) +find_library(GMOCKD_LIBRARY_PATH gmockd) +find_library(GTESTD_LIBRARY_PATH gtestd) +if (NOT GMOCKD_LIBRARY_PATH) + set(GMOCKD_LIBRARY_PATH ${GMOCK_LIBRARY_PATH}) +endif() +if (NOT GTESTD_LIBRARY_PATH) + set(GTESTD_LIBRARY_PATH ${GTEST_LIBRARY_PATH}) +endif() + +file(GLOB TEST_SOURCES *.cc c/*.cc) + +add_executable(pulsar-tests ${TEST_SOURCES} ${PROTO_SOURCES}) + +target_include_directories(pulsar-tests PRIVATE ${AUTOGEN_DIR}/lib) +target_compile_options(pulsar-tests PRIVATE -DTOKEN_PATH="${TOKEN_PATH}" -DTEST_CONF_DIR="${TEST_CONF_DIR}") +target_link_libraries(pulsar-tests ${CLIENT_LIBS} pulsarStatic $<$:${GMOCKD_LIBRARY_PATH}> $<$:${GTESTD_LIBRARY_PATH}> $<$>:${GMOCK_LIBRARY_PATH}> $<$>:${GTEST_LIBRARY_PATH}>) + +if (UNIX) + add_executable(ConnectionFailTest unix/ConnectionFailTest.cc HttpHelper.cc) + target_link_libraries(ConnectionFailTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) +endif () + +add_executable(BrokerMetadataTest brokermetadata/BrokerMetadataTest.cc) +target_link_libraries(BrokerMetadataTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) + +add_executable(Oauth2Test oauth2/Oauth2Test.cc) +target_compile_options(Oauth2Test PRIVATE -DTEST_CONF_DIR="${TEST_CONF_DIR}") +target_link_libraries(Oauth2Test ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) + +add_executable(ChunkDedupTest chunkdedup/ChunkDedupTest.cc HttpHelper.cc) +target_link_libraries(ChunkDedupTest ${CLIENT_LIBS} pulsarStatic ${GTEST_LIBRARY_PATH}) + +add_executable(ExtensibleLoadManagerTest extensibleLM/ExtensibleLoadManagerTest.cc HttpHelper.cc) +target_include_directories(ExtensibleLoadManagerTest PRIVATE ${AUTOGEN_DIR}/lib) +target_link_libraries(ExtensibleLoadManagerTest PRIVATE pulsarStatic ${GTEST_LIBRARY_PATH}) diff --git a/tests/LookupServiceTest.cc b/tests/LookupServiceTest.cc index 3457bd4d..924acd43 100644 --- a/tests/LookupServiceTest.cc +++ b/tests/LookupServiceTest.cc @@ -22,11 +22,14 @@ #include #include +#include #include +#include #include #include "HttpHelper.h" #include "PulsarFriend.h" +#include "PulsarWrapper.h" #include "lib/BinaryProtoLookupService.h" #include "lib/ClientConnection.h" #include "lib/ConnectionPool.h" @@ -48,7 +51,10 @@ namespace pulsar { class LookupServiceTest : public ::testing::TestWithParam { public: - void SetUp() override { client_ = Client{GetParam()}; } + void SetUp() override { + serviceUrl_ = GetParam(); + client_ = Client{serviceUrl_}; + } void TearDown() override { client_.close(); } template @@ -63,6 +69,7 @@ class LookupServiceTest : public ::testing::TestWithParam { } protected: + std::string serviceUrl_; Client client_{httpLookupUrl}; }; @@ -77,8 +84,7 @@ TEST(LookupServiceTest, basicLookup) { ClientConfiguration conf; ExecutorServiceProviderPtr ioExecutorProvider_(std::make_shared(1)); ConnectionPool pool_(conf, ioExecutorProvider_, authData, ""); - ServiceNameResolver serviceNameResolver(url); - BinaryProtoLookupService lookupService(serviceNameResolver, pool_, conf); + BinaryProtoLookupService lookupService(url, pool_, conf); TopicNamePtr topicName = TopicName::get("topic"); @@ -141,26 +147,24 @@ static void testMultiAddresses(LookupService& lookupService) { TEST(LookupServiceTest, testMultiAddresses) { ConnectionPool pool({}, std::make_shared(1), AuthFactory::Disabled(), ""); - ServiceNameResolver serviceNameResolver("pulsar://localhost,localhost:9999"); ClientConfiguration conf; - BinaryProtoLookupService binaryLookupService(serviceNameResolver, pool, conf); + BinaryProtoLookupService binaryLookupService("pulsar://localhost,localhost:9999", pool, conf); testMultiAddresses(binaryLookupService); // HTTPLookupService calls shared_from_this() internally, we must create a shared pointer to test - ServiceNameResolver serviceNameResolverForHttp("http://localhost,localhost:9999"); auto httpLookupServicePtr = std::make_shared( - std::ref(serviceNameResolverForHttp), ClientConfiguration{}, AuthFactory::Disabled()); + "http://localhost,localhost:9999", ClientConfiguration{}, AuthFactory::Disabled()); testMultiAddresses(*httpLookupServicePtr); } TEST(LookupServiceTest, testRetry) { auto executorProvider = std::make_shared(1); ConnectionPool pool({}, executorProvider, AuthFactory::Disabled(), ""); - ServiceNameResolver serviceNameResolver("pulsar://localhost:9999,localhost"); ClientConfiguration conf; auto lookupService = RetryableLookupService::create( - std::make_shared(serviceNameResolver, pool, conf), 30 /* seconds */, - executorProvider); + std::make_shared("pulsar://localhost:9999,localhost", pool, conf), + std::chrono::seconds(30), executorProvider); + ServiceNameResolver& serviceNameResolver = lookupService->getServiceNameResolver(); PulsarFriend::setServiceUrlIndex(serviceNameResolver, 0); auto topicNamePtr = TopicName::get("lookup-service-test-retry"); @@ -189,13 +193,13 @@ TEST(LookupServiceTest, testRetry) { TEST(LookupServiceTest, testTimeout) { auto executorProvider = std::make_shared(1); ConnectionPool pool({}, executorProvider, AuthFactory::Disabled(), ""); - ServiceNameResolver serviceNameResolver("pulsar://localhost:9990,localhost:9902,localhost:9904"); ClientConfiguration conf; constexpr int timeoutInSeconds = 2; auto lookupService = RetryableLookupService::create( - std::make_shared(serviceNameResolver, pool, conf), timeoutInSeconds, - executorProvider); + std::make_shared("pulsar://localhost:9990,localhost:9902,localhost:9904", + pool, conf), + std::chrono::seconds(timeoutInSeconds), executorProvider); auto topicNamePtr = TopicName::get("lookup-service-test-retry"); decltype(std::chrono::high_resolution_clock::now()) startTime; @@ -431,13 +435,38 @@ TEST_P(LookupServiceTest, testGetSchemaByVersion) { producer2.close(); } +TEST_P(LookupServiceTest, testGetSchemaTimeout) { + if (serviceUrl_.find("pulsar://") == std::string::npos) { + // HTTP request timeout can only be configured with seconds + return; + } + const auto topic = "lookup-service-test-get-schema-timeout"; + Producer producer; + ProducerConfiguration producerConf; + producerConf.setSchema(SchemaInfo(STRING, "", "")); + ASSERT_EQ(ResultOk, client_.createProducer(topic, producerConf, producer)); + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent("msg").build())); + client_.close(); + + ClientConfiguration conf; + PulsarWrapper::deref(conf).operationTimeout = std::chrono::nanoseconds(1); + client_ = Client{serviceUrl_, conf}; + auto promise = std::make_shared>(); + client_.getSchemaInfoAsync(topic, 0L, + [promise](Result result, const SchemaInfo&) { promise->set_value(result); }); + auto future = promise->get_future(); + ASSERT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready); + ASSERT_EQ(future.get(), ResultTimeout); + client_.close(); +} + INSTANTIATE_TEST_SUITE_P(Pulsar, LookupServiceTest, ::testing::Values(binaryLookupUrl, httpLookupUrl)); class BinaryProtoLookupServiceRedirectTestHelper : public BinaryProtoLookupService { public: - BinaryProtoLookupServiceRedirectTestHelper(ServiceNameResolver& serviceNameResolver, ConnectionPool& pool, + BinaryProtoLookupServiceRedirectTestHelper(const std::string& serviceUrl, ConnectionPool& pool, const ClientConfiguration& clientConfiguration) - : BinaryProtoLookupService(serviceNameResolver, pool, clientConfiguration) {} + : BinaryProtoLookupService(serviceUrl, pool, clientConfiguration) {} LookupResultFuture findBroker(const std::string& address, bool authoritative, const std::string& topic, size_t redirectCount) { @@ -452,14 +481,13 @@ TEST(LookupServiceTest, testRedirectionLimit) { conf.setMaxLookupRedirects(redirect_limit); ExecutorServiceProviderPtr ioExecutorProvider_(std::make_shared(1)); ConnectionPool pool_(conf, ioExecutorProvider_, authData, ""); - std::string url = "pulsar://localhost:6650"; - ServiceNameResolver serviceNameResolver(url); - BinaryProtoLookupServiceRedirectTestHelper lookupService(serviceNameResolver, pool_, conf); + string url = "pulsar://localhost:6650"; + BinaryProtoLookupServiceRedirectTestHelper lookupService(url, pool_, conf); const auto topicNamePtr = TopicName::get("topic"); for (auto idx = 0; idx < redirect_limit + 5; ++idx) { - auto future = - lookupService.findBroker(serviceNameResolver.resolveHost(), false, topicNamePtr->toString(), idx); + auto future = lookupService.findBroker(lookupService.getServiceNameResolver().resolveHost(), false, + topicNamePtr->toString(), idx); LookupService::LookupResult lookupResult; auto result = future.get(lookupResult); diff --git a/tests/MockClientImpl.h b/tests/MockClientImpl.h new file mode 100644 index 00000000..074808fc --- /dev/null +++ b/tests/MockClientImpl.h @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#pragma once + +#include +#include + +#include +#include +#include + +#include "lib/ClientImpl.h" + +namespace pulsar { + +class MockClientImpl : public ClientImpl { + public: + struct SyncOpResult { + Result result; + long timeMs; + }; + using PromisePtr = std::shared_ptr>; + MockClientImpl(const std::string& serviceUrl, ClientConfiguration conf = {}) + : ClientImpl(serviceUrl, conf) {} + + MOCK_METHOD((Future), getConnection, + (const std::string&, const std::string&, size_t), (override)); + + SyncOpResult createProducer(const std::string& topic) { + using namespace std::chrono; + auto start = high_resolution_clock::now(); + auto promise = createPromise(); + createProducerAsync(topic, {}, [&start, promise](Result result, Producer) { + auto timeMs = duration_cast(high_resolution_clock::now() - start).count(); + promise->set_value({result, timeMs}); + }); + return wait(promise); + } + + SyncOpResult subscribe(const std::string& topic) { + using namespace std::chrono; + auto start = std::chrono::high_resolution_clock::now(); + auto promise = createPromise(); + subscribeAsync(topic, "sub", {}, [&start, &promise](Result result, Consumer) { + auto timeMs = duration_cast(high_resolution_clock::now() - start).count(); + promise->set_value({result, timeMs}); + }); + return wait(promise); + } + + GetConnectionFuture getConnectionReal(const std::string& topic, size_t key) { + return ClientImpl::getConnection("", topic, key); + } + + Result close() { + auto promise = createPromise(); + closeAsync([promise](Result result) { promise->set_value({result, 0L}); }); + return wait(promise).result; + } + + private: + static PromisePtr createPromise() { return std::make_shared>(); } + + static SyncOpResult wait(const PromisePtr& promise) { + using namespace std::chrono; + auto future = promise->get_future(); + auto status = future.wait_for(std::chrono::seconds(10)); + if (status == std::future_status::ready) { + return future.get(); + } else { + return {ResultUnknownError, -1L}; + } + } +}; + +} // namespace pulsar diff --git a/tests/MultiTopicsConsumerTest.cc b/tests/MultiTopicsConsumerTest.cc new file mode 100644 index 00000000..5aae1eb9 --- /dev/null +++ b/tests/MultiTopicsConsumerTest.cc @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include + +#include + +#include "ThreadSafeMessages.h" +#include "lib/LogUtils.h" + +static const std::string lookupUrl = "pulsar://localhost:6650"; + +DECLARE_LOG_OBJECT() + +using namespace pulsar; + +extern std::string unique_str(); + +TEST(MultiTopicsConsumerTest, testSeekToNewerPosition) { + const std::string topicPrefix = "multi-topics-consumer-seek-to-newer-position"; + Client client{lookupUrl}; + std::vector topics{topicPrefix + unique_str(), topicPrefix + unique_str()}; + Producer producer1; + ASSERT_EQ(ResultOk, client.createProducer(topics[0], producer1)); + Producer producer2; + ASSERT_EQ(ResultOk, client.createProducer(topics[1], producer2)); + producer1.send(MessageBuilder().setContent("1-0").build()); + producer2.send(MessageBuilder().setContent("2-0").build()); + producer1.send(MessageBuilder().setContent("1-1").build()); + producer2.send(MessageBuilder().setContent("2-1").build()); + + Consumer consumer; + ConsumerConfiguration conf; + conf.setSubscriptionInitialPosition(InitialPositionEarliest); + ASSERT_EQ(ResultOk, client.subscribe(topics, "sub", conf, consumer)); + std::vector timestamps; + Message msg; + for (int i = 0; i < 4; i++) { + ASSERT_EQ(ResultOk, consumer.receive(msg, 3000)); + timestamps.emplace_back(msg.getPublishTimestamp()); + } + std::sort(timestamps.begin(), timestamps.end()); + const auto timestamp = timestamps[2]; + consumer.close(); + + ThreadSafeMessages messages{2}; + + // Test synchronous receive after seek + ASSERT_EQ(ResultOk, client.subscribe(topics, "sub-2", conf, consumer)); + consumer.seek(timestamp); + for (int i = 0; i < 2; i++) { + ASSERT_EQ(ResultOk, consumer.receive(msg, 3000)); + messages.add(msg); + } + ASSERT_EQ(messages.getSortedValues(), (std::vector{"1-1", "2-1"})); + consumer.close(); + + // Test asynchronous receive after seek + ASSERT_EQ(ResultOk, client.subscribe(topics, "sub-3", conf, consumer)); + messages.clear(); + consumer.seek(timestamp); + for (int i = 0; i < 2; i++) { + consumer.receiveAsync([&messages](Result result, const Message& msg) { + if (result == ResultOk) { + messages.add(msg); + } else { + LOG_ERROR("Failed to receive: " << result); + } + }); + } + ASSERT_TRUE(messages.wait(std::chrono::seconds(3))); + ASSERT_EQ(messages.getSortedValues(), (std::vector{"1-1", "2-1"})); + consumer.close(); + + // Test message listener + conf.setMessageListener([&messages](Consumer consumer, Message msg) { messages.add(msg); }); + messages.clear(); + messages.setMinNumMsgs(4); + ASSERT_EQ(ResultOk, client.subscribe(topics, "sub-4", conf, consumer)); + ASSERT_TRUE(messages.wait(std::chrono::seconds(3))); + ASSERT_EQ(messages.getSortedValues(), (std::vector{"1-0", "1-1", "2-0", "2-1"})); + messages.clear(); + messages.setMinNumMsgs(2); + consumer.seek(timestamp); + ASSERT_TRUE(messages.wait(std::chrono::seconds(3))); + ASSERT_EQ(messages.getSortedValues(), (std::vector{"1-1", "2-1"})); + + client.close(); +} diff --git a/tests/ProducerTest.cc b/tests/ProducerTest.cc index eeda2b47..21e491de 100644 --- a/tests/ProducerTest.cc +++ b/tests/ProducerTest.cc @@ -243,6 +243,8 @@ TEST_P(ProducerTest, testMaxMessageSize) { ASSERT_EQ(ResultMessageTooBig, producer.send(MessageBuilder().setContent(std::string(maxMessageSize, 'b')).build())); + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent(msg).build())); + client.close(); } @@ -476,7 +478,23 @@ TEST_P(ProducerTest, testFlushBatch) { producer.sendAsync(msg, cb); } - producer.flush(); + auto assertFlushCallbackOnce = [&producer] { + Latch latch{1}; + std::mutex mutex; + std::vector results; + producer.flushAsync([&](Result result) { + { + std::lock_guard lock{mutex}; + results.emplace_back(result); + } + latch.countdown(); + }); + latch.wait(); + std::lock_guard lock{mutex}; + ASSERT_EQ(results, (std::vector{ResultOk})); + }; + + assertFlushCallbackOnce(); ASSERT_EQ(needCallBack.load(), 0); producer.close(); @@ -494,7 +512,7 @@ TEST_P(ProducerTest, testFlushBatch) { producer.sendAsync(msg, cb2); } - producer.flush(); + assertFlushCallbackOnce(); ASSERT_EQ(needCallBack2.load(), 0); producer.close(); @@ -618,4 +636,51 @@ TEST(ProducerTest, testNoDeadlockWhenClosingPartitionedProducerAfterPartitionsUp client.close(); } +TEST(ProducerTest, testReconnectMultiConnectionsPerBroker) { + ClientConfiguration conf; + conf.setConnectionsPerBroker(10); + + Client client(serviceUrl, conf); + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer("producer-test-reconnect-twice", producer)); + + for (int i = 0; i < 5; i++) { + ASSERT_TRUE(PulsarFriend::reconnect(producer)) << "i: " << i; + } + + client.close(); +} + +TEST(ProducerTest, testFailedToCreateNewPartitionProducer) { + const std::string topic = + "public/default/testFailedToCreateNewPartitionProducer" + std::to_string(time(nullptr)); + std::string topicOperateUrl = adminUrl + "admin/v2/persistent/" + topic + "/partitions"; + + int res = makePutRequest(topicOperateUrl, "2"); + ASSERT_TRUE(res == 204 || res == 409) << "res: " << res; + + ClientConfiguration clientConf; + clientConf.setPartititionsUpdateInterval(1); + Client client(serviceUrl, clientConf); + ProducerConfiguration conf; + Producer producer; + client.createProducer(topic, conf, producer); + ASSERT_TRUE(waitUntil(std::chrono::seconds(1), [&producer]() -> bool { return producer.isConnected(); })); + + PartitionedProducerImpl& partitionedProducer = PulsarFriend::getPartitionedProducerImpl(producer); + PulsarFriend::updatePartitions(partitionedProducer, 3); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + auto& newProducer = PulsarFriend::getInternalProducerImpl(producer, 2); + ASSERT_FALSE(newProducer.isConnected()); // should fail with topic not found + + res = makePostRequest(topicOperateUrl, "3"); + ASSERT_TRUE(res == 204 || res == 409) << "res: " << res; + + ASSERT_TRUE( + waitUntil(std::chrono::seconds(5), [&newProducer]() -> bool { return newProducer.isConnected(); })); + + producer.close(); + client.close(); +} + INSTANTIATE_TEST_CASE_P(Pulsar, ProducerTest, ::testing::Values(true, false)); diff --git a/lib/MultiResultCallback.h b/tests/PulsarAdminHelper.h similarity index 54% rename from lib/MultiResultCallback.h rename to tests/PulsarAdminHelper.h index 739bc4a6..944c7b8d 100644 --- a/lib/MultiResultCallback.h +++ b/tests/PulsarAdminHelper.h @@ -18,34 +18,25 @@ */ #pragma once -#include // for ResultCallback +#include +#include -#include -#include +#include "HttpHelper.h" namespace pulsar { -class MultiResultCallback { - public: - MultiResultCallback(ResultCallback callback, int numToComplete) - : callback_(callback), - numToComplete_(numToComplete), - numCompletedPtr_(std::make_shared(0)) {} - - void operator()(Result result) { - if (result == ResultOk) { - if (++(*numCompletedPtr_) == numToComplete_) { - callback_(result); - } - } else { - callback_(result); - } +inline std::string getTopicStats(const std::string& topic, boost::property_tree::ptree& root) { + const auto url = "http://localhost:8080/admin/v2/persistent/public/default/" + topic + "/stats"; + std::string responseData; + int code = makeGetRequest(url, responseData); + if (code != 200) { + return url + " failed: " + std::to_string(code); } - private: - ResultCallback callback_; - const int numToComplete_; - const std::shared_ptr numCompletedPtr_; -}; + std::stringstream stream; + stream << responseData; + boost::property_tree::read_json(stream, root); + return ""; +} } // namespace pulsar diff --git a/tests/PulsarFriend.h b/tests/PulsarFriend.h index c25b1406..e7084050 100644 --- a/tests/PulsarFriend.h +++ b/tests/PulsarFriend.h @@ -21,10 +21,12 @@ #include +#include "WaitUtils.h" #include "lib/ClientConnection.h" #include "lib/ClientImpl.h" #include "lib/ConsumerConfigurationImpl.h" #include "lib/ConsumerImpl.h" +#include "lib/LookupService.h" #include "lib/MessageImpl.h" #include "lib/MultiTopicsConsumerImpl.h" #include "lib/NamespaceName.h" @@ -37,6 +39,7 @@ using std::string; namespace pulsar { +using ClientConnectionWeakPtr = std::weak_ptr; class PulsarFriend { public: static ProducerStatsImplPtr getProducerStatsPtr(Producer producer) { @@ -139,6 +142,8 @@ class PulsarFriend { return connections; } + static ExecutorServicePtr getExecutor(const ClientConnection& cnx) { return cnx.executor_; } + static std::vector getProducers(const ClientConnection& cnx) { std::vector producers; std::lock_guard lock(cnx.mutex_); @@ -163,18 +168,26 @@ class PulsarFriend { static ClientConnectionWeakPtr getClientConnection(HandlerBase& handler) { return handler.connection_; } + static std::string getConnectionPhysicalAddress(HandlerBase& handler) { + auto cnx = handler.getCnx().lock(); + if (cnx) { + return cnx->physicalAddress_; + } + return ""; + } + static void setClientConnection(HandlerBase& handler, ClientConnectionWeakPtr conn) { handler.connection_ = conn; } - static boost::posix_time::ptime& getFirstBackoffTime(Backoff& backoff) { + static auto getFirstBackoffTime(Backoff& backoff) -> decltype(backoff.firstBackoffTime_)& { return backoff.firstBackoffTime_; } static void setServiceUrlIndex(ServiceNameResolver& resolver, size_t index) { resolver.index_ = index; } static void setServiceUrlIndex(const Client& client, size_t index) { - setServiceUrlIndex(client.impl_->serviceNameResolver_, index); + setServiceUrlIndex(client.impl_->lookupServicePtr_->getServiceNameResolver(), index); } static proto::MessageMetadata& getMessageMetadata(Message& message) { return message.impl_->metadata; } @@ -197,6 +210,13 @@ class PulsarFriend { lookupData->setPartitions(newPartitions); partitionedProducer.handleGetPartitions(ResultOk, lookupData); } + + static bool reconnect(Producer producer) { + auto producerImpl = std::dynamic_pointer_cast(producer.impl_); + producerImpl->disconnectProducer(); + return waitUntil(std::chrono::seconds(3), + [producerImpl] { return !producerImpl->getCnx().expired(); }); + } }; } // namespace pulsar diff --git a/lib/TimeUtils.cc b/tests/PulsarWrapper.h similarity index 72% rename from lib/TimeUtils.cc rename to tests/PulsarWrapper.h index 7eecb86b..87626e1c 100644 --- a/lib/TimeUtils.cc +++ b/tests/PulsarWrapper.h @@ -16,17 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +#pragma once -#include "TimeUtils.h" +#include "lib/ClientConfigurationImpl.h" +#include "pulsar/ClientConfiguration.h" namespace pulsar { -ptime TimeUtils::now() { return microsec_clock::universal_time(); } +class PulsarWrapper { + public: + static ClientConfigurationImpl& deref(ClientConfiguration conf) { return *conf.impl_; } +}; -int64_t TimeUtils::currentTimeMillis() { - static ptime time_t_epoch(boost::gregorian::date(1970, 1, 1)); - - time_duration diff = now() - time_t_epoch; - return diff.total_milliseconds(); -} -} // namespace pulsar \ No newline at end of file +} // namespace pulsar diff --git a/tests/ReaderTest.cc b/tests/ReaderTest.cc index ac2fa234..92fdf624 100644 --- a/tests/ReaderTest.cc +++ b/tests/ReaderTest.cc @@ -700,9 +700,16 @@ TEST_P(ReaderTest, testReceiveAfterSeek) { client.close(); } -TEST(ReaderSeekTest, testSeekForMessageId) { - Client client(serviceUrl); +class ReaderSeekTest : public ::testing::TestWithParam { + public: + void SetUp() override { client = Client{serviceUrl}; } + + void TearDown() override { client.close(); } + + Client client{serviceUrl}; +}; +TEST_F(ReaderSeekTest, testSeekForMessageId) { const std::string topic = "test-seek-for-message-id-" + std::to_string(time(nullptr)); Producer producer; @@ -752,4 +759,134 @@ TEST(ReaderSeekTest, testSeekForMessageId) { producer.close(); } +#define EXPECT_HAS_MESSAGE_AVAILABLE(reader, expected) \ + { \ + bool actual; \ + ASSERT_EQ(ResultOk, reader.hasMessageAvailable(actual)); \ + EXPECT_EQ(actual, (expected)); \ + } + +TEST_F(ReaderSeekTest, testStartAtLatestMessageId) { + const std::string topic = "test-seek-latest-message-id-" + std::to_string(time(nullptr)); + + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer(topic, producer)); + + MessageId id; + for (int i = 0; i < 10; i++) { + ASSERT_EQ(ResultOk, + producer.send(MessageBuilder().setContent("msg-" + std::to_string(i)).build(), id)); + } + + Reader readerExclusive; + ASSERT_EQ(ResultOk, + client.createReader(topic, MessageId::latest(), ReaderConfiguration(), readerExclusive)); + + Reader readerInclusive; + ASSERT_EQ(ResultOk, + client.createReader(topic, MessageId::latest(), + ReaderConfiguration().setStartMessageIdInclusive(true), readerInclusive)); + + EXPECT_HAS_MESSAGE_AVAILABLE(readerExclusive, false); + EXPECT_HAS_MESSAGE_AVAILABLE(readerInclusive, true); + + Message msg; + ASSERT_EQ(ResultOk, readerInclusive.readNext(msg, 3000)); + ASSERT_EQ(msg.getDataAsString(), "msg-9"); + + readerInclusive.seek(0L); + EXPECT_HAS_MESSAGE_AVAILABLE(readerInclusive, true); + ASSERT_EQ(ResultOk, readerInclusive.readNext(msg, 3000)); + ASSERT_EQ(msg.getDataAsString(), "msg-0"); + + readerExclusive.close(); + readerInclusive.close(); + producer.close(); +} + +TEST_F(ReaderSeekTest, testSeekInProgress) { + const auto topic = "test-seek-in-progress-" + std::to_string(time(nullptr)); + Reader reader; + ASSERT_EQ(ResultOk, client.createReader(topic, MessageId::earliest(), {}, reader)); + + reader.seekAsync(MessageId::earliest(), [](Result) {}); + Promise promise; + reader.seekAsync(MessageId::earliest(), [promise](Result result) { promise.setValue(result); }); + Result result; + promise.getFuture().get(result); + ASSERT_EQ(result, ResultNotAllowedError); +} + +TEST_P(ReaderSeekTest, testHasMessageAvailableAfterSeekToEnd) { + const auto topic = "test-has-message-available-after-seek-to-end-" + std::to_string(time(nullptr)); + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer(topic, producer)); + Reader reader; + ASSERT_EQ(ResultOk, client.createReader(topic, MessageId::earliest(), {}, reader)); + + producer.send(MessageBuilder().setContent("msg-0").build()); + producer.send(MessageBuilder().setContent("msg-1").build()); + + bool hasMessageAvailable; + if (GetParam()) { + ASSERT_EQ(ResultOk, reader.hasMessageAvailable(hasMessageAvailable)); + } + + ASSERT_EQ(ResultOk, reader.seek(MessageId::latest())); + ASSERT_EQ(ResultOk, reader.hasMessageAvailable(hasMessageAvailable)); + ASSERT_FALSE(hasMessageAvailable); + + producer.send(MessageBuilder().setContent("msg-2").build()); + ASSERT_EQ(ResultOk, reader.hasMessageAvailable(hasMessageAvailable)); + ASSERT_TRUE(hasMessageAvailable); + + Message msg; + ASSERT_EQ(ResultOk, reader.readNext(msg, 1000)); + ASSERT_EQ("msg-2", msg.getDataAsString()); + + // Test the 2nd seek + ASSERT_EQ(ResultOk, reader.seek(MessageId::latest())); + ASSERT_EQ(ResultOk, reader.hasMessageAvailable(hasMessageAvailable)); + ASSERT_FALSE(hasMessageAvailable); +} + +TEST_P(ReaderSeekTest, testHasMessageAvailableAfterSeekTimestamp) { + using namespace std::chrono; + const auto topic = "test-has-message-available-after-seek-timestamp-" + std::to_string(time(nullptr)); + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer(topic, producer)); + MessageId sentMsgId; + const auto timestampBeforeSend = + duration_cast(system_clock::now().time_since_epoch()).count(); + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent("msg").build(), sentMsgId)); + + auto createReader = [this, &topic](Reader& reader, const MessageId& msgId) { + ASSERT_EQ(ResultOk, client.createReader(topic, msgId, {}, reader)); + if (GetParam()) { + if (msgId == MessageId::earliest()) { + EXPECT_HAS_MESSAGE_AVAILABLE(reader, true); + } else { + EXPECT_HAS_MESSAGE_AVAILABLE(reader, false); + } + } + }; + + std::vector msgIds{MessageId::earliest(), sentMsgId, MessageId::latest()}; + + for (auto&& msgId : msgIds) { + Reader reader; + createReader(reader, msgId); + ASSERT_EQ(ResultOk, + reader.seek(duration_cast(system_clock::now().time_since_epoch()).count())); + EXPECT_HAS_MESSAGE_AVAILABLE(reader, false); + } + for (auto&& msgId : msgIds) { + Reader reader; + createReader(reader, msgId); + ASSERT_EQ(ResultOk, reader.seek(timestampBeforeSend)); + EXPECT_HAS_MESSAGE_AVAILABLE(reader, true); + } +} + INSTANTIATE_TEST_SUITE_P(Pulsar, ReaderTest, ::testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(Pulsar, ReaderSeekTest, ::testing::Values(true, false)); diff --git a/tests/RetryableOperationCacheTest.cc b/tests/RetryableOperationCacheTest.cc index ea1eb695..2a6948e3 100644 --- a/tests/RetryableOperationCacheTest.cc +++ b/tests/RetryableOperationCacheTest.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include "lib/RetryableOperationCache.h" @@ -82,7 +83,7 @@ class RetryableOperationCacheTest : public ::testing::Test { using namespace pulsar; TEST_F(RetryableOperationCacheTest, testRetry) { - auto cache = RetryableOperationCache::create(provider_, 30 /* seconds */); + auto cache = RetryableOperationCache::create(provider_, std::chrono::seconds(30)); for (int i = 0; i < 10; i++) { futures_.emplace_back(cache->run("key-" + std::to_string(i), CountdownFunc{i * 100})); } @@ -94,7 +95,7 @@ TEST_F(RetryableOperationCacheTest, testRetry) { } TEST_F(RetryableOperationCacheTest, testCache) { - auto cache = RetryableOperationCache::create(provider_, 30 /* seconds */); + auto cache = RetryableOperationCache::create(provider_, std::chrono::seconds(30)); constexpr int numKeys = 5; for (int i = 0; i < 100; i++) { futures_.emplace_back(cache->run("key-" + std::to_string(i % numKeys), CountdownFunc{i * 100})); @@ -107,7 +108,7 @@ TEST_F(RetryableOperationCacheTest, testCache) { } TEST_F(RetryableOperationCacheTest, testTimeout) { - auto cache = RetryableOperationCache::create(provider_, 1 /* seconds */); + auto cache = RetryableOperationCache::create(provider_, std::chrono::seconds(1)); auto future = cache->run("key", CountdownFunc{0, 1000 /* retry count */}); try { wait(future); @@ -118,7 +119,7 @@ TEST_F(RetryableOperationCacheTest, testTimeout) { } TEST_F(RetryableOperationCacheTest, testClear) { - auto cache = RetryableOperationCache::create(provider_, 30 /* seconds */); + auto cache = RetryableOperationCache::create(provider_, std::chrono::seconds(30)); for (int i = 0; i < 10; i++) { futures_.emplace_back(cache->run("key-" + std::to_string(i), CountdownFunc{100})); } diff --git a/tests/RoundRobinMessageRouterTest.cc b/tests/RoundRobinMessageRouterTest.cc index 56a76050..145c45ae 100644 --- a/tests/RoundRobinMessageRouterTest.cc +++ b/tests/RoundRobinMessageRouterTest.cc @@ -31,7 +31,7 @@ TEST(RoundRobinMessageRouterTest, onePartition) { const int numPartitions = 1; RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, false, 1, 1, - boost::posix_time::milliseconds(0)); + std::chrono::milliseconds(0)); Message msg1 = MessageBuilder().setPartitionKey("my-key-1").setContent("one").build(); Message msg2 = MessageBuilder().setPartitionKey("my-key-2").setContent("two").build(); @@ -49,7 +49,7 @@ TEST(RoundRobinMessageRouterTest, sameKey) { const int numPartitions = 13; RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, false, 1, 1, - boost::posix_time::milliseconds(0)); + std::chrono::milliseconds(0)); Message msg1 = MessageBuilder().setPartitionKey("my-key").setContent("one").build(); Message msg2 = MessageBuilder().setPartitionKey("my-key").setContent("two").build(); @@ -63,7 +63,7 @@ TEST(RoundRobinMessageRouterTest, batchingDisabled) { const int numPartitions = 13; RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, false, 1, 1, - boost::posix_time::milliseconds(0)); + std::chrono::milliseconds(0)); Message msg1 = MessageBuilder().setContent("one").build(); Message msg2 = MessageBuilder().setContent("two").build(); @@ -77,7 +77,7 @@ TEST(RoundRobinMessageRouterTest, batchingEnabled) { const int numPartitions = 13; RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 1000, 100000, - boost::posix_time::seconds(1)); + std::chrono::seconds(1)); int p = -1; for (int i = 0; i < 100; i++) { @@ -96,7 +96,7 @@ TEST(RoundRobinMessageRouterTest, maxDelay) { const int numPartitions = 13; RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 1000, 100000, - boost::posix_time::seconds(1)); + std::chrono::seconds(1)); int p1 = -1; for (int i = 0; i < 100; i++) { @@ -132,8 +132,7 @@ TEST(RoundRobinMessageRouterTest, maxDelay) { TEST(RoundRobinMessageRouterTest, maxNumberOfMessages) { const int numPartitions = 13; - RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 2, 1000, - boost::posix_time::seconds(1)); + RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 2, 1000, std::chrono::seconds(1)); Message msg1 = MessageBuilder().setContent("one").build(); Message msg2 = MessageBuilder().setContent("two").build(); @@ -150,8 +149,7 @@ TEST(RoundRobinMessageRouterTest, maxNumberOfMessages) { TEST(RoundRobinMessageRouterTest, maxBatchSize) { const int numPartitions = 13; - RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 10, 8, - boost::posix_time::seconds(1)); + RoundRobinMessageRouter router(ProducerConfiguration::BoostHash, true, 10, 8, std::chrono::seconds(1)); Message msg1 = MessageBuilder().setContent("one").build(); Message msg2 = MessageBuilder().setContent("two").build(); diff --git a/tests/ThreadSafeMessages.h b/tests/ThreadSafeMessages.h new file mode 100644 index 00000000..f30dbf02 --- /dev/null +++ b/tests/ThreadSafeMessages.h @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace pulsar { + +// When we receive messages in the message listener or the callback of receiveAsync(), we need to verify the +// received messages in the test thread. This class is a helper class for thread-safe access to the messages. +class ThreadSafeMessages { + public: + ThreadSafeMessages(size_t minNumMsgs) : minNumMsgs_(minNumMsgs) {} + + template + bool wait(Duration duration) { + std::unique_lock lock{mutex_}; + return cond_.wait_for(lock, duration, [this] { return msgs_.size() >= minNumMsgs_; }); + } + + void add(const Message& msg) { + std::lock_guard lock{mutex_}; + msgs_.emplace_back(msg); + if (msgs_.size() >= minNumMsgs_) { + cond_.notify_all(); + } + } + + void clear() { + std::lock_guard lock{mutex_}; + msgs_.clear(); + } + + std::vector getSortedValues() const { + std::unique_lock lock{mutex_}; + std::vector values(msgs_.size()); + std::transform(msgs_.cbegin(), msgs_.cend(), values.begin(), + [](const Message& msg) { return msg.getDataAsString(); }); + lock.unlock(); + std::sort(values.begin(), values.end()); + return values; + } + + void setMinNumMsgs(size_t minNumMsgs) noexcept { minNumMsgs_ = minNumMsgs; } + + private: + std::atomic_size_t minNumMsgs_; + std::vector msgs_; + mutable std::mutex mutex_; + mutable std::condition_variable cond_; +}; + +} // namespace pulsar diff --git a/tests/blue-green/docker-compose.yml b/tests/blue-green/docker-compose.yml new file mode 100644 index 00000000..b2d22ebf --- /dev/null +++ b/tests/blue-green/docker-compose.yml @@ -0,0 +1,152 @@ +version: '3' +networks: + green-pulsar: + driver: bridge +services: + # Start ZooKeeper + zookeeper: + image: apachepulsar/pulsar:latest + container_name: green-zookeeper + restart: on-failure + networks: + - green-pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + command: > + bash -c "bin/apply-config-from-env.py conf/zookeeper.conf && \ + bin/generate-zookeeper-config.sh conf/zookeeper.conf && \ + exec bin/pulsar zookeeper" + healthcheck: + test: ["CMD", "bin/pulsar-zookeeper-ruok.sh"] + interval: 10s + timeout: 5s + retries: 30 + + # Initialize cluster metadata + pulsar-init: + container_name: green-pulsar-init + hostname: pulsar-init + image: apachepulsar/pulsar:latest + networks: + - green-pulsar + environment: + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + command: > + bin/pulsar initialize-cluster-metadata \ + --cluster cluster-a \ + --zookeeper zookeeper:2181 \ + --configuration-store zookeeper:2181 \ + --web-service-url http://broker-1:8080 \ + --broker-service-url pulsar://broker-1:6650 + depends_on: + zookeeper: + condition: service_healthy + + # Start bookie + bookie: + image: apachepulsar/pulsar:latest + container_name: green-bookie + restart: on-failure + networks: + - green-pulsar + environment: + - clusterName=cluster-a + - zkServers=zookeeper:2181 + - metadataServiceUri=metadata-store:zk:zookeeper:2181 + - advertisedAddress=bookie + - BOOKIE_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + depends_on: + zookeeper: + condition: service_healthy + pulsar-init: + condition: service_completed_successfully + command: bash -c "bin/apply-config-from-env.py conf/bookkeeper.conf && exec bin/pulsar bookie" + + proxy: + image: apachepulsar/pulsar:latest + container_name: green-proxy + hostname: proxy + restart: on-failure + networks: + - green-pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + ports: + - "8081:8080" + - "6651:6650" + depends_on: + broker-1: + condition: service_started + broker-2: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/proxy.conf && exec bin/pulsar proxy" + + # Start broker 1 + broker-1: + image: apachepulsar/pulsar:latest + container_name: green-broker-1 + hostname: broker-1 + restart: on-failure + networks: + - green-pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - managedLedgerDefaultEnsembleSize=1 + - managedLedgerDefaultWriteQuorum=1 + - managedLedgerDefaultAckQuorum=1 + - advertisedAddress=green-broker-1 + - internalListenerName=internal + - advertisedListeners=internal:pulsar://green-broker-1:6650 + - PULSAR_MEM=-Xms256m -Xmx256m -XX:MaxDirectMemorySize=56m + # Load Manager. Here uses the extensible load balancer, sets the unloading strategy to TransferShedder, and enables debug mode. + - loadManagerClassName=org.apache.pulsar.broker.loadbalance.extensions.ExtensibleLoadManagerImpl + - loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.extensions.scheduler.TransferShedder + - loadBalancerSheddingEnabled=false + - loadBalancerDebugModeEnabled=true + - brokerServiceCompactionThresholdInBytes=1000000 + - PULSAR_PREFIX_defaultNumberOfNamespaceBundles=1 + depends_on: + zookeeper: + condition: service_healthy + bookie: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/broker.conf && exec bin/pulsar broker" + + # Start broker 2 + broker-2: + image: apachepulsar/pulsar:latest + container_name: green-broker-2 + hostname: broker-2 + restart: on-failure + networks: + - green-pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - managedLedgerDefaultEnsembleSize=1 + - managedLedgerDefaultWriteQuorum=1 + - managedLedgerDefaultAckQuorum=1 + - advertisedAddress=green-broker-2 + - internalListenerName=internal + - advertisedListeners=internal:pulsar://green-broker-2:6650 + - PULSAR_MEM=-Xms256m -Xmx256m -XX:MaxDirectMemorySize=56m + # Load Manager. Here uses the extensible load balancer, sets the unloading strategy to TransferShedder, and enables debug mode. + - loadManagerClassName=org.apache.pulsar.broker.loadbalance.extensions.ExtensibleLoadManagerImpl + - loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.extensions.scheduler.TransferShedder + - loadBalancerSheddingEnabled=false + - loadBalancerDebugModeEnabled=true + - brokerServiceCompactionThresholdInBytes=1000000 + - PULSAR_PREFIX_defaultNumberOfNamespaceBundles=1 + depends_on: + zookeeper: + condition: service_healthy + bookie: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/broker.conf && exec bin/pulsar broker" \ No newline at end of file diff --git a/tests/c/c_BasicEndToEndTest.cc b/tests/c/c_BasicEndToEndTest.cc index 04aa1dcd..e3ff19aa 100644 --- a/tests/c/c_BasicEndToEndTest.cc +++ b/tests/c/c_BasicEndToEndTest.cc @@ -109,7 +109,7 @@ TEST(c_BasicEndToEndTest, testAsyncProduceConsume) { delete receive_ctx.data; ASSERT_EQ(pulsar_result_Ok, pulsar_consumer_unsubscribe(consumer)); - ASSERT_EQ(pulsar_result_AlreadyClosed, pulsar_consumer_close(consumer)); + ASSERT_EQ(pulsar_result_Ok, pulsar_consumer_close(consumer)); ASSERT_EQ(pulsar_result_Ok, pulsar_producer_close(producer)); ASSERT_EQ(pulsar_result_Ok, pulsar_client_close(client)); diff --git a/tests/c/c_ClientConfigurationTest.cc b/tests/c/c_ClientConfigurationTest.cc index f22f28a4..3fec51a5 100644 --- a/tests/c/c_ClientConfigurationTest.cc +++ b/tests/c/c_ClientConfigurationTest.cc @@ -28,4 +28,7 @@ TEST(C_ClientConfigurationTest, testCApiConfig) { ASSERT_STREQ(pulsar_client_configuration_get_tls_private_key_file_path(conf), "private.key"); ASSERT_STREQ(pulsar_client_configuration_get_tls_certificate_file_path(conf), "certificate.pem"); + + pulsar_client_configuration_set_listener_name(conf, "listenerName"); + ASSERT_STREQ(pulsar_client_configuration_get_listener_name(conf), "listenerName"); } diff --git a/tests/chunkdedup/ChunkDedupTest.cc b/tests/chunkdedup/ChunkDedupTest.cc index 609511f2..4c9c28e7 100644 --- a/tests/chunkdedup/ChunkDedupTest.cc +++ b/tests/chunkdedup/ChunkDedupTest.cc @@ -18,7 +18,9 @@ */ #include #include +#include +#include "../HttpHelper.h" #include "lib/Latch.h" #include "lib/LogUtils.h" @@ -47,6 +49,30 @@ TEST(ChunkDedupTest, testSendChunks) { client.close(); } +TEST(ChunkDedupTest, testLazyPartitionedProducer) { + std::string topic = "test-lazy-partitioned-producer-" + std::to_string(time(nullptr)); + Client client{"pulsar://localhost:6650"}; + ProducerConfiguration conf; + conf.setLazyStartPartitionedProducers(true); + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer(topic, conf, producer)); + + constexpr int numPartitions = 3; + int res = + makePutRequest("http://localhost:8080/admin/v2/persistent/public/default/" + topic + "/partitions", + std::to_string(numPartitions)); + ASSERT_TRUE(res == 204 || res == 409) << "res: " << res; + + for (int i = 0; i < 10; i++) { + const auto key = std::to_string(i % numPartitions); + MessageId msgId; + producer.send(MessageBuilder().setPartitionKey(key).setContent("msg-" + std::to_string(i)).build(), + msgId); + ASSERT_TRUE(msgId.ledgerId() >= 0 && msgId.entryId() >= 0) << "i: " << i << ", msgId: " << msgId; + } + client.close(); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/tests/extensibleLM/ExtensibleLoadManagerTest.cc b/tests/extensibleLM/ExtensibleLoadManagerTest.cc new file mode 100644 index 00000000..c7e5aa0f --- /dev/null +++ b/tests/extensibleLM/ExtensibleLoadManagerTest.cc @@ -0,0 +1,265 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// Run `docker-compose up -d` to set up the test environment for this test. +#include + +#include +#include + +#include "include/pulsar/Client.h" +#include "lib/LogUtils.h" +#include "lib/Semaphore.h" +#include "lib/TimeUtils.h" +#include "tests/HttpHelper.h" +#include "tests/PulsarFriend.h" + +DECLARE_LOG_OBJECT() + +using namespace pulsar; + +bool checkTime() { + const static auto start = std::chrono::high_resolution_clock::now(); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start).count(); + return duration < 300 * 1000; +} + +TEST(ExtensibleLoadManagerTest, testPubSubWhileUnloading) { + constexpr auto maxWaitTime = std::chrono::seconds(5); + constexpr long maxWaitTimeMs = maxWaitTime.count() * 1000L; + + const static std::string blueAdminUrl = "http://localhost:8080/"; + const static std::string greenAdminUrl = "http://localhost:8081/"; + const static std::string topicNameSuffix = std::to_string(time(NULL)); + const static std::string topicName = "persistent://public/unload-test/topic-" + topicNameSuffix; + + ASSERT_TRUE(waitUntil(std::chrono::seconds(60), [&] { + std::string url = blueAdminUrl + "admin/v2/clusters/cluster-a/migrate?migrated=false"; + int res = makePostRequest(url, R"( + { + "serviceUrl": "http://localhost:8081", + "serviceUrlTls":"https://localhost:8085", + "brokerServiceUrl": "pulsar://localhost:6651", + "brokerServiceUrlTls": "pulsar+ssl://localhost:6655" + })"); + LOG_INFO("res:" << res); + return res == 200; + })); + + ASSERT_TRUE(waitUntil(std::chrono::seconds(60), [&] { + std::string url = blueAdminUrl + "admin/v2/namespaces/public/unload-test?bundles=1"; + int res = makePutRequest(url, ""); + return res == 204 || res == 409; + })); + + ASSERT_TRUE(waitUntil(std::chrono::seconds(60), [&] { + std::string url = greenAdminUrl + "admin/v2/namespaces/public/unload-test?bundles=1"; + int res = makePutRequest(url, ""); + return res == 204 || res == 409; + })); + + ClientConfiguration conf; + conf.setIOThreads(8); + Client client{"pulsar://localhost:6650", conf}; + Producer producer; + ProducerConfiguration producerConfiguration; + Result producerResult = client.createProducer(topicName, producerConfiguration, producer); + ASSERT_EQ(producerResult, ResultOk); + Consumer consumer; + Result consumerResult = client.subscribe(topicName, "sub", consumer); + ASSERT_EQ(consumerResult, ResultOk); + + Semaphore unloadSemaphore(0); + Semaphore pubWaitSemaphore(0); + Semaphore migrationSemaphore(0); + + const int msgCount = 20; + SynchronizedHashMap producedMsgs; + auto produce = [&]() { + int i = 0; + while (i < msgCount && checkTime()) { + if (i == 3 || i == 8 || i == 17) { + unloadSemaphore.acquire(); + } + + if (i == 5 || i == 15) { + pubWaitSemaphore.release(); + } + + if (i == 12) { + migrationSemaphore.acquire(); + } + + std::string content = std::to_string(i); + const auto msg = MessageBuilder().setContent(content).build(); + + auto start = TimeUtils::currentTimeMillis(); + Result sendResult = producer.send(msg); + auto elapsed = TimeUtils::currentTimeMillis() - start; + LOG_INFO("produce i: " << i << " " << elapsed << " ms"); + ASSERT_EQ(sendResult, ResultOk); + ASSERT_TRUE(elapsed < maxWaitTimeMs); + + producedMsgs.emplace(i, i); + i++; + } + LOG_INFO("producer finished"); + }; + std::atomic stopConsumer(false); + SynchronizedHashMap consumedMsgs; + auto consume = [&]() { + Message receivedMsg; + while (checkTime()) { + if (stopConsumer && producedMsgs.size() == msgCount && consumedMsgs.size() == msgCount) { + break; + } + auto start = TimeUtils::currentTimeMillis(); + Result receiveResult = consumer.receive(receivedMsg, maxWaitTimeMs); + auto elapsed = TimeUtils::currentTimeMillis() - start; + int i = std::stoi(receivedMsg.getDataAsString()); + LOG_INFO("receive i: " << i << " " << elapsed << " ms"); + ASSERT_EQ(receiveResult, ResultOk); + ASSERT_TRUE(elapsed < maxWaitTimeMs); + + start = TimeUtils::currentTimeMillis(); + Result ackResult = consumer.acknowledge(receivedMsg); + elapsed = TimeUtils::currentTimeMillis() - start; + LOG_INFO("acked i:" << i << " " << elapsed << " ms"); + ASSERT_TRUE(elapsed < maxWaitTimeMs); + ASSERT_EQ(ackResult, ResultOk); + consumedMsgs.emplace(i, i); + } + LOG_INFO("consumer finished"); + }; + + std::thread produceThread(produce); + std::thread consumeThread(consume); + + auto unload = [&](bool migrated) { + const std::string &adminUrl = migrated ? greenAdminUrl : blueAdminUrl; + auto clientImplPtr = PulsarFriend::getClientImplPtr(client); + auto &consumerImpl = PulsarFriend::getConsumerImpl(consumer); + auto &producerImpl = PulsarFriend::getProducerImpl(producer); + uint64_t lookupCountBeforeUnload; + std::string destinationBroker; + while (checkTime()) { + // make sure producers and consumers are ready + ASSERT_TRUE(waitUntil(maxWaitTime, + [&] { return consumerImpl.isConnected() && producerImpl.isConnected(); })); + + std::string url = + adminUrl + "lookup/v2/topic/persistent/public/unload-test/topic" + topicNameSuffix; + std::string responseDataBeforeUnload; + int res = makeGetRequest(url, responseDataBeforeUnload); + if (res != 200) { + continue; + } + std::string prefix = migrated ? "green-" : ""; + destinationBroker = + prefix + (responseDataBeforeUnload.find("broker-2") == std::string::npos ? "broker-2:8080" + : "broker-1:8080"); + lookupCountBeforeUnload = clientImplPtr->getLookupCount(); + ASSERT_TRUE(lookupCountBeforeUnload > 0); + + url = adminUrl + + "admin/v2/namespaces/public/unload-test/0x00000000_0xffffffff/unload?destinationBroker=" + + destinationBroker; + LOG_INFO("before lookup responseData:" << responseDataBeforeUnload << ",unload url:" << url + << ",lookupCountBeforeUnload:" << lookupCountBeforeUnload); + res = makePutRequest(url, ""); + LOG_INFO("unload res:" << res); + if (res != 204) { + continue; + } + + // make sure producers and consumers are ready + ASSERT_TRUE(waitUntil(maxWaitTime, + [&] { return consumerImpl.isConnected() && producerImpl.isConnected(); })); + std::string responseDataAfterUnload; + ASSERT_TRUE(waitUntil(std::chrono::seconds(60), [&] { + url = adminUrl + "lookup/v2/topic/persistent/public/unload-test/topic" + topicNameSuffix; + res = makeGetRequest(url, responseDataAfterUnload); + return res == 200 && responseDataAfterUnload.find(destinationBroker) != std::string::npos; + })); + LOG_INFO("after lookup responseData:" << responseDataAfterUnload << ",res:" << res); + + auto lookupCountAfterUnload = clientImplPtr->getLookupCount(); + if (lookupCountBeforeUnload != lookupCountAfterUnload) { + continue; + } + break; + } + }; + LOG_INFO("#### starting first unload ####"); + unload(false); + unloadSemaphore.release(); + pubWaitSemaphore.acquire(); + LOG_INFO("#### starting second unload ####"); + unload(false); + unloadSemaphore.release(); + + LOG_INFO("#### migrating the cluster ####"); + migrationSemaphore.release(); + ASSERT_TRUE(waitUntil(std::chrono::seconds(60), [&] { + std::string url = blueAdminUrl + "admin/v2/clusters/cluster-a/migrate?migrated=true"; + int res = makePostRequest(url, R"({ + "serviceUrl": "http://localhost:8081", + "serviceUrlTls":"https://localhost:8085", + "brokerServiceUrl": "pulsar://localhost:6651", + "brokerServiceUrlTls": "pulsar+ssl://localhost:6655" + })"); + LOG_INFO("res:" << res); + return res == 200; + })); + ASSERT_TRUE(waitUntil(maxWaitTime, [&] { + auto &consumerImpl = PulsarFriend::getConsumerImpl(consumer); + auto &producerImpl = PulsarFriend::getProducerImpl(producer); + auto consumerConnAddress = PulsarFriend::getConnectionPhysicalAddress(consumerImpl); + auto producerConnAddress = PulsarFriend::getConnectionPhysicalAddress(producerImpl); + return consumerImpl.isConnected() && producerImpl.isConnected() && + consumerConnAddress.find("6651") != std::string::npos && + producerConnAddress.find("6651") != std::string::npos; + })); + pubWaitSemaphore.acquire(); + LOG_INFO("#### starting third unload after migration ####"); + unload(true); + unloadSemaphore.release(); + + stopConsumer = true; + consumeThread.join(); + produceThread.join(); + ASSERT_EQ(producedMsgs.size(), msgCount); + ASSERT_EQ(consumedMsgs.size(), msgCount); + for (int i = 0; i < msgCount; i++) { + producedMsgs.remove(i); + consumedMsgs.remove(i); + } + ASSERT_EQ(producedMsgs.size(), 0); + ASSERT_EQ(consumedMsgs.size(), 0); + + client.close(); + + ASSERT_TRUE(checkTime()) << "timed out"; +} + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/extensibleLM/docker-compose.yml b/tests/extensibleLM/docker-compose.yml new file mode 100644 index 00000000..14876395 --- /dev/null +++ b/tests/extensibleLM/docker-compose.yml @@ -0,0 +1,154 @@ +version: '3' +networks: + pulsar: + driver: bridge +services: + # Start ZooKeeper + zookeeper: + image: apachepulsar/pulsar:latest + container_name: zookeeper + restart: on-failure + networks: + - pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + command: > + bash -c "bin/apply-config-from-env.py conf/zookeeper.conf && \ + bin/generate-zookeeper-config.sh conf/zookeeper.conf && \ + exec bin/pulsar zookeeper" + healthcheck: + test: ["CMD", "bin/pulsar-zookeeper-ruok.sh"] + interval: 10s + timeout: 5s + retries: 30 + + # Initialize cluster metadata + pulsar-init: + container_name: pulsar-init + hostname: pulsar-init + image: apachepulsar/pulsar:latest + networks: + - pulsar + environment: + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + command: > + bin/pulsar initialize-cluster-metadata \ + --cluster cluster-a \ + --zookeeper zookeeper:2181 \ + --configuration-store zookeeper:2181 \ + --web-service-url http://broker-1:8080 \ + --broker-service-url pulsar://broker-1:6650 + depends_on: + zookeeper: + condition: service_healthy + + # Start bookie + bookie: + image: apachepulsar/pulsar:latest + container_name: bookie + restart: on-failure + networks: + - pulsar + environment: + - clusterName=cluster-a + - zkServers=zookeeper:2181 + - metadataServiceUri=metadata-store:zk:zookeeper:2181 + - advertisedAddress=bookie + - BOOKIE_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + depends_on: + zookeeper: + condition: service_healthy + pulsar-init: + condition: service_completed_successfully + command: bash -c "bin/apply-config-from-env.py conf/bookkeeper.conf && exec bin/pulsar bookie" + + proxy: + image: apachepulsar/pulsar:latest + container_name: proxy + hostname: proxy + restart: on-failure + networks: + - pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - PULSAR_MEM=-Xms128m -Xmx128m -XX:MaxDirectMemorySize=56m + ports: + - "8080:8080" + - "6650:6650" + depends_on: + broker-1: + condition: service_started + broker-2: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/proxy.conf && exec bin/pulsar proxy" + + # Start broker 1 + broker-1: + image: apachepulsar/pulsar:latest + container_name: broker-1 + hostname: broker-1 + restart: on-failure + networks: + - pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - managedLedgerDefaultEnsembleSize=1 + - managedLedgerDefaultWriteQuorum=1 + - managedLedgerDefaultAckQuorum=1 + - advertisedAddress=broker-1 + - internalListenerName=internal + - advertisedListeners=internal:pulsar://broker-1:6650 + - PULSAR_MEM=-Xms256m -Xmx256m -XX:MaxDirectMemorySize=56m + # Load Manager. Here uses the extensible load balancer, sets the unloading strategy to TransferShedder, and enables debug mode. + - loadManagerClassName=org.apache.pulsar.broker.loadbalance.extensions.ExtensibleLoadManagerImpl + - loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.extensions.scheduler.TransferShedder + - loadBalancerSheddingEnabled=false + - loadBalancerDebugModeEnabled=true + - clusterMigrationCheckDurationSeconds=1 + - brokerServiceCompactionThresholdInBytes=1000000 + - PULSAR_PREFIX_defaultNumberOfNamespaceBundles=1 + depends_on: + zookeeper: + condition: service_healthy + bookie: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/broker.conf && exec bin/pulsar broker" + + # Start broker 2 + broker-2: + image: apachepulsar/pulsar:latest + container_name: broker-2 + hostname: broker-2 + restart: on-failure + networks: + - pulsar + environment: + - metadataStoreUrl=zk:zookeeper:2181 + - zookeeperServers=zookeeper:2181 + - clusterName=cluster-a + - managedLedgerDefaultEnsembleSize=1 + - managedLedgerDefaultWriteQuorum=1 + - managedLedgerDefaultAckQuorum=1 + - advertisedAddress=broker-2 + - internalListenerName=internal + - advertisedListeners=internal:pulsar://broker-2:6650 + - PULSAR_MEM=-Xms256m -Xmx256m -XX:MaxDirectMemorySize=56m + # Load Manager. Here uses the extensible load balancer, sets the unloading strategy to TransferShedder, and enables debug mode. + - loadManagerClassName=org.apache.pulsar.broker.loadbalance.extensions.ExtensibleLoadManagerImpl + - loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.extensions.scheduler.TransferShedder + - loadBalancerSheddingEnabled=false + - loadBalancerDebugModeEnabled=true + - clusterMigrationCheckDurationSeconds=1 + - brokerServiceCompactionThresholdInBytes=1000000 + - PULSAR_PREFIX_defaultNumberOfNamespaceBundles=1 + depends_on: + zookeeper: + condition: service_healthy + bookie: + condition: service_started + command: bash -c "bin/apply-config-from-env.py conf/broker.conf && exec bin/pulsar broker" \ No newline at end of file diff --git a/tests/oauth2/Oauth2Test.cc b/tests/oauth2/Oauth2Test.cc index 5f273d7e..158065eb 100644 --- a/tests/oauth2/Oauth2Test.cc +++ b/tests/oauth2/Oauth2Test.cc @@ -22,16 +22,17 @@ #include #include +#include #include "lib/Base64Utils.h" using namespace pulsar; -#ifndef TEST_ROOT_PATH -#define TEST_ROOT_PATH "." +#ifndef TEST_CONF_DIR +#error "TEST_CONF_DIR is not specified" #endif -static const std::string gKeyPath = std::string(TEST_ROOT_PATH) + "/../test-conf/cpp_credentials_file.json"; +static const std::string gKeyPath = TEST_CONF_DIR "/cpp_credentials_file.json"; static std::string gClientId; static std::string gClientSecret; static ParamMap gCommonParams; @@ -64,6 +65,26 @@ TEST(Oauth2Test, testWrongUrl) { ASSERT_EQ(ResultAuthenticationError, testCreateProducer("my-protocol:" + gKeyPath)); } +TEST(Oauth2Test, testTlsTrustFilePath) { + const auto caPath = "/etc/ssl/certs/my-cert.crt"; + std::ifstream fin{caPath}; + if (!fin) { // Skip this test if the CA cert is not prepared + return; + } + fin.close(); + + ClientConfiguration conf; + conf.setTlsTrustCertsFilePath(caPath); + auto params = gCommonParams; + params["private_key"] = "file://" + gKeyPath; + conf.setAuth(AuthOauth2::create(params)); + + Client client{"pulsar://localhost:6650", conf}; + Producer producer; + ASSERT_EQ(ResultOk, client.createProducer("oauth2-test", producer)); + client.close(); +} + int main(int argc, char* argv[]) { std::cout << "Load Oauth2 configs from " << gKeyPath << "..." << std::endl; boost::property_tree::ptree root; diff --git a/vcpkg b/vcpkg new file mode 160000 index 00000000..97dd2672 --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit 97dd26728e3856ed1ab62ee74ee3a391d9c81d19 diff --git a/vcpkg-example/CMakeLists.txt b/vcpkg-example/CMakeLists.txt new file mode 100644 index 00000000..40eff936 --- /dev/null +++ b/vcpkg-example/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.15) + +if (NOT CMAKE_TOOLCHAIN_FILE) + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/../vcpkg/scripts/buildsystems/vcpkg.cmake") +endif () +if (VCPKG_TARGET_TRIPLET MATCHES ".*windows-static") + cmake_policy(SET CMP0091 NEW) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif () +project(PulsarDemo CXX) + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) +endif () + +find_package(unofficial-pulsar CONFIG) +add_executable(main main.cc) +target_link_libraries(main PRIVATE unofficial::pulsar::pulsar) diff --git a/vcpkg-example/README.md b/vcpkg-example/README.md new file mode 100644 index 00000000..15266d69 --- /dev/null +++ b/vcpkg-example/README.md @@ -0,0 +1,52 @@ +# vcpkg-example + +A simple example vcpkg project that imported pulsar-client-cpp 3.4.2 as the dependency. + +## How to build + +Before running the commands below, ensure the vcpkg has been installed. If you have already downloaded the submodule of this project, just run the following commands. If you want to specify an existing vcpkg installation directory (assuming it's `VCPKG_ROOT`), add the `-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake` option to the 1st command. + +```bash +cmake -B build +cmake --build build +``` + +Then the `main` executable will be generated under `./build` for single-configuration generators or `build/Debug` for multi-configuration generators. + +## How to link release libraries + +By default, debug libraries are linked so that the executable can be debugged by debuggers. However, when used for production, release libraries should be linked for better performance. + +See [cmake-generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html) for the concept of CMake Generator. + +### Single-configuration generator + +With single-configuration generators like Unix Makefiles on Linux and macOS, you need to specify the `CMAKE_BUILD_TYPE`. + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build +``` + +The `main` executable will still be generated under the `./build` directory but release libraries are linked now. + +### Multi-configuration generator + +With multi-configuration generators like Visual Studio on Windows, you just need to specify the `--config` option. + +```bash +cmake -B build +cmake --build build --config Release +``` + +The `main` executable that links release libraries will be generated under the `./build/Release` directory. + +## Link static libraries on Windows + +The default vcpkg triplet is `x64-windows` on Windows. If you changed the triplet for static libraries, like `x64-windows-static`, you need to add a line before the `project(PulsarDemo CXX)` in CMakeLists.txt. + +```cmake +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +``` + +See [CMAKE_MSVC_RUNTIME_LIBRARY](https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html) for details. diff --git a/vcpkg-example/main.cc b/vcpkg-example/main.cc new file mode 100644 index 00000000..f4784faa --- /dev/null +++ b/vcpkg-example/main.cc @@ -0,0 +1,8 @@ +#include +using namespace pulsar; + +int main(int argc, char *argv[]) { + Client client{"pulsar://localhost:6650"}; + client.close(); + return 0; +} diff --git a/vcpkg-example/vcpkg.json b/vcpkg-example/vcpkg.json new file mode 100644 index 00000000..e48c7217 --- /dev/null +++ b/vcpkg-example/vcpkg.json @@ -0,0 +1,11 @@ +{ + "name": "vcpkg-pulsar-demo", + "version-string": "0.1.0", + "builtin-baseline": "38d1652f152d36481f2f4e8a85c0f1e14f3769f7", + "dependencies": [ + { + "name": "pulsar-client-cpp", + "version>=": "3.4.2#1" + } + ] +} diff --git a/vcpkg.json b/vcpkg.json index 5cc38635..5ff44100 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,29 +1,75 @@ { "name": "pulsar-cpp", - "version": "2.8.0", + "version": "3.5.0", "description": "Pulsar C++ SDK", + "builtin-baseline": "b051745c68faa6f65c493371d564c4eb8af34dad", "dependencies": [ - "boost-accumulators", - "boost-algorithm", - "boost-any", - "boost-circular-buffer", - "boost-asio", - "boost-date-time", - "boost-predef", - "boost-program-options", - "boost-property-tree", - "boost-random", - "boost-serialization", - "boost-xpressive", - "curl", - "openssl", - "protobuf", - "snappy", - "zlib", - "zstd", + { + "name": "asio", + "features": [ + "openssl" + ], + "version>=": "1.28.2" + }, + { + "name": "boost-accumulators", + "version>=": "1.83.0" + }, + { + "name": "boost-property-tree", + "version>=": "1.83.0" + }, + { + "name": "curl", + "default-features": false, + "features": [ + "openssl" + ], + "version>=": "8.4.0" + }, { "name": "dlfcn-win32", "platform": "windows" + }, + { + "name": "openssl", + "version>=": "3.1.4#1" + }, + { + "name": "protobuf", + "version>=": "3.21.12" + }, + { + "name": "snappy", + "version>=": "1.1.10" + }, + { + "name": "zlib", + "version>=": "1.3" + }, + { + "name": "zstd", + "version>=": "1.5.5" + } + ], + "features": { + "perf": { + "description": "Build Performance Tool", + "dependencies": [ + { + "name": "boost-program-options", + "version>=": "1.83.0" + } + ] + }, + "tests": { + "description": "Build Tests", + "dependencies": [ + { + "name": "gtest", + "version>=": "1.14.0" + } + ] } - ] + } } diff --git a/version.txt b/version.txt index ba21b572..d5c0c991 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.0-pre +3.5.1