diff --git a/.asf.yaml b/.asf.yaml index b4f53506dd..b2f56d8d29 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -1,3 +1,6 @@ +notifications: + issues: issues@brpc.apache.org + discussions: issues@brpc.apache.org github: description: brpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC". labels: @@ -13,3 +16,8 @@ github: comment_issue: "Re: [I] {title} ({repository})" close_issue: "Re: [I] {title} ({repository})" catchall: "[GH] {title} ({repository})" + features: + wiki: true + issues: true + projects: false + discussions: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 92555f6e93..1ac04efa41 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,20 +4,20 @@ about: Create a report to help us improve --- -**Describe the bug (描述bug)** +**Describe the bug** -**To Reproduce (复现方法)** +**To Reproduce** -**Expected behavior (期望行为)** +**Expected behavior** -**Versions (各种版本)** +**Versions** OS: Compiler: brpc: protobuf: -**Additional context/screenshots (更多上下文/截图)** +**Additional context/screenshots** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bfb0e4bc54..8b3ad9003c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,14 +4,14 @@ about: Suggest an idea for this project --- -**Is your feature request related to a problem? (你需要的功能是否与某个问题有关?)** +**Is your feature request related to a problem?** -**Describe the solution you'd like (描述你期望的解决方法)** +**Describe the solution you'd like** -**Describe alternatives you've considered (描述你想到的折衷方案)** +**Describe alternatives you've considered** -**Additional context/screenshots (更多上下文/截图)** +**Additional context/screenshots** diff --git a/.github/actions/compile-with-make-protobuf/action.yml b/.github/actions/compile-with-make-protobuf/action.yml new file mode 100644 index 0000000000..5448b81d84 --- /dev/null +++ b/.github/actions/compile-with-make-protobuf/action.yml @@ -0,0 +1,27 @@ +inputs: + protobuf-version: + description: version of protobuf + required: true + protobuf-cpp-version: + description: version of protobuf-cpp + required: true + protobuf-install-dir: + description: install directory of protobuf + required: true + config-brpc-options: + description: extra options for config_brpc.sh + required: true +runs: + using: "composite" + steps: + - run: | + wget https://github.com/protocolbuffers/protobuf/releases/download/v${{inputs.protobuf-version}}/protobuf-cpp-${{inputs.protobuf-cpp-version}}.tar.gz + tar -xf protobuf-cpp-${{inputs.protobuf-cpp-version}}.tar.gz + cd protobuf-${{inputs.protobuf-cpp-version}} + ./configure --prefix=${{inputs.protobuf-install-dir}} --with-pic --disable-java --disable-python --disable-other-languages + sudo mkdir ${{inputs.protobuf-install-dir}} + make -j ${{env.proc_num}} && sudo make install + shell: bash + - uses: ./.github/actions/compile-with-make + with: + options: --headers="${{inputs.protobuf-install-dir}}/include /usr/include" --libs="${{inputs.protobuf-install-dir}} /usr/lib /usr/lib64" ${{inputs.config-brpc-options}} diff --git a/.github/actions/compile-with-make/action.yml b/.github/actions/compile-with-make/action.yml new file mode 100644 index 0000000000..1192b30da2 --- /dev/null +++ b/.github/actions/compile-with-make/action.yml @@ -0,0 +1,11 @@ +inputs: + options: + description: extra options for config_brpc.sh + required: false +runs: + using: "composite" + steps: + - run: | + sh config_brpc.sh --nodebugsymbols ${{inputs.options}} + cat config.mk && make clean && make -j ${{env.proc_num}} + shell: bash diff --git a/.github/actions/init-make-config/action.yml b/.github/actions/init-make-config/action.yml deleted file mode 100644 index 1132b6059a..0000000000 --- a/.github/actions/init-make-config/action.yml +++ /dev/null @@ -1,9 +0,0 @@ -inputs: - options: - description: extra options for config_brpc.sh - required: false -runs: - using: "composite" - steps: - - run: sh config_brpc.sh --headers="/usr/include" --libs="/usr/lib /usr/lib64" --nodebugsymbols ${{inputs.options}} - shell: bash diff --git a/.github/actions/init-ut-make-config/action.yml b/.github/actions/init-ut-make-config/action.yml index b13800ed98..891e45b210 100644 --- a/.github/actions/init-ut-make-config/action.yml +++ b/.github/actions/init-ut-make-config/action.yml @@ -5,9 +5,22 @@ inputs: runs: using: "composite" steps: - - run: sudo git clone https://github.com/libunwind/libunwind.git && cd libunwind && sudo git checkout tags/v1.8.1 && sudo mkdir -p /libunwind && sudo autoreconf -i && sudo CC=clang CXX=clang++ ./configure --prefix=/libunwind && sudo make -j ${{env.proc_num}} && sudo make install - shell: bash - - run: sudo apt-get update && sudo apt-get install -y libgtest-dev cmake gdb libstdc++6-9-dbg && cd /usr/src/gtest && sudo cmake . && sudo make -j ${{env.proc_num}} && sudo mv lib/libgtest* /usr/lib/ - shell: bash - - run: sh config_brpc.sh --headers="/libunwind/include /usr/include" --libs="/libunwind/lib /usr/lib /usr/lib64" --nodebugsymbols ${{inputs.options}} - shell: bash + - run: | + sudo apt-get update && sudo apt-get install -y clang-12 lldb-12 lld-12 libgtest-dev cmake gdb libstdc++6-11-dbg + cd /usr/src/gtest && export CC=clang-12 && export CXX=clang++-12 && sudo cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 . + sudo make -j ${{env.proc_num}} && sudo mv lib/libgtest* /usr/lib/ + shell: bash + - run: | + sudo git clone https://github.com/libunwind/libunwind.git + cd libunwind && sudo git checkout tags/v1.8.1 && sudo mkdir -p /libunwind + sudo autoreconf -i && sudo CC=clang-12 CXX=clang++-12 ./configure --prefix=/libunwind + sudo make -j ${{env.proc_num}} && sudo make install + shell: bash + - run: | + sudo git clone https://github.com/gperftools/gperftools.git + cd gperftools && sudo git checkout tags/gperftools-2.16 && sudo mkdir -p /gperftools + sudo ./autogen.sh && sudo CC=clang-12 CXX=clang++-12 ./configure --prefix=/gperftools --enable-frame-pointers + sudo make -j ${{env.proc_num}} && sudo make install + shell: bash + - run: sh config_brpc.sh --headers="/libunwind/include /gperftools/include /usr/include" --libs="/libunwind/lib /gperftools/lib /usr/lib /usr/lib64" ${{inputs.options}} + shell: bash diff --git a/.github/actions/install-all-dependences/action.yml b/.github/actions/install-all-dependencies/action.yml similarity index 59% rename from .github/actions/install-all-dependences/action.yml rename to .github/actions/install-all-dependencies/action.yml index 8ff502f81a..cb4d000244 100644 --- a/.github/actions/install-all-dependences/action.yml +++ b/.github/actions/install-all-dependencies/action.yml @@ -1,10 +1,10 @@ runs: using: "composite" steps: - - uses: ./.github/actions/install-essential-dependences - - run: sudo apt-get install -y libgoogle-glog-dev automake bison flex libboost-all-dev libevent-dev libtool pkg-config libibverbs1 libibverbs-dev libunwind8-dev + - uses: ./.github/actions/install-essential-dependencies + - run: sudo apt-get install -y libunwind-dev libgoogle-glog-dev automake bison flex libboost-all-dev libevent-dev libtool pkg-config libibverbs1 libibverbs-dev shell: bash - run: wget https://archive.apache.org/dist/thrift/0.11.0/thrift-0.11.0.tar.gz && tar -xf thrift-0.11.0.tar.gz shell: bash - - run: cd thrift-0.11.0/ && ./configure --prefix=/usr --with-rs=no --with-ruby=no --with-python=no --with-java=no --with-go=no --with-perl=no --with-php=no --with-csharp=no --with-erlang=no --with-lua=no --with-nodejs=no --with-haskell=no --with-dotnetcore=no CXXFLAGS="-Wno-unused-variable" && make -j $(nproc) && sudo make install + - run: cd thrift-0.11.0/ && ./configure --prefix=/usr --with-rs=no --with-ruby=no --with-python=no --with-java=no --with-go=no --with-perl=no --with-php=no --with-csharp=no --with-erlang=no --with-lua=no --with-nodejs=no --with-haskell=no --with-dotnetcore=no CXXFLAGS="-Wno-unused-variable" && make -j ${{env.proc_num}} && sudo make install shell: bash diff --git a/.github/actions/install-essential-dependences/action.yml b/.github/actions/install-essential-dependencies/action.yml similarity index 90% rename from .github/actions/install-essential-dependences/action.yml rename to .github/actions/install-essential-dependencies/action.yml index 4b3249d89a..3411b7f7c1 100644 --- a/.github/actions/install-essential-dependences/action.yml +++ b/.github/actions/install-essential-dependencies/action.yml @@ -3,5 +3,5 @@ runs: steps: - run: ulimit -c unlimited -S && sudo bash -c "echo 'core.%e.%p' > /proc/sys/kernel/core_pattern" shell: bash - - run: sudo apt-get install -y git g++ make libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev libgoogle-perftools-dev + - run: sudo apt-get install -y git g++ make libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f0ce207329..6cd036b6a9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,12 +9,12 @@ Problem Summary: Changed: Side effects: -- Performance effects(性能影响): +- Performance effects: -- Breaking backward compatibility(向后兼容性): +- Breaking backward compatibility: --- ### Check List: -- Please make sure your changes are compilable(请确保你的更改可以通过编译). -- When providing us with a new feature, it is best to add related tests(如果你向我们增加一个新的功能, 请添加相关测试). -- Please follow [Contributor Covenant Code of Conduct](https://github.com/apache/brpc/blob/master/CODE_OF_CONDUCT.md).(请遵循贡献者准则). +- Please make sure your changes are compilable. +- When providing us with a new feature, it is best to add related tests. +- Please follow [Contributor Covenant Code of Conduct](https://github.com/apache/brpc/blob/master/CODE_OF_CONDUCT.md). diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index e4db5c0e1b..dbceac1a51 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -13,169 +13,194 @@ on: env: proc_num: $(nproc) +# https://github.com/actions/runner-images jobs: - gcc-compile-with-make: - runs-on: ubuntu-20.04 # https://github.com/actions/runner-images + compile-with-make: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-essential-dependences - - uses: ./.github/actions/init-make-config + - uses: ./.github/actions/install-all-dependencies + + - name: gcc with default options + uses: ./.github/actions/compile-with-make with: - options: --cc=gcc --cxx=g++ --werror - - name: compile - run: | - make -j ${{env.proc_num}} + options: --headers=/usr/include --libs=/usr/lib /usr/lib64 --cc=gcc --cxx=g++ --werror + + - name: gcc with all options + uses: ./.github/actions/compile-with-make + with: + options: --headers=/usr/include --libs=/usr/lib /usr/lib64 --cc=gcc --cxx=g++ --werror --with-thrift --with-glog --with-rdma --with-debug-bthread-sche-safety --with-debug-lock --with-bthread-tracer --with-asan - gcc-compile-with-cmake: - runs-on: ubuntu-20.04 + - name: clang with default options + uses: ./.github/actions/compile-with-make + with: + options: --headers=/usr/include --libs=/usr/lib /usr/lib64 --cc=clang --cxx=clang++ --werror + + - name: clang with all options + uses: ./.github/actions/compile-with-make + with: + options: --headers=/usr/include --libs=/usr/lib /usr/lib64 --cc=clang --cxx=clang++ --werror --with-thrift --with-glog --with-rdma --with-debug-bthread-sche-safety --with-debug-lock --with-bthread-tracer --with-asan + + compile-with-cmake: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-essential-dependences - - name: cmake + - uses: ./.github/actions/install-all-dependencies + + - name: gcc with default options run: | - export CC=gcc && export CXX=g++ - mkdir build - cd build - cmake .. - - name: compile + export CC=gcc && export CXX=g++ + mkdir gcc_build && cd gcc_build && cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. + make -j ${{env.proc_num}} && make clean + + - name: gcc with all options run: | - cd build - make -j ${{env.proc_num}} + export CC=gcc && export CXX=g++ + mkdir gcc_build_all && cd gcc_build_all + cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON -DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON -DWITH_BTHREAD_TRACER=ON -DWITH_ASAN=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. + make -j ${{env.proc_num}} && make clean - gcc-compile-with-bazel: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - run: bazel test --verbose_failures -- //... -//example/... + - name: clang with default options + run: | + export CC=clang && export CXX=clang++ + mkdir clang_build && cd clang_build && cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. + make -j ${{env.proc_num}} && make clean - gcc-compile-with-boringssl: - runs-on: ubuntu-20.04 + - name: clang with all options + run: | + export CC=clang && export CXX=clang++ + mkdir clang_build_all && cd clang_build_all + cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON -DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON -DWITH_BTHREAD_TRACER=ON -DWITH_ASAN=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. + make -j ${{env.proc_num}} && make clean + + gcc-compile-with-make-protobuf: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - run: bazel test --verbose_failures --define with_mesalink=false --define with_glog=true --define with_thrift=true --define BRPC_WITH_BORINGSSL=true -- //... -//example/... + - uses: ./.github/actions/install-essential-dependencies - gcc-compile-with-make-all-options: - runs-on: ubuntu-20.04 + - name: protobuf 3.5.1 + uses: ./.github/actions/compile-with-make-protobuf + with: + protobuf-version: 3.5.1 + protobuf-cpp-version: 3.5.1 + protobuf-install-dir: /protobuf-3.5.1 + config-brpc-options: --cc=gcc --cxx=g++ --werror + + - name: protobuf 3.12.4 + uses: ./.github/actions/compile-with-make-protobuf + with: + protobuf-version: 3.12.4 + protobuf-cpp-version: 3.12.4 + protobuf-install-dir: /protobuf-3.12.4 + config-brpc-options: --cc=gcc --cxx=g++ --werror + + - name: protobuf 21.12 + uses: ./.github/actions/compile-with-make-protobuf + with: + protobuf-version: 21.12 + protobuf-cpp-version: 3.21.12 + protobuf-install-dir: /protobuf-3.21.12 + config-brpc-options: --cc=gcc --cxx=g++ --werror + + gcc-compile-with-bazel: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-all-dependences - - uses: ./.github/actions/init-make-config - with: - options: --cc=gcc --cxx=g++ --with-thrift --with-glog --with-rdma --with-debug-bthread-sche-safety --with-debug-lock --with-bthread-tracer --werror - - name: compile - run: | - make -j ${{env.proc_num}} + - run: bazel build --verbose_failures -- //... -//example/... - gcc-compile-with-cmake-all-options: - runs-on: ubuntu-20.04 + gcc-compile-with-boringssl: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-all-dependences - - name: cmake - run: | - export CC=gcc && export CXX=g++ - mkdir build - cd build - cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON -DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON -WITH_BTHREAD_TRACER=ON .. - - name: compile - run: | - cd build - make -j ${{env.proc_num}} + - run: bazel build --verbose_failures --define with_mesalink=false --define with_glog=true --define with_thrift=true --define BRPC_WITH_BORINGSSL=true -- //... -//example/... gcc-compile-with-bazel-all-options: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - run: bazel test --verbose_failures --define with_mesalink=false --define with_glog=true --define with_thrift=true --define with_debug_bthread_sche_safety=true --define with_debug_lock=true -- //... -//example/... + - run: bazel build --verbose_failures --define with_mesalink=false --define with_glog=true --define with_thrift=true --define with_debug_bthread_sche_safety=true --define with_debug_lock=true --define with_asan=true -- //... -//example/... - clang-compile-with-make: - runs-on: ubuntu-20.04 + clang-compile-with-make-protobuf: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-essential-dependences - - uses: ./.github/actions/init-make-config + - uses: ./.github/actions/install-essential-dependencies + + - name: protobuf 3.5.1 + uses: ./.github/actions/compile-with-make-protobuf with: - options: --cc=clang --cxx=clang++ --werror - - name: compile - run: | - make -j ${{env.proc_num}} + protobuf-version: 3.5.1 + protobuf-cpp-version: 3.5.1 + protobuf-install-dir: /protobuf-3.5.1 + config-brpc-options: --cc=clang --cxx=clang++ --werror - clang-compile-with-cmake: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - uses: ./.github/actions/install-essential-dependences - - name: cmake - run: | - export CC=clang && export CXX=clang++ - mkdir build - cd build - cmake .. - - name: compile - run: | - cd build - make -j ${{env.proc_num}} + - name: protobuf 3.12.4 + uses: ./.github/actions/compile-with-make-protobuf + with: + protobuf-version: 3.12.4 + protobuf-cpp-version: 3.12.4 + protobuf-install-dir: /protobuf-3.12.4 + config-brpc-options: --cc=clang --cxx=clang++ --werror + + - name: protobuf 21.12 + uses: ./.github/actions/compile-with-make-protobuf + with: + protobuf-version: 21.12 + protobuf-cpp-version: 3.21.12 + protobuf-install-dir: /protobuf-3.21.12 + config-brpc-options: --cc=clang --cxx=clang++ --werror clang-compile-with-bazel: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - run: bazel build --verbose_failures --action_env=CC=clang-12 -- //... -//example/... + - run: bazel build --verbose_failures --action_env=CC=clang -- //... -//example/... clang-compile-with-boringssl: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - run: bazel build --verbose_failures --action_env=CC=clang-12 --define with_mesalink=false --define with_glog=true --define with_thrift=true --define BRPC_WITH_BORINGSSL=true -- //... -//example/... + - run: bazel build --verbose_failures --action_env=CC=clang --define with_mesalink=false --define with_glog=true --define with_thrift=true --define BRPC_WITH_BORINGSSL=true -- //... -//example/... - clang-compile-with-make-all-options: - runs-on: ubuntu-20.04 + clang-compile-with-bazel-all-options: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-all-dependences - - uses: ./.github/actions/init-make-config - with: - options: --cc=clang --cxx=clang++ --with-thrift --with-glog --with-rdma --with-debug-bthread-sche-safety --with-debug-lock --with-bthread-tracer --werror - - name: compile - run: | - make -j ${{env.proc_num}} + - run: bazel build --verbose_failures --action_env=CC=clang --define with_mesalink=false --define with_glog=true --define with_thrift=true --define with_debug_bthread_sche_safety=true --define with_debug_lock=true --define with_asan=true -- //... -//example/... - clang-compile-with-cmake-all-options: - runs-on: ubuntu-20.04 + clang-unittest: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - uses: ./.github/actions/install-all-dependences - - name: cmake + - uses: ./.github/actions/install-essential-dependencies + - uses: ./.github/actions/init-ut-make-config + with: + options: --cc=clang-12 --cxx=clang++-12 --with-bthread-tracer + - name: compile tests run: | - export CC=clang && export CXX=clang++ - mkdir build - cd build - cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON -DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON -WITH_BTHREAD_TRACER=ON .. - - name: compile + cat config.mk + cd test + make -j ${{env.proc_num}} + - name: run tests run: | - cd build - make -j ${{env.proc_num}} + cd test + sh ./run_tests.sh - clang-compile-with-bazel-all-options: - runs-on: ubuntu-20.04 + clang-unittest-asan: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - run: bazel build --verbose_failures --action_env=CC=clang-12 --define with_mesalink=false --define with_glog=true --define with_thrift=true --define with_debug_bthread_sche_safety=true --define with_debug_lock=true -- //... -//example/... - - clang-unittest: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - uses: ./.github/actions/install-essential-dependences - - uses: ./.github/actions/init-ut-make-config - with: - options: --cc=clang --cxx=clang++ --with-bthread-tracer - - name: compile tests - run: | - cat config.mk - cd test - make -j ${{env.proc_num}} - - name: run tests - run: | - cd test - sh ./run_tests.sh + - uses: ./.github/actions/install-essential-dependencies + - uses: ./.github/actions/init-ut-make-config + with: + options: --cc=clang-12 --cxx=clang++-12 --with-bthread-tracer --with-asan + - name: compile tests + run: | + cat config.mk + cd test + make NEED_GPERFTOOLS=0 -j ${{env.proc_num}} + - name: run tests + run: | + cd test + sh ./run_tests.sh diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 50ab2e0294..014850f694 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -14,7 +14,7 @@ env: proc_num: $(sysctl -n hw.logicalcpu) jobs: - compile-with-make: + compile: runs-on: macos-latest # https://github.com/actions/runner-images steps: @@ -25,37 +25,17 @@ jobs: brew install ./homebrew-formula/protobuf.rb brew install openssl gnu-getopt coreutils gflags leveldb - - name: config_brpc + - name: compile with make run: | - GETOPT_PATH=$(brew --prefix gnu-getopt)/bin - export PATH=$GETOPT_PATH:$PATH - ./config_brpc.sh --header="$(brew --prefix)/include" --libs="$(brew --prefix)/lib" + GETOPT_PATH=$(brew --prefix gnu-getopt)/bin + export PATH=$GETOPT_PATH:$PATH + ./config_brpc.sh --header="$(brew --prefix)/include" --libs="$(brew --prefix)/lib" + make -j ${{env.proc_num}} && make clean - - name: compile + - name: compile with cmake run: | - make -j ${{env.proc_num}} - - compile-with-cmake: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - - name: install dependences - run: | - brew install ./homebrew-formula/protobuf.rb - brew install openssl gflags leveldb - - - name: cmake - run: | - mkdir build - cd build - cmake .. - - - name: compile - run: | - cd build - make -j ${{env.proc_num}} + mkdir build && cd build && cmake .. + make -j ${{env.proc_num}} && make clean compile-with-make-protobuf23: runs-on: macos-latest # https://github.com/actions/runner-images @@ -72,38 +52,14 @@ jobs: curl -o protobuf.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/b85b8dbf23ad509f163677a88ac72268f31e9c4a/Formula/protobuf.rb HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --formula --ignore-dependencies ./abseil.rb ./protobuf.rb - - name: config_brpc + - name: compile with make run: | GETOPT_PATH=$(brew --prefix gnu-getopt)/bin export PATH=$GETOPT_PATH:$PATH ./config_brpc.sh --header="$(brew --prefix)/include" --libs="$(brew --prefix)/lib" + make -j ${{env.proc_num}} && make clean - - name: compile - run: | - make -j ${{env.proc_num}} - - compile-with-cmake-protobuf23: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - - name: install dependences - run: | - brew install openssl gflags leveldb - # abseil 20230125.3 - curl -o abseil.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/b85b8dbf23ad509f163677a88ac72268f31e9c4a/Formula/abseil.rb - # protobuf 23.3 - curl -o protobuf.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/b85b8dbf23ad509f163677a88ac72268f31e9c4a/Formula/protobuf.rb - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --formula --ignore-dependencies ./abseil.rb ./protobuf.rb - - - name: cmake - run: | - mkdir build - cd build - cmake .. - - - name: compile + - name: compile with make run: | - cd build - make -j ${{env.proc_num}} + mkdir build && cd build && cmake .. + make -j ${{env.proc_num}} && make clean diff --git a/BUILD.bazel b/BUILD.bazel index 84ae6bd775..4e0217fc89 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -48,15 +48,21 @@ COPTS = [ "//bazel/config:brpc_with_debug_bthread_sche_safety": ["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=1"], "//conditions:default": ["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=0"], }) + select({ - "//bazel/config:brpc_with_debug_lock": ["-DBRPC_DEBUG_LOCK=1"], - "//conditions:default": ["-DBRPC_DEBUG_LOCK=0"], + "//bazel/config:brpc_with_debug_lock": ["-DBRPC_DEBUG_LOCK=1"], + "//conditions:default": ["-DBRPC_DEBUG_LOCK=0"], +}) + select({ + "@platforms//cpu:x86_64": ["-DBRPC_BTHREAD_TRACER"], + "//conditions:default": [], +}) + select({ + "//bazel/config:brpc_with_asan": ["-fsanitize=address"], + "//conditions:default": [""], }) LINKOPTS = [ "-pthread", "-ldl", ] + select({ - "@bazel_tools//tools/osx:darwin_x86_64": [ + "@bazel_tools//src/conditions:darwin": [ "-framework CoreFoundation", "-framework CoreGraphics", "-framework CoreData", @@ -84,7 +90,10 @@ LINKOPTS = [ "-libverbs", ], "//conditions:default": [], -}) +}) + select({ + "//bazel/config:brpc_with_asan": ["-fsanitize=address"], + "//conditions:default": [""], + }) genrule( name = "config_h", @@ -225,7 +234,7 @@ BUTIL_SRCS = [ "src/butil/recordio.cc", "src/butil/popen.cpp", ] + select({ - "@bazel_tools//tools/osx:darwin_x86_64": [ + "@bazel_tools//src/conditions:darwin": [ "src/butil/time/time_mac.cc", "src/butil/mac/scoped_mach_port.cc", ], @@ -340,7 +349,7 @@ cc_library( "//bazel/config:brpc_with_glog": ["@com_github_google_glog//:glog"], "//conditions:default": [], }) + select({ - "@bazel_tools//tools/osx:darwin_x86_64": [":macos_lib"], + "@bazel_tools//src/conditions:darwin": [":macos_lib"], "//conditions:default": [], }) + select({ "//bazel/config:brpc_with_boringssl": ["@boringssl//:ssl", "@boringssl//:crypto"], @@ -402,7 +411,10 @@ cc_library( deps = [ ":butil", ":bvar", - ], + ] + select({ + "@platforms//cpu:x86_64": ["@com_github_libunwind_libunwind//:libunwind"], + "//conditions:default": [], + }), ) cc_library( @@ -444,7 +456,7 @@ cc_library( deps = [ ":brpc_idl_options_cc_proto", ":butil", - "@com_google_protobuf//:protoc_lib", + "@com_google_protobuf//src/google/protobuf/compiler:code_generator", ], ) @@ -459,6 +471,7 @@ filegroup( proto_library( name = "brpc_idl_options_proto", srcs = [":brpc_idl_options_proto_srcs"], + strip_import_prefix = "src", visibility = ["//visibility:public"], deps = [ "@com_google_protobuf//:descriptor_proto", diff --git a/CMakeLists.txt b/CMakeLists.txt index afc2de4d31..c72a1646aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ option(WITH_SNAPPY "With snappy" OFF) option(WITH_RDMA "With RDMA" OFF) option(WITH_DEBUG_BTHREAD_SCHE_SAFETY "With debugging bthread sche safety" OFF) option(WITH_DEBUG_LOCK "With debugging lock" OFF) +option(WITH_ASAN "With AddressSanitizer" OFF) option(BUILD_UNIT_TESTS "Whether to build unit tests" OFF) option(BUILD_FUZZ_TESTS "Whether to build fuzz tests" OFF) option(BUILD_BRPC_TOOLS "Whether to build brpc tools" ON) @@ -39,7 +40,7 @@ if(POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() -set(BRPC_VERSION 1.12.0) +set(BRPC_VERSION 1.13.0) SET(CPACK_GENERATOR "DEB") SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "brpc authors") @@ -134,6 +135,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") endif() set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} ${DEFINE_CLOCK_GETTIME} -DBRPC_WITH_GLOG=${WITH_GLOG_VAL} -DBRPC_WITH_RDMA=${WITH_RDMA_VAL} -DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=${WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL} -DBRPC_DEBUG_LOCK=${WITH_DEBUG_LOCK_VAL}") +if (WITH_ASAN) + set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -fsanitize=address") + set(CMAKE_C_FLAGS "${CMAKE_CPP_FLAGS} -fsanitize=address") +endif() if(WITH_MESALINK) set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -DUSE_MESALINK") endif() @@ -314,8 +319,8 @@ endif() set(BRPC_PRIVATE_LIBS "-lgflags -lprotobuf -lleveldb -lprotoc -lssl -lcrypto -ldl -lz") if(WITH_GLOG) - set(DYNAMIC_LIB ${DYNAMIC_LIB} ${GLOG_LIB}) - set(BRPC_PRIVATE_LIBS "${BRPC_PRIVATE_LIBS} -lglog") + set(DYNAMIC_LIB ${GLOG_LIB} ${DYNAMIC_LIB}) + set(BRPC_PRIVATE_LIBS "-lglog ${BRPC_PRIVATE_LIBS}") endif() if(WITH_SNAPPY) diff --git a/MODULE.bazel b/MODULE.bazel index e041b67a03..b947415672 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = 'brpc', - version = '1.12.0', + version = '1.13.0', compatibility_level = 1, ) @@ -12,13 +12,24 @@ bazel_dep(name = 'protobuf', version = '27.3', repo_name = 'com_google_protobuf' bazel_dep(name = 'gflags', version = '2.2.2', repo_name = 'com_github_gflags_gflags') bazel_dep(name = 'glog', version = '0.5.0', repo_name = 'com_github_google_glog') bazel_dep(name = 'platforms', version = '0.0.4') +bazel_dep(name = "apple_support", version = "1.17.1") bazel_dep(name = 'rules_cc', version = '0.0.1') bazel_dep(name = 'rules_proto', version = '4.0.0') -bazel_dep(name = 'zlib', version = '1.2.13', repo_name = 'com_github_madler_zlib') +bazel_dep(name = 'zlib', version = '1.3.1.bcr.5', repo_name = 'com_github_madler_zlib') +bazel_dep(name = "libunwind", version = "1.8.1", repo_name = 'com_github_libunwind_libunwind') # --registry=https://baidu.github.io/babylon/registry bazel_dep(name = 'leveldb', version = '1.23', repo_name = 'com_github_google_leveldb') +single_version_override( + module_name = "leveldb", + registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main", +) bazel_dep(name = 'openssl', version = '3.3.2') +single_version_override( + module_name = "openssl", + version = "3.3.2.bcr.1", + registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main", +) bazel_dep(name = 'thrift', version = '0.21.0', repo_name = 'org_apache_thrift') # test only diff --git a/Makefile b/Makefile index 641a8a7822..fce3d82d60 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,10 @@ include config.mk # Notes on the flags: # 1. Added -fno-omit-frame-pointer: perf/tcmalloc-profiler use frame pointers by default # 2. Removed -Werror: Not block compilation for non-vital warnings, especially when the -# code is tested on newer systems. If the code is used in production, add -Werror back +# code is tested on newer systems. If the code is used in production, config `config_brpc.sh -werror'. CPPFLAGS+=-DBTHREAD_USE_FAST_PTHREAD_MUTEX -D_GNU_SOURCE -DUSE_SYMBOLIZE -DNO_TCMALLOC -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -DNDEBUG -DBRPC_REVISION=\"$(shell ./tools/get_brpc_revision.sh .)\" -CXXFLAGS+=$(CPPFLAGS) -O2 -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-invalid-offsetof -Wno-unused-parameter -fno-omit-frame-pointer -CFLAGS=$(CPPFLAGS) -O2 -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-unused-parameter -fno-omit-frame-pointer +CXXFLAGS+=$(CPPFLAGS) -O2 -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-invalid-offsetof -Wno-unused-parameter -fno-omit-frame-pointer -Wno-deprecated-declarations -Wno-unused-but-set-variable +CFLAGS=$(CPPFLAGS) -O2 -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-unused-parameter -fno-omit-frame-pointer -Wno-deprecated-declarations -Wno-unused-but-set-variable DEBUG_CXXFLAGS = $(filter-out -DNDEBUG,$(CXXFLAGS)) -DUNIT_TEST -DBVAR_NOT_LINK_DEFAULT_VARIABLES DEBUG_CFLAGS = $(filter-out -DNDEBUG,$(CFLAGS)) -DUNIT_TEST HDRPATHS=-I./src $(addprefix -I, $(HDRS)) diff --git a/RELEASE_VERSION b/RELEASE_VERSION index 0eed1a29ef..feaae22bac 100644 --- a/RELEASE_VERSION +++ b/RELEASE_VERSION @@ -1 +1 @@ -1.12.0 +1.13.0 diff --git a/bazel/config/BUILD.bazel b/bazel/config/BUILD.bazel index 1096574702..57569dd34e 100644 --- a/bazel/config/BUILD.bazel +++ b/bazel/config/BUILD.bazel @@ -120,4 +120,10 @@ config_setting( name = "brpc_with_debug_lock", define_values = {"with_debug_lock": "true"}, visibility = ["//visibility:public"], +) + +config_setting( + name = "brpc_with_asan", + define_values = {"with_asan": "true"}, + visibility = ["//visibility:public"], ) \ No newline at end of file diff --git a/community/release_cn.md b/community/release_cn.md index 0b20776a79..ee24c1bc4a 100644 --- a/community/release_cn.md +++ b/community/release_cn.md @@ -556,7 +556,7 @@ Brief notes of this release: - zzz More details regarding Apache brpc can be found at: -http://brpc.apache.org/ +https://brpc.apache.org/ The release is available for download at: https://brpc.apache.org/download/ @@ -564,7 +564,7 @@ https://brpc.apache.org/download/ The release notes can be found here: https://github.com/apache/brpc/releases/tag/1.0.0 -Website: http://brpc.apache.org/ +Website: https://brpc.apache.org/ Apache bRPC Resources: - Issue: https://github.com/apache/brpc/issues/ diff --git a/community/release_en.md b/community/release_en.md index 6a05f2f4ed..8a314826d8 100644 --- a/community/release_en.md +++ b/community/release_en.md @@ -563,7 +563,7 @@ Brief notes of this release: - zzz More details regarding Apache brpc can be found at: -http://brpc.apache.org/ +https://brpc.apache.org/ The release is available for download at: https://brpc.apache.org/download/ @@ -571,7 +571,7 @@ https://brpc.apache.org/download/ The release notes can be found here: https://github.com/apache/brpc/releases/tag/1.0.0 -Website: http://brpc.apache.org/ +Website: https://brpc.apache.org/ Apache bRPC Resources: - Issue: https://github.com/apache/brpc/issues/ diff --git a/config_brpc.sh b/config_brpc.sh index 25f506fe04..a16bab8154 100755 --- a/config_brpc.sh +++ b/config_brpc.sh @@ -38,12 +38,13 @@ else LDD=ldd fi -TEMP=`getopt -o v: --long headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-bthread-tracer,with-debug-bthread-sche-safety,with-debug-lock,nodebugsymbols,werror -n 'config_brpc' -- "$@"` +TEMP=`getopt -o v: --long headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-bthread-tracer,with-debug-bthread-sche-safety,with-debug-lock,with-asan,nodebugsymbols,werror -n 'config_brpc' -- "$@"` WITH_GLOG=0 WITH_THRIFT=0 WITH_RDMA=0 WITH_MESALINK=0 WITH_BTHREAD_TRACER=0 +WITH_ASAN=0 BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0 DEBUGSYMBOLS=-g WERROR= @@ -74,6 +75,7 @@ while true; do --with-bthread-tracer) WITH_BTHREAD_TRACER=1; shift 1 ;; --with-debug-bthread-sche-safety ) BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1; shift 1 ;; --with-debug-lock ) BRPC_DEBUG_LOCK=1; shift 1 ;; + --with-asan) WITH_ASAN=1; shift 1 ;; --nodebugsymbols ) DEBUGSYMBOLS=; shift 1 ;; --werror ) WERROR=-Werror; shift 1 ;; -- ) shift; break ;; @@ -345,10 +347,15 @@ else CXXFLAGS="-std=c++0x" fi -LEVELDB_HDR=$(find_dir_of_header_or_die leveldb/db.h) - CPPFLAGS= +if [ $WITH_ASAN != 0 ]; then + CPPFLAGS="${CPPFLAGS} -fsanitize=address" + DYNAMIC_LINKINGS="$DYNAMIC_LINKINGS -fsanitize=address" +fi + +LEVELDB_HDR=$(find_dir_of_header_or_die leveldb/db.h) + if [ $WITH_BTHREAD_TRACER != 0 ]; then if [ "$SYSTEM" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then >&2 $ECHO "bthread tracer is only supported on Linux x86_64 platform" @@ -425,7 +432,7 @@ append_to_output "STATIC_LINKINGS=$STATIC_LINKINGS" append_to_output "DYNAMIC_LINKINGS=$DYNAMIC_LINKINGS" # CPP means C PreProcessing, not C PlusPlus -CPPFLAGS="${CPPFLAGS} -DBRPC_WITH_GLOG=$WITH_GLOG -DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=$BRPC_DEBUG_BTHREAD_SCHE_SAFETY -DBRPC_DEBUG_LOCK=$BRPC_DEBUG_LOCK" +CPPFLAGS="${CPPFLAGS} -DBRPC_WITH_GLOG=$WITH_GLOG -DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=$BRPC_DEBUG_BTHREAD_SCHE_SAFETY -DBRPC_DEBUG_LOCK=$BRPC_DEBUG_LOCK" # Avoid over-optimizations of TLS variables by GCC>=4.8 # See: https://github.com/apache/brpc/issues/1693 @@ -523,7 +530,10 @@ append_to_output "ifeq (\$(NEED_GPERFTOOLS), 1)" TCMALLOC_LIB=$(find_dir_of_lib tcmalloc_and_profiler) if [ -z "$TCMALLOC_LIB" ]; then append_to_output " \$(error \"Fail to find gperftools\")" +elif [ $WITH_ASAN != 0 ]; then + append_to_output " \$(error \"gperftools is not compatible with ASAN\")" else + append_to_output " CPPFLAGS+=-DBRPC_ENABLE_CPU_PROFILER" append_to_output_libs "$TCMALLOC_LIB" " " if [ -f $TCMALLOC_LIB/libtcmalloc.$SO ]; then append_to_output " DYNAMIC_LINKINGS+=-ltcmalloc_and_profiler" diff --git a/docs/cn/getting_started.md b/docs/cn/getting_started.md index 76aa6f86ae..1b91e15b91 100644 --- a/docs/cn/getting_started.md +++ b/docs/cn/getting_started.md @@ -352,8 +352,6 @@ bRPC 中使用了 protobuf 内部 API,上游不保证相关 API 的兼容性 [1.8.0](https://github.com/apache/brpc/releases/tag/1.8.0) 中 [#2406](https://github.com/apache/brpc/pull/2406) 和 [#2493](https://github.com/apache/brpc/pull/2493)引入了部分 proto3 语法,所以目前 bRPC 不再兼容 protobuf 2.x 版本。如果你希望使用 2.x 版本,可以使用 1.8.0 之前的 bRPC 版本。 -pb 3.x中的Arena至今没被支持。 - ## gflags: 2.1-2.2.2 2.1.1 中存在一处已知问题,需要[补丁](https://github.com/gflags/gflags/commit/408061b46974cc8377a8a794a048ecae359ad887)。 diff --git a/docs/cn/heap_profiler.md b/docs/cn/heap_profiler.md index 941f0f7a6c..2243b50fa8 100644 --- a/docs/cn/heap_profiler.md +++ b/docs/cn/heap_profiler.md @@ -121,7 +121,7 @@ brpc还提供一个类似的growth profiler分析内存的分配去向(不考 4. 相关gflags说明: - FLAGS_je_prof_active:true:开启采样,false:关闭采样。 - FLAGS_je_prof_dump:修改值会生成heap文件,用于手动操作jeprof分析。 - - FLAGS_je_prof_reset:清理已采样数据和重置profiler选项,并且动态设置采样率,[默认](https://jemalloc.net/jemalloc.3.html#opt.lg_prof_sample)2^19B(512K),对性能影响可忽略。 + - FLAGS_je_prof_reset:清理已采样数据,并且动态设置采样率,[默认](https://jemalloc.net/jemalloc.3.html#opt.lg_prof_sample)2^19B(512K),对性能影响可忽略。 5. 若要做memory leak: - `MALLOC_CONF="prof:true,prof_leak:true,prof_final:true" LD_PRELOAD=/xxx/lib/libjemalloc.so ./bin/test_server` ,进程退出时生成heap文件。 - 注:可`kill pid`优雅退出,不可`kill -9 pid`;可用`FLAGS_graceful_quit_on_sigterm=true FLAGS_graceful_quit_on_sighup=true`来支持优雅退出。 @@ -144,6 +144,8 @@ brpc还提供一个类似的growth profiler分析内存的分配去向(不考 ![img](../images/curl_jeprof_svg.png) +- 分配对象个数`curl ip:port/pprof/heap?display=svg&extra_options=inuse_objects`。 + - curl生成火焰图`curl ip:port/pprof/heap?display=flamegraph`。需配置env FLAMEGRAPH_PL_PATH=/xxx/flamegraph.pl,[flamegraph](https://github.com/brendangregg/FlameGraph) ![img](../images/curl_jeprof_flamegraph.png) diff --git a/docs/cn/sanitizers.md b/docs/cn/sanitizers.md new file mode 100644 index 0000000000..3013526927 --- /dev/null +++ b/docs/cn/sanitizers.md @@ -0,0 +1,32 @@ +# Sanitizers + +新版本的GCC/Clang支持[sanitizers](https://github.com/google/sanitizers),方便开发者排查代码中的bug。 bRPC对sanitizers提供了一定的支持。 + +## AddressSanitizer(ASan) + +ASan提供了[对协程的支持](https://reviews.llvm.org/D20913)。 在bthread创建、切换、销毁时,让ASan知道当前bthread的栈信息,主要用于维护[fake stack](https://github.com/google/sanitizers/wiki/AddressSanitizerUseAfterReturn)。 + +bRPC中启用ASan的方法:给config_brpc.sh增加`--with-asan`选项、给cmake增加`-DWITH_ASAN=ON`选项或者给bazel增加`--define with_asan=true`选项。 + +另外需要注意的是,ASan没法检测非ASan分配内存或者对象池复用内存。所以我们封装了两个宏,让ASan知道内存块是否能被使用。在非ASan环境下,这两个宏什么也不做,没有开销。 + +```c++ +#include + +BUTIL_ASAN_POISON_MEMORY_REGION(addr, size); +BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size); +``` + +其他问题:如果ASan报告中new/delete的调用栈不完整,可以通过设置`fast_unwind_on_malloc=0`回溯出完整的调用栈了。需要注意的是`fast_unwind_on_malloc=0`很耗性能。 + +## ThreadSanitizer(TSan) + +待支持(TODO) + +## LeakSanitizer(LSan) / MemorySanitizer(MSan) / UndefinedBehaviorSanitizer(UBSan) + +bRPC默认支持这三种sanitizers,编译及链接时加上对应的选项即可启用对应的sanitizers: + +1. LSan: `-fsanitize=leak`; +2. MSan: `-fsanitize=memory`; +3. UBSan: `-fsanitize=undefined`; diff --git a/docs/cn/streaming_rpc.md b/docs/cn/streaming_rpc.md index c9f9ab3673..6bdb2f2913 100644 --- a/docs/cn/streaming_rpc.md +++ b/docs/cn/streaming_rpc.md @@ -16,8 +16,7 @@ Streaming RPC保证: - 全双工。 - 支持流控。 - 提供超时提醒 - -目前的实现还没有自动切割过大的消息,同一个tcp连接上的多个Stream之间可能有[Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking)问题,请尽量避免过大的单个消息,实现自动切割后我们会告知并更新文档。 +- 支持自动切割过大的消息,避免[Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking)问题 例子见[example/streaming_echo_c++](https://github.com/apache/brpc/tree/master/example/streaming_echo_c++/)。 diff --git a/docs/en/getting_started.md b/docs/en/getting_started.md index a2ae80d47c..b2363ce39f 100644 --- a/docs/en/getting_started.md +++ b/docs/en/getting_started.md @@ -347,8 +347,6 @@ Please [submit issue](https://github.com/apache/brpc/issues) if you have any pro [#2406](https://github.com/apache/brpc/pull/2406) and [#2493](https://github.com/apache/brpc/pull/2493) in [version 1.8.0]((https://github.com/apache/brpc/releases/tag/1.8.0)) introduce some proto3 syntax, so currently bRPC is no longer compatible with pb 2.x version. If you want to use pb 2.x version, you can use bRPC version before 1.8.0. -Arena in pb 3.x is not supported yet. - ## gflags: 2.0-2.2.2 [gflags patch](https://github.com/gflags/gflags/commit/408061b46974cc8377a8a794a048ecae359ad887) is required when compiled with 2.1.1. diff --git a/docs/en/streaming_rpc.md b/docs/en/streaming_rpc.md index 8e06714887..7a41c24dc8 100644 --- a/docs/en/streaming_rpc.md +++ b/docs/en/streaming_rpc.md @@ -16,8 +16,8 @@ Streaming RPC ensures/provides: - Full duplex - Flow control - Notification on timeout - -We do not support segment large messages automatically so that multiple Streams on a single TCP connection may lead to [Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking) problem. Please avoid putting huge data into single message until we provide automatic segmentation. +- We support segment large messages automatically to avoid [Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_bloc +king) problem. For examples please refer to [example/streaming_echo_c++](https://github.com/apache/brpc/tree/master/example/streaming_echo_c++/). diff --git a/example/BUILD.bazel b/example/BUILD.bazel index b38cd5a156..df2722a4f6 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -123,3 +123,14 @@ cc_binary( "//:brpc", ], ) + +cc_binary( + name = "redis_c++_server", + srcs = [ + "redis_c++/redis_server.cpp", + ], + copts = COPTS, + deps = [ + "//:brpc", + ], +) \ No newline at end of file diff --git a/example/redis_c++/redis_server.cpp b/example/redis_c++/redis_server.cpp index 6ebc385315..a53ee26ed7 100644 --- a/example/redis_c++/redis_server.cpp +++ b/example/redis_c++/redis_server.cpp @@ -31,21 +31,54 @@ DEFINE_int32(port, 6379, "TCP Port of this server"); +class AuthSession : public brpc::Destroyable { +public: + explicit AuthSession(const std::string& user_name, const std::string& password) + : _user_name(user_name), _password(password) {} + + void Destroy() override { + delete this; + } + + const std::string _user_name; + const std::string _password; +}; + class RedisServiceImpl : public brpc::RedisService { public: - bool Set(const std::string& key, const std::string& value) { + RedisServiceImpl() { + _user_password["db1"] = "123456"; + _user_password["db2"] = "123456"; + _db_map["db1"].resize(kHashSlotNum); + _db_map["db2"].resize(kHashSlotNum); + } + + bool Set(const std::string& db_name, const std::string& key, const std::string& value) { int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum; _mutex[slot].lock(); - _map[slot][key] = value; + auto& kv = _db_map[db_name]; + kv[slot][key] = value; _mutex[slot].unlock(); return true; } - bool Get(const std::string& key, std::string* value) { + bool Auth(const std::string& db_name, const std::string& password) { + if (_user_password.find(db_name) == _user_password.end()) { + return false; + } else { + if (_user_password[db_name] != password) { + return false; + } + } + return true; + } + + bool Get(const std::string& db_name, const std::string& key, std::string* value) { int slot = butil::crc32c::Value(key.c_str(), key.size()) % kHashSlotNum; _mutex[slot].lock(); - auto it = _map[slot].find(key); - if (it == _map[slot].end()) { + auto& kv = _db_map[db_name]; + auto it = kv[slot].find(key); + if (it == kv[slot].end()) { _mutex[slot].unlock(); return false; } @@ -56,7 +89,9 @@ class RedisServiceImpl : public brpc::RedisService { private: const static int kHashSlotNum = 32; - std::unordered_map _map[kHashSlotNum]; + typedef std::unordered_map KVStore; + std::unordered_map> _db_map; + std::unordered_map _user_password; butil::Mutex _mutex[kHashSlotNum]; }; @@ -65,16 +100,27 @@ class GetCommandHandler : public brpc::RedisCommandHandler { explicit GetCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) {} - brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, brpc::RedisReply* output, bool /*flush_batched*/) override { + + AuthSession* session = static_cast(ctx->get_session()); + if (session == nullptr) { + output->FormatError("No auth session"); + return brpc::REDIS_CMD_HANDLED; + } + if (session->_user_name.empty()) { + output->FormatError("No user name"); + return brpc::REDIS_CMD_HANDLED; + } if (args.size() != 2ul) { output->FormatError("Expect 1 arg for 'get', actually %lu", args.size()-1); return brpc::REDIS_CMD_HANDLED; } const std::string key(args[1].data(), args[1].size()); std::string value; - if (_rsimpl->Get(key, &value)) { + if (_rsimpl->Get(session->_user_name, key, &value)) { output->SetString(value); } else { output->SetNullString(); @@ -91,22 +137,64 @@ class SetCommandHandler : public brpc::RedisCommandHandler { explicit SetCommandHandler(RedisServiceImpl* rsimpl) : _rsimpl(rsimpl) {} - brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, brpc::RedisReply* output, bool /*flush_batched*/) override { + AuthSession* session = static_cast(ctx->get_session()); + if (session == nullptr) { + output->FormatError("No auth session"); + return brpc::REDIS_CMD_HANDLED; + } + if (session->_user_name.empty()) { + output->FormatError("No user name"); + return brpc::REDIS_CMD_HANDLED; + } if (args.size() != 3ul) { output->FormatError("Expect 2 args for 'set', actually %lu", args.size()-1); return brpc::REDIS_CMD_HANDLED; } const std::string key(args[1].data(), args[1].size()); const std::string value(args[2].data(), args[2].size()); - _rsimpl->Set(key, value); + _rsimpl->Set(session->_user_name, key, value); output->SetStatus("OK"); return brpc::REDIS_CMD_HANDLED; } private: - RedisServiceImpl* _rsimpl; + RedisServiceImpl* _rsimpl; +}; + + + +class AuthCommandHandler : public brpc::RedisCommandHandler { +public: + explicit AuthCommandHandler(RedisServiceImpl* rsimpl) + : _rsimpl(rsimpl) {} + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool /*flush_batched*/) override { + if (args.size() != 3ul) { + output->FormatError("Expect 2 args for 'auth', actually %lu", args.size()-1); + return brpc::REDIS_CMD_HANDLED; + } + + const std::string db_name(args[1].data(), args[1].size()); + const std::string password(args[2].data(), args[2].size()); + + if (_rsimpl->Auth(db_name, password)) { + output->SetStatus("OK"); + auto auth_session = new AuthSession(db_name, password); + ctx->reset_session(auth_session); + } else { + output->FormatError("Invalid password for database '%s'", db_name.c_str()); + } + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rsimpl; }; int main(int argc, char* argv[]) { @@ -114,9 +202,11 @@ int main(int argc, char* argv[]) { RedisServiceImpl *rsimpl = new RedisServiceImpl; auto get_handler =std::unique_ptr(new GetCommandHandler(rsimpl)); auto set_handler =std::unique_ptr( new SetCommandHandler(rsimpl)); + auto auth_handler = std::unique_ptr(new AuthCommandHandler(rsimpl)); rsimpl->AddCommandHandler("get", get_handler.get()); rsimpl->AddCommandHandler("set", set_handler.get()); - + rsimpl->AddCommandHandler("auth", auth_handler.get()); + brpc::Server server; brpc::ServerOptions server_options; server_options.redis_service = rsimpl; diff --git a/package/rpm/brpc.spec b/package/rpm/brpc.spec index 4348347426..7bb519fd11 100644 --- a/package/rpm/brpc.spec +++ b/package/rpm/brpc.spec @@ -18,7 +18,7 @@ # Name: brpc -Version: 1.12.0 +Version: 1.13.0 Release: 1%{?dist} Summary: Industrial-grade RPC framework using C++ Language. diff --git a/src/brpc/adaptive_max_concurrency.cpp b/src/brpc/adaptive_max_concurrency.cpp index ae11ceffcd..d1be273032 100644 --- a/src/brpc/adaptive_max_concurrency.cpp +++ b/src/brpc/adaptive_max_concurrency.cpp @@ -21,18 +21,22 @@ #include "butil/logging.h" #include "butil/strings/string_number_conversions.h" #include "brpc/adaptive_max_concurrency.h" +#include "brpc/concurrency_limiter.h" namespace brpc { +const std::string AdaptiveMaxConcurrency::UNLIMITED = "unlimited"; +const std::string AdaptiveMaxConcurrency::CONSTANT = "constant"; + AdaptiveMaxConcurrency::AdaptiveMaxConcurrency() - : _value(UNLIMITED()) + : _value(UNLIMITED) , _max_concurrency(0) { } AdaptiveMaxConcurrency::AdaptiveMaxConcurrency(int max_concurrency) : _max_concurrency(0) { if (max_concurrency <= 0) { - _value = UNLIMITED(); + _value = UNLIMITED; _max_concurrency = 0; } else { _value = butil::string_printf("%d", max_concurrency); @@ -72,44 +76,43 @@ void AdaptiveMaxConcurrency::operator=(const butil::StringPiece& value) { value.CopyToString(&_value); _max_concurrency = -1; } + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } } void AdaptiveMaxConcurrency::operator=(int max_concurrency) { if (max_concurrency <= 0) { - _value = UNLIMITED(); + _value = UNLIMITED; _max_concurrency = 0; } else { _value = butil::string_printf("%d", max_concurrency); _max_concurrency = max_concurrency; } + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } } void AdaptiveMaxConcurrency::operator=(const TimeoutConcurrencyConf& value) { _value = "timeout"; _max_concurrency = -1; _timeout_conf = value; + if (_cl) { + _cl->ResetMaxConcurrency(*this); + } } const std::string& AdaptiveMaxConcurrency::type() const { if (_max_concurrency > 0) { - return CONSTANT(); + return CONSTANT; } else if (_max_concurrency == 0) { - return UNLIMITED(); + return UNLIMITED; } else { return _value; } } -const std::string& AdaptiveMaxConcurrency::UNLIMITED() { - static std::string* s = new std::string("unlimited"); - return *s; -} - -const std::string& AdaptiveMaxConcurrency::CONSTANT() { - static std::string* s = new std::string("constant"); - return *s; -} - bool operator==(const AdaptiveMaxConcurrency& adaptive_concurrency, const butil::StringPiece& concurrency) { return CompareStringPieceWithoutCase(concurrency, diff --git a/src/brpc/adaptive_max_concurrency.h b/src/brpc/adaptive_max_concurrency.h index 6bdad1ef1a..c90def6106 100644 --- a/src/brpc/adaptive_max_concurrency.h +++ b/src/brpc/adaptive_max_concurrency.h @@ -32,6 +32,7 @@ struct TimeoutConcurrencyConf { int max_concurrency; }; +class ConcurrencyLimiter; class AdaptiveMaxConcurrency{ public: explicit AdaptiveMaxConcurrency(); @@ -65,14 +66,17 @@ class AdaptiveMaxConcurrency{ const std::string& type() const; // Get strings filled with "unlimited" and "constant" - static const std::string& UNLIMITED(); - static const std::string& CONSTANT(); + static const std::string UNLIMITED;// = "unlimited"; + static const std::string CONSTANT;// = "constant"; + + void SetConcurrencyLimiter(ConcurrencyLimiter* cl) { _cl = cl; } private: std::string _value; int _max_concurrency; TimeoutConcurrencyConf _timeout_conf; // TODO std::varient for different type + ConcurrencyLimiter* _cl{nullptr}; }; inline std::ostream& operator<<(std::ostream& os, const AdaptiveMaxConcurrency& amc) { diff --git a/src/brpc/builtin/prometheus_metrics_service.cpp b/src/brpc/builtin/prometheus_metrics_service.cpp index 88f675bb81..4c5dd5903f 100644 --- a/src/brpc/builtin/prometheus_metrics_service.cpp +++ b/src/brpc/builtin/prometheus_metrics_service.cpp @@ -54,6 +54,8 @@ class PrometheusMetricsDumper : public bvar::Dumper { } bool dump(const std::string& name, const butil::StringPiece& desc) override; + bool dump_mvar(const std::string& name, const butil::StringPiece& desc) override; + bool dump_comment(const std::string& name, const std::string& type) override; private: DISALLOW_COPY_AND_ASSIGN(PrometheusMetricsDumper); @@ -108,6 +110,21 @@ bool PrometheusMetricsDumper::dump(const std::string& name, return true; } +bool PrometheusMetricsDumper::dump_mvar(const std::string& name, const butil::StringPiece& desc) { + if (!desc.empty() && desc[0] == '"') { + // there is no necessary to monitor string in prometheus + return true; + } + *_os << name << " " << desc << "\n"; + return true; +} + +bool PrometheusMetricsDumper::dump_comment(const std::string& name, const std::string& type) { + *_os << "# HELP " << name << '\n' + << "# TYPE " << name << " " << type << '\n'; + return true; +} + const PrometheusMetricsDumper::SummaryItems* PrometheusMetricsDumper::ProcessLatencyRecorderSuffix(const butil::StringPiece& name, const butil::StringPiece& desc) { diff --git a/src/brpc/compress.cpp b/src/brpc/compress.cpp index dd9ba825aa..36e55c7cdc 100644 --- a/src/brpc/compress.cpp +++ b/src/brpc/compress.cpp @@ -17,9 +17,10 @@ #include "butil/logging.h" +#include "json2pb/json_to_pb.h" #include "brpc/compress.h" #include "brpc/protocol.h" - +#include "brpc/proto_base.pb.h" namespace brpc { @@ -47,7 +48,7 @@ int RegisterCompressHandler(CompressType type, // Find CompressHandler by type. // Returns NULL if not found -inline const CompressHandler* FindCompressHandler(CompressType type) { +const CompressHandler* FindCompressHandler(CompressType type) { int index = type; if (index < 0 || index >= MAX_HANDLER_SIZE) { LOG(ERROR) << "CompressType=" << type << " is out of range"; @@ -83,10 +84,14 @@ bool ParseFromCompressedData(const butil::IOBuf& data, return ParsePbFromIOBuf(msg, data); } const CompressHandler* handler = FindCompressHandler(compress_type); - if (NULL != handler) { - return handler->Decompress(data, msg); + if (NULL == handler) { + return false; } - return false; + + Deserializer deserializer([msg](google::protobuf::io::ZeroCopyInputStream* input) { + return msg->ParseFromZeroCopyStream(input); + }); + return handler->Decompress(data, &deserializer); } bool SerializeAsCompressedData(const google::protobuf::Message& msg, @@ -96,10 +101,28 @@ bool SerializeAsCompressedData(const google::protobuf::Message& msg, return msg.SerializeToZeroCopyStream(&wrapper); } const CompressHandler* handler = FindCompressHandler(compress_type); - if (NULL != handler) { - return handler->Compress(msg, buf); + if (NULL == handler) { + return false; } - return false; + + Serializer serializer([&msg](google::protobuf::io::ZeroCopyOutputStream* output) { + return msg.SerializeToZeroCopyStream(output); + }); + return handler->Compress(serializer, buf); +} + +::google::protobuf::Metadata Serializer::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = SerializerBase::descriptor(); + metadata.reflection = nullptr; + return metadata; +} + +::google::protobuf::Metadata Deserializer::GetMetadata() const { + ::google::protobuf::Metadata metadata{}; + metadata.descriptor = DeserializerBase::descriptor(); + metadata.reflection = nullptr; + return metadata; } } // namespace brpc diff --git a/src/brpc/compress.h b/src/brpc/compress.h index 0b0fcb17e2..4529959873 100644 --- a/src/brpc/compress.h +++ b/src/brpc/compress.h @@ -21,10 +21,143 @@ #include // Message #include "butil/iobuf.h" // butil::IOBuf +#include "butil/logging.h" #include "brpc/options.pb.h" // CompressType +#include "brpc/nonreflectable_message.h" namespace brpc { +// Serializer can be used to implement custom serialization +// before compression with user callback. +class Serializer : public NonreflectableMessage { +public: + using Callback = std::function; + + Serializer() :Serializer(NULL) {} + + explicit Serializer(Callback callback) + :_callback(std::move(callback)) { + SharedCtor(); + } + + ~Serializer() override { + SharedDtor(); + } + + Serializer(const Serializer& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); + } + + Serializer& operator=(const Serializer& from) { + CopyFrom(from); + return *this; + } + + void Swap(Serializer* other) { + if (other != this) { + } + } + + void MergeFrom(const Serializer& from) override { + CHECK_NE(&from, this); + } + + // implements Message ---------------------------------------------- + void Clear() override { + _callback = nullptr; + } + size_t ByteSizeLong() const override { return 0; } + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + + // Converts the data into `output' for later compression. + bool SerializeTo(google::protobuf::io::ZeroCopyOutputStream* output) const { + if (!_callback) { + LOG(WARNING) << "Serializer::SerializeTo() called without callback"; + return false; + } + return _callback(output); + } + + void SetCallback(Callback callback) { + _callback = std::move(callback); + } + +private: + void SharedCtor() {} + void SharedDtor() {} + + Callback _callback; +}; + +// Deserializer can be used to implement custom deserialization +// after decompression with user callback. +class Deserializer : public NonreflectableMessage { +public: +public: + using Callback = std::function; + + Deserializer() :Deserializer(NULL) {} + + explicit Deserializer(Callback callback) : _callback(std::move(callback)) { + SharedCtor(); + } + + ~Deserializer() override { + SharedDtor(); + } + + Deserializer(const Deserializer& from) + : NonreflectableMessage(from) { + SharedCtor(); + MergeFrom(from); + } + + Deserializer& operator=(const Deserializer& from) { + CopyFrom(from); + return *this; + } + + void Swap(Deserializer* other) { + if (other != this) { + _callback.swap(other->_callback); + } + } + + void MergeFrom(const Deserializer& from) override { + CHECK_NE(&from, this); + _callback = from._callback; + } + + // implements Message ---------------------------------------------- + void Clear() override { _callback = nullptr; } + size_t ByteSizeLong() const override { return 0; } + int GetCachedSize() const PB_425_OVERRIDE { return ByteSize(); } + + ::google::protobuf::Metadata GetMetadata() const PB_527_OVERRIDE; + + // Converts the decompressed `input'. + bool DeserializeFrom(google::protobuf::io::ZeroCopyInputStream* intput) const { + if (!_callback) { + LOG(WARNING) << "Deserializer::DeserializeFrom() called without callback"; + return false; + } + return _callback(intput); + } + void SetCallback(Callback callback) { + _callback = std::move(callback); + } + +private: + void SharedCtor() {} + void SharedDtor() {} + + Callback _callback; +}; + struct CompressHandler { // Compress serialized `msg' into `buf'. // Returns true on success, false otherwise @@ -42,6 +175,9 @@ struct CompressHandler { // Returns 0 on success, -1 otherwise int RegisterCompressHandler(CompressType type, CompressHandler handler); +// Returns CompressHandler pointer of `type' if registered, NULL otherwise. +const CompressHandler* FindCompressHandler(CompressType type); + // Returns the `name' of the CompressType if registered const char* CompressTypeToCStr(CompressType type); diff --git a/src/brpc/concurrency_limiter.h b/src/brpc/concurrency_limiter.h index 083e2cf941..351dd0de3d 100644 --- a/src/brpc/concurrency_limiter.h +++ b/src/brpc/concurrency_limiter.h @@ -47,6 +47,9 @@ class ConcurrencyLimiter { // The return value is only for logging. virtual int MaxConcurrency() = 0; + // Reset max_concurrency + virtual int ResetMaxConcurrency(const AdaptiveMaxConcurrency& amc) = 0; + // Create an instance from the amc // Caller is responsible for delete the instance after usage. virtual ConcurrencyLimiter* New(const AdaptiveMaxConcurrency& amc) const = 0; diff --git a/src/brpc/controller.cpp b/src/brpc/controller.cpp index 271ccfa485..1362d32296 100644 --- a/src/brpc/controller.cpp +++ b/src/brpc/controller.cpp @@ -290,6 +290,8 @@ void Controller::ResetPods() { _http_response = NULL; _request_user_fields = NULL; _response_user_fields = NULL; + _request_content_type = CONTENT_TYPE_PB; + _response_content_type = CONTENT_TYPE_PB; _request_streams.clear(); _response_streams.clear(); _remote_stream_settings = NULL; @@ -1437,12 +1439,12 @@ void Controller::HandleStreamConnection(Socket *host_socket) { Stream* s = (Stream*)ptrs[0]->conn(); s->SetConnected(_remote_stream_settings); if (stream_num > 1) { - const auto& extra_stream_ids = _remote_stream_settings->extra_stream_ids(); + auto extra_stream_ids = std::move(*_remote_stream_settings->mutable_extra_stream_ids()); _remote_stream_settings->clear_extra_stream_ids(); for (size_t i = 1; i < stream_num; ++i) { Stream* extra_stream = (Stream *) ptrs[i]->conn(); _remote_stream_settings->set_stream_id(extra_stream_ids[i - 1]); - s->ShareHostSocket(*extra_stream); + s->SetHostSocket(host_socket); extra_stream->SetConnected(_remote_stream_settings); } } diff --git a/src/brpc/controller.h b/src/brpc/controller.h index d9799f889b..000aee2f50 100644 --- a/src/brpc/controller.h +++ b/src/brpc/controller.h @@ -616,6 +616,20 @@ friend void policy::ProcessThriftRequest(InputMessageBase*); void CallAfterRpcResp(const google::protobuf::Message* req, const google::protobuf::Message* res); + void set_request_content_type(ContentType type) { + _request_content_type = type; + } + ContentType request_content_type() const { + return _request_content_type; + } + + void set_response_content_type(ContentType type) { + _response_content_type = type; + } + ContentType response_content_type() const { + return _response_content_type; + } + private: struct CompletionInfo { CallId id; // call_id of the corresponding request @@ -859,6 +873,11 @@ friend void policy::ProcessThriftRequest(InputMessageBase*); butil::IOBuf _request_attachment; butil::IOBuf _response_attachment; + // Only SerializedRequest supports `_request_content_type'. + ContentType _request_content_type; + // Only SerializedResponse supports `_response_content_type'. + ContentType _response_content_type; + // Writable progressive attachment butil::intrusive_ptr _wpa; // Readable progressive attachment diff --git a/src/brpc/details/http_message.cpp b/src/brpc/details/http_message.cpp index 3160afbfcd..0ffe5b1143 100644 --- a/src/brpc/details/http_message.cpp +++ b/src/brpc/details/http_message.cpp @@ -141,7 +141,7 @@ int HttpMessage::on_header_value(http_parser *parser, } int HttpMessage::on_headers_complete(http_parser *parser) { - HttpMessage *http_message = (HttpMessage *)parser->data; + HttpMessage* http_message = (HttpMessage *)parser->data; http_message->_stage = HTTP_ON_HEADERS_COMPLETE; if (parser->http_major > 1) { // NOTE: this checking is a MUST because ProcessHttpResponse relies @@ -282,9 +282,12 @@ int HttpMessage::OnBody(const char *at, const size_t length) { } if (!_read_body_progressively) { // Normal read. - // TODO: The input data is from IOBuf as well, possible to append - // data w/o copying. - _body.append(at, length); + if (NULL != _current_source_iobuf) { + _current_source_iobuf->append_to( + &_body, length, _parsed_block_size + (at - _current_block_base)); + } else { + _body.append(at, length); + } return 0; } // Progressive read. @@ -434,13 +437,8 @@ const http_parser_settings g_parser_settings = { HttpMessage::HttpMessage(bool read_body_progressively, HttpMethod request_method) - : _parsed_length(0) - , _stage(HTTP_ON_MESSAGE_BEGIN) - , _request_method(request_method) - , _read_body_progressively(read_body_progressively) - , _body_reader(NULL) - , _cur_value(NULL) - , _vbodylen(0) { + : _request_method(request_method) + , _read_body_progressively(read_body_progressively) { http_parser_init(&_parser, HTTP_BOTH); _parser.allow_chunked_length = 1; _parser.data = this; @@ -489,6 +487,11 @@ ssize_t HttpMessage::ParseFromIOBuf(const butil::IOBuf &buf) { << ") to already-completed message"; return -1; } + _parsed_block_size = 0; + _current_source_iobuf = &buf; + BRPC_SCOPE_EXIT { + _current_source_iobuf = NULL; + }; size_t nprocessed = 0; for (size_t i = 0; i < buf.backing_block_num(); ++i) { butil::StringPiece blk = buf.backing_block(i); @@ -496,8 +499,11 @@ ssize_t HttpMessage::ParseFromIOBuf(const butil::IOBuf &buf) { // length=0 will be treated as EOF by http_parser, must skip. continue; } - nprocessed += http_parser_execute( + _current_block_base = blk.data(); + size_t n = http_parser_execute( &_parser, &g_parser_settings, blk.data(), blk.size()); + nprocessed += n; + _parsed_block_size += n; if (_parser.http_errno != 0) { // May try HTTP on other formats, failure is norm. RPC_VLOG << "Fail to parse http message, parser=" << _parser diff --git a/src/brpc/details/http_message.h b/src/brpc/details/http_message.h index b86ffdd9b5..655ba1a9d8 100644 --- a/src/brpc/details/http_message.h +++ b/src/brpc/details/http_message.h @@ -88,7 +88,7 @@ class HttpMessage { bool read_body_progressively() const { return _read_body_progressively; } void set_read_body_progressively(bool read_body_progressively) { - this->_read_body_progressively = read_body_progressively; + _read_body_progressively = read_body_progressively; } // Send new parts of the body to the reader. If the body already has some @@ -99,32 +99,38 @@ class HttpMessage { protected: int OnBody(const char* data, size_t size); int OnMessageComplete(); - size_t _parsed_length; + size_t _parsed_length{0}; private: DISALLOW_COPY_AND_ASSIGN(HttpMessage); int UnlockAndFlushToBodyReader(std::unique_lock& locked); - HttpParserStage _stage; + HttpParserStage _stage{HTTP_ON_MESSAGE_BEGIN}; std::string _url; - HttpMethod _request_method; + HttpMethod _request_method{HTTP_METHOD_GET}; HttpHeader _header; - bool _read_body_progressively; + bool _read_body_progressively{false}; // For mutual exclusion between on_body and SetBodyReader. butil::Mutex _body_mutex; // Read body progressively - ProgressiveReader* _body_reader; + ProgressiveReader* _body_reader{NULL}; butil::IOBuf _body; + // Store the IOBuf information in `ParseFromIOBuf' + // for later zero-copy usage in `OnBody'. + const butil::IOBuf* _current_source_iobuf{NULL}; + const char* _current_block_base{NULL}; + size_t _parsed_block_size{0}; + // Parser related members struct http_parser _parser; std::string _cur_header; - std::string *_cur_value; + std::string *_cur_value{NULL}; protected: // Only valid when -http_verbose is on std::unique_ptr _vmsgbuilder; - size_t _vbodylen; + size_t _vbodylen{0}; }; std::ostream& operator<<(std::ostream& os, const http_parser& parser); diff --git a/src/brpc/details/http_parser.cpp b/src/brpc/details/http_parser.cpp index 0f9e906a1a..a4aa4d1276 100644 --- a/src/brpc/details/http_parser.cpp +++ b/src/brpc/details/http_parser.cpp @@ -2191,13 +2191,13 @@ http_parser_init (http_parser *parser, enum http_parser_type t) const char * http_errno_name(enum http_errno err) { - assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + assert(err < (http_errno)(sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); return http_strerror_tab[err].name; } const char * http_errno_description(enum http_errno err) { - assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + assert(err < (http_errno)(sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); return http_strerror_tab[err].description; } diff --git a/src/brpc/details/jemalloc_profiler.cpp b/src/brpc/details/jemalloc_profiler.cpp index ea9dcac49b..c14d54907e 100644 --- a/src/brpc/details/jemalloc_profiler.cpp +++ b/src/brpc/details/jemalloc_profiler.cpp @@ -163,19 +163,12 @@ static int JeProfileReset(size_t lg_sample) { return ret; } LOG(INFO) << "mallctl set prof.reset:" << lg_sample << " succ"; - - FLAGS_je_prof_active = false; - if (FLAGS_je_prof_active) { - LOG(WARNING) << "reset FLAGS_je_prof_active fail"; - return -1; - } - return 0; } void JeControlProfile(Controller* cntl) { const brpc::URI& uri = cntl->http_request().uri(); - // http:ip:port/pprof/heap?display=(text|svg|stats|flamegraph) + // http:ip:port/pprof/heap?display=(text|svg|stats|flamegraph)&extra_options=(inuse_space|inuse_objects..) const std::string* uri_display = uri.GetQuery("display"); butil::IOBuf& buf = cntl->response_attachment(); @@ -236,6 +229,14 @@ void JeControlProfile(Controller* cntl) { const std::string process_file(process_path, len); std::string cmd_str = jeprof + " " + process_file + " " + prof_name; + + // https://github.com/jemalloc/jemalloc/blob/5.3.0/bin/jeprof.in#L211-L222 + // e.g: inuse_space, contentions + const std::string* uri_extra_options = uri.GetQuery("extra_options"); + if (uri_extra_options != nullptr && !uri_extra_options->empty()) { + cmd_str += " --" + *uri_extra_options + " "; + } + bool display_img = false; if (*uri_display == "svg") { cmd_str += " --svg "; diff --git a/src/brpc/details/method_status.cpp b/src/brpc/details/method_status.cpp index faf6449c3c..3bed6bf209 100644 --- a/src/brpc/details/method_status.cpp +++ b/src/brpc/details/method_status.cpp @@ -149,6 +149,13 @@ void MethodStatus::SetConcurrencyLimiter(ConcurrencyLimiter* cl) { _cl.reset(cl); } +int HandleResponseWritten(bthread_id_t id, void* data, int /*error_code*/) { + auto args = static_cast(data); + args->sent_us = butil::cpuwide_time_us(); + CHECK_EQ(0, bthread_id_unlock_and_destroy(id)); + return 0; +} + ConcurrencyRemover::~ConcurrencyRemover() { if (_status) { _status->OnResponded(_c->ErrorCode(), butil::cpuwide_time_us() - _received_us); diff --git a/src/brpc/details/method_status.h b/src/brpc/details/method_status.h index b49b6754d0..9b7f070991 100644 --- a/src/brpc/details/method_status.h +++ b/src/brpc/details/method_status.h @@ -75,18 +75,23 @@ friend class Server; bvar::PassiveStatus _max_concurrency_bvar; }; +struct ResponseWriteInfo { + int64_t sent_us{0}; +}; + +int HandleResponseWritten(bthread_id_t id, void* data, int error_code); + class ConcurrencyRemover { public: ConcurrencyRemover(MethodStatus* status, Controller* c, int64_t received_us) - : _status(status) - , _c(c) - , _received_us(received_us) {} + : _status(status) , _c(c) , _received_us(received_us) {} ~ConcurrencyRemover(); + private: DISALLOW_COPY_AND_ASSIGN(ConcurrencyRemover); MethodStatus* _status; Controller* _c; - uint64_t _received_us; + int64_t _received_us; }; inline bool MethodStatus::OnRequested(int* rejected_cc, Controller* cntl) { diff --git a/src/brpc/event_dispatcher.cpp b/src/brpc/event_dispatcher.cpp index 53495ea6d7..bbd946846f 100644 --- a/src/brpc/event_dispatcher.cpp +++ b/src/brpc/event_dispatcher.cpp @@ -21,9 +21,9 @@ #include "butil/fd_utility.h" // make_close_on_exec #include "butil/logging.h" // LOG #include "butil/third_party/murmurhash3/murmurhash3.h"// fmix32 +#include "bvar/latency_recorder.h" // bvar::LatencyRecorder #include "bthread/bthread.h" // bthread_start_background #include "brpc/event_dispatcher.h" -#include "brpc/reloadable_flags.h" DECLARE_int32(task_group_ntags); @@ -37,6 +37,8 @@ DEFINE_bool(usercode_in_coroutine, false, "User's callback are run in coroutine, no bthread or pthread blocking call"); static EventDispatcher* g_edisp = NULL; +static bvar::LatencyRecorder* g_edisp_read_lantency = NULL; +static bvar::LatencyRecorder* g_edisp_write_lantency = NULL; static pthread_once_t g_edisp_once = PTHREAD_ONCE_INIT; static void StopAndJoinGlobalDispatchers() { @@ -46,8 +48,14 @@ static void StopAndJoinGlobalDispatchers() { g_edisp[i * FLAGS_event_dispatcher_num + j].Join(); } } + delete g_edisp_read_lantency; + delete g_edisp_write_lantency; } + void InitializeGlobalDispatchers() { + g_edisp_read_lantency = new bvar::LatencyRecorder("event_dispatcher_read_latency"); + g_edisp_write_lantency = new bvar::LatencyRecorder("event_dispatcher_write_latency"); + g_edisp = new EventDispatcher[FLAGS_task_group_ntags * FLAGS_event_dispatcher_num]; for (int i = 0; i < FLAGS_task_group_ntags; ++i) { for (int j = 0; j < FLAGS_event_dispatcher_num; ++j) { diff --git a/src/brpc/event_dispatcher_epoll.cpp b/src/brpc/event_dispatcher_epoll.cpp index 64717b1623..0ea404fff6 100644 --- a/src/brpc/event_dispatcher_epoll.cpp +++ b/src/brpc/event_dispatcher_epoll.cpp @@ -222,14 +222,18 @@ void EventDispatcher::Run() { || (e[i].events & has_epollrdhup) #endif ) { + int64_t start_ns = butil::cpuwide_time_ns(); // We don't care about the return value. CallInputEventCallback(e[i].data.u64, e[i].events, _thread_attr); + (*g_edisp_read_lantency) << (butil::cpuwide_time_ns() - start_ns); } } for (int i = 0; i < n; ++i) { if (e[i].events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) { + int64_t start_ns = butil::cpuwide_time_ns(); // We don't care about the return value. CallOutputEventCallback(e[i].data.u64, e[i].events, _thread_attr); + (*g_edisp_write_lantency) << (butil::cpuwide_time_ns() - start_ns); } } } diff --git a/src/brpc/event_dispatcher_kqueue.cpp b/src/brpc/event_dispatcher_kqueue.cpp index 97ad29bba6..a179048604 100644 --- a/src/brpc/event_dispatcher_kqueue.cpp +++ b/src/brpc/event_dispatcher_kqueue.cpp @@ -205,16 +205,20 @@ void EventDispatcher::Run() { } for (int i = 0; i < n; ++i) { if ((e[i].flags & EV_ERROR) || e[i].filter == EVFILT_READ) { + int64_t start_ns = butil::cpuwide_time_ns(); // We don't care about the return value. CallInputEventCallback((IOEventDataId)e[i].udata, e[i].filter, _thread_attr); + (*g_edisp_read_lantency) << (butil::cpuwide_time_ns() - start_ns); } } for (int i = 0; i < n; ++i) { if ((e[i].flags & EV_ERROR) || e[i].filter == EVFILT_WRITE) { + int64_t start_ns = butil::cpuwide_time_ns(); // We don't care about the return value. CallOutputEventCallback((IOEventDataId)e[i].udata, e[i].filter, _thread_attr); + (*g_edisp_write_lantency) << (butil::cpuwide_time_ns() - start_ns); } } } diff --git a/src/brpc/extension.h b/src/brpc/extension.h index 7190ac91de..7854362e86 100644 --- a/src/brpc/extension.h +++ b/src/brpc/extension.h @@ -48,7 +48,7 @@ class Extension { void List(std::ostream& os, char separator); private: -friend class butil::GetLeakySingleton >; +template friend U* butil::create_leaky_singleton_obj(); Extension() = default; butil::CaseIgnoredFlatMap _instance_map; butil::Mutex _map_mutex; diff --git a/src/brpc/global.cpp b/src/brpc/global.cpp index d45c67f054..6b3310ec7f 100644 --- a/src/brpc/global.cpp +++ b/src/brpc/global.cpp @@ -388,25 +388,22 @@ static void GlobalInitializeOrDieImpl() { LoadBalancerExtension()->RegisterOrDie("_dynpart", &g_ext->dynpart_lb); // Compress Handlers - const CompressHandler gzip_compress = - { GzipCompress, GzipDecompress, "gzip" }; + CompressHandler gzip_compress = { GzipCompress, GzipDecompress, "gzip" }; if (RegisterCompressHandler(COMPRESS_TYPE_GZIP, gzip_compress) != 0) { exit(1); } - const CompressHandler zlib_compress = - { ZlibCompress, ZlibDecompress, "zlib" }; + CompressHandler zlib_compress = { ZlibCompress, ZlibDecompress, "zlib" }; if (RegisterCompressHandler(COMPRESS_TYPE_ZLIB, zlib_compress) != 0) { exit(1); } - const CompressHandler snappy_compress = - { SnappyCompress, SnappyDecompress, "snappy" }; + CompressHandler snappy_compress = { SnappyCompress, SnappyDecompress, "snappy" }; if (RegisterCompressHandler(COMPRESS_TYPE_SNAPPY, snappy_compress) != 0) { exit(1); } // Protocols Protocol baidu_protocol = { ParseRpcMessage, - SerializeRequestDefault, PackRpcRequest, + SerializeRpcRequest, PackRpcRequest, ProcessRpcRequest, ProcessRpcResponse, VerifyRpcRequest, NULL, NULL, CONNECTION_TYPE_ALL, "baidu_std" }; diff --git a/src/brpc/load_balancer.h b/src/brpc/load_balancer.h index cda0517e87..a32b298d1f 100644 --- a/src/brpc/load_balancer.h +++ b/src/brpc/load_balancer.h @@ -184,6 +184,13 @@ inline Extension* LoadBalancerExtension() { return Extension::instance(); } +inline uint32_t GenRandomStride() { + uint32_t prime_offset[] = { + #include "bthread/offset_inl.list" + }; + return prime_offset[butil::fast_rand_less_than(ARRAY_SIZE(prime_offset))]; +} + } // namespace brpc diff --git a/src/brpc/memcache.cpp b/src/brpc/memcache.cpp index c198d16873..489d84db16 100644 --- a/src/brpc/memcache.cpp +++ b/src/brpc/memcache.cpp @@ -32,7 +32,7 @@ MemcacheRequest::MemcacheRequest() } MemcacheRequest::MemcacheRequest(const MemcacheRequest& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } @@ -143,7 +143,7 @@ MemcacheResponse::MemcacheResponse() } MemcacheResponse::MemcacheResponse(const MemcacheResponse& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } diff --git a/src/brpc/nonreflectable_message.h b/src/brpc/nonreflectable_message.h index 495e187407..1494cd1b75 100644 --- a/src/brpc/nonreflectable_message.h +++ b/src/brpc/nonreflectable_message.h @@ -21,7 +21,7 @@ #include #include -#include "pb_compat.h" +#include "brpc/pb_compat.h" namespace brpc { @@ -52,12 +52,20 @@ class NonreflectableMessage : public ::google::protobuf::Message { } #if GOOGLE_PROTOBUF_VERSION >= 5029000 - const ::google::protobuf::internal::ClassData* GetClassData() const override { - return nullptr; - } + using ClassData = ::google::protobuf::internal::ClassData; + using ClassDataFull = ::google::protobuf::internal::ClassDataFull; #elif GOOGLE_PROTOBUF_VERSION >= 5026000 + using ClassData = ::google::protobuf::Message::ClassData; + using ClassDataFull = ::google::protobuf::Message::ClassDataFull; +#endif + +#if GOOGLE_PROTOBUF_VERSION >= 5026000 const ClassData* GetClassData() const override { +# if GOOGLE_PROTOBUF_VERSION >= 5027000 + return _class_data_.base(); +# else return nullptr; +# endif } #endif @@ -210,8 +218,67 @@ class NonreflectableMessage : public ::google::protobuf::Message { private: static T _instance; + +#if GOOGLE_PROTOBUF_VERSION >= 5027000 + struct NonreflectableMessageClassData : ClassDataFull { + constexpr NonreflectableMessageClassData() + : ClassDataFull( +# if GOOGLE_PROTOBUF_VERSION >= 5029000 + ClassData{ + &_instance, // prototype + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + nullptr, // merge_to_from + ::google::protobuf::internal::MessageCreator(), // message_creator + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# elif GOOGLE_PROTOBUF_VERSION >= 5028000 + ClassData{ + &_instance, // prototype + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + nullptr, // merge_to_from + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# else + ClassData{ + nullptr, // tc_table + nullptr, // on_demand_register_arena_dtor + nullptr, // is_initialized + 0, // cached_size_offset + false, // is_lite + }, + nullptr, // merge_to_from + nullptr, // descriptor_methods + nullptr, // descriptor_table + nullptr // get_metadata_tracker +# endif + ) { + // Only can be used to determine whether the Types are the same. + descriptor = default_instance().GetMetadata().descriptor; + reflection = default_instance().GetMetadata().reflection; + } + }; + + static const NonreflectableMessageClassData _class_data_; +#endif }; +#if GOOGLE_PROTOBUF_VERSION >= 5027000 +template +const typename NonreflectableMessage::NonreflectableMessageClassData NonreflectableMessage::_class_data_; +#endif + template T NonreflectableMessage::_instance; diff --git a/src/brpc/nshead_message.cpp b/src/brpc/nshead_message.cpp index fe9e4c96c0..46081c702a 100644 --- a/src/brpc/nshead_message.cpp +++ b/src/brpc/nshead_message.cpp @@ -28,7 +28,7 @@ NsheadMessage::NsheadMessage() } NsheadMessage::NsheadMessage(const NsheadMessage& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } diff --git a/src/brpc/options.proto b/src/brpc/options.proto index 3e34b5f6f6..e334c48ea0 100644 --- a/src/brpc/options.proto +++ b/src/brpc/options.proto @@ -74,6 +74,13 @@ enum CompressType { COMPRESS_TYPE_LZ4 = 4; } +enum ContentType { + CONTENT_TYPE_PB = 0; + CONTENT_TYPE_JSON = 1; + CONTENT_TYPE_PROTO_JSON = 2; + CONTENT_TYPE_PROTO_TEXT = 3; +} + message ChunkInfo { required int64 stream_id = 1; required int64 chunk_id = 2; diff --git a/src/brpc/policy/auto_concurrency_limiter.cpp b/src/brpc/policy/auto_concurrency_limiter.cpp index d1d52d6d26..dd5a02ec99 100644 --- a/src/brpc/policy/auto_concurrency_limiter.cpp +++ b/src/brpc/policy/auto_concurrency_limiter.cpp @@ -134,6 +134,10 @@ int AutoConcurrencyLimiter::MaxConcurrency() { return _max_concurrency; } +int AutoConcurrencyLimiter::ResetMaxConcurrency(const AdaptiveMaxConcurrency&) { + return -1; +} + int64_t AutoConcurrencyLimiter::NextResetTime(int64_t sampling_time_us) { int64_t reset_start_us = sampling_time_us + (FLAGS_auto_cl_noload_latency_remeasure_interval_ms / 2 + diff --git a/src/brpc/policy/auto_concurrency_limiter.h b/src/brpc/policy/auto_concurrency_limiter.h index 6cf5e10c9e..d221f73bd9 100644 --- a/src/brpc/policy/auto_concurrency_limiter.h +++ b/src/brpc/policy/auto_concurrency_limiter.h @@ -35,6 +35,8 @@ class AutoConcurrencyLimiter : public ConcurrencyLimiter { int MaxConcurrency() override; + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + AutoConcurrencyLimiter* New(const AdaptiveMaxConcurrency&) const override; private: diff --git a/src/brpc/policy/baidu_rpc_meta.proto b/src/brpc/policy/baidu_rpc_meta.proto index 300564bbee..591798310a 100644 --- a/src/brpc/policy/baidu_rpc_meta.proto +++ b/src/brpc/policy/baidu_rpc_meta.proto @@ -33,6 +33,7 @@ message RpcMeta { optional bytes authentication_data = 7; optional StreamSettings stream_settings = 8; map user_fields = 9; + optional ContentType content_type = 10; } message RpcRequestMeta { diff --git a/src/brpc/policy/baidu_rpc_protocol.cpp b/src/brpc/policy/baidu_rpc_protocol.cpp index f8ea1f6361..8efff06546 100644 --- a/src/brpc/policy/baidu_rpc_protocol.cpp +++ b/src/brpc/policy/baidu_rpc_protocol.cpp @@ -20,11 +20,13 @@ #include // Message #include #include +#include #include "butil/logging.h" // LOG() -#include "butil/time.h" #include "butil/iobuf.h" // butil::IOBuf #include "butil/raw_pack.h" // RawPacker RawUnpacker #include "butil/memory/scope_guard.h" +#include "json2pb/json_to_pb.h" +#include "json2pb/pb_to_json.h" #include "brpc/controller.h" // Controller #include "brpc/socket.h" // Socket #include "brpc/server.h" // Server @@ -56,6 +58,8 @@ DEFINE_bool(baidu_protocol_use_fullname, true, DEFINE_bool(baidu_std_protocol_deliver_timeout_ms, false, "If this flag is true, baidu_std puts timeout_ms in requests."); +DECLARE_bool(pb_enum_as_number); + // Notes: // 1. 12-byte header [PRPC][body_size][meta_size] // 2. body_size and meta_size are in network byte order @@ -137,23 +141,95 @@ ParseResult ParseRpcMessage(butil::IOBuf* source, Socket* socket, return MakeMessage(msg); } +bool SerializeRpcMessage(const google::protobuf::Message& message, + Controller& cntl, ContentType content_type, + CompressType compress_type, butil::IOBuf* buf) { + auto serialize = [&](Serializer& serializer) -> bool { + bool ok; + if (COMPRESS_TYPE_NONE == compress_type) { + butil::IOBufAsZeroCopyOutputStream stream(buf); + ok = serializer.SerializeTo(&stream); + } else { + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + ok = handler->Compress(serializer, buf); + } + return ok; + }; + + if (CONTENT_TYPE_PB == content_type) { + Serializer serializer([&message](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + return message.SerializeToZeroCopyStream(output); + }); + return serialize(serializer); + } else if (CONTENT_TYPE_JSON == content_type) { + Serializer serializer([&message, &cntl](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + json2pb::Pb2JsonOptions options; + options.bytes_to_base64 = cntl.has_pb_bytes_to_base64(); + options.jsonify_empty_array = cntl.has_pb_jsonify_empty_array(); + options.always_print_primitive_fields = cntl.has_always_print_primitive_fields(); + options.single_repeated_to_array = cntl.has_pb_single_repeated_to_array(); + options.enum_option = FLAGS_pb_enum_as_number + ? json2pb::OUTPUT_ENUM_BY_NUMBER + : json2pb::OUTPUT_ENUM_BY_NAME; + std::string error; + bool ok = json2pb::ProtoMessageToJson(message, output, options, &error); + if (!ok) { + LOG(INFO) << "Fail to serialize message=" + << message.GetDescriptor()->full_name() + << " to json :" << error; + } + return ok; + }); + return serialize(serializer); + } else if (CONTENT_TYPE_PROTO_JSON == content_type) { + Serializer serializer([&message, &cntl](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + json2pb::Pb2ProtoJsonOptions options; + options.always_print_enums_as_ints = FLAGS_pb_enum_as_number; + AlwaysPrintPrimitiveFields(options) = cntl.has_always_print_primitive_fields(); + std::string error; + bool ok = json2pb::ProtoMessageToProtoJson(message, output, options, &error); + if (!ok) { + LOG(INFO) << "Fail to serialize message=" + << message.GetDescriptor()->full_name() + << " to proto-json :" << error; + } + return ok; + }); + return serialize(serializer); + } else if (CONTENT_TYPE_PROTO_TEXT == content_type) { + Serializer serializer([&message](google::protobuf::io::ZeroCopyOutputStream* output) -> bool { + return google::protobuf::TextFormat::Print(message, output); + }); + return serialize(serializer); + } + return false; +} + static bool SerializeResponse(const google::protobuf::Message& res, - Controller& cntl, CompressType compress_type, - butil::IOBuf& buf) { + Controller& cntl, butil::IOBuf& buf) { if (res.GetDescriptor() == SerializedResponse::descriptor()) { buf.swap(((SerializedResponse&)res).serialized_data()); return true; } if (!res.IsInitialized()) { - cntl.SetFailed(ERESPONSE, - "Missing required fields in response: %s", + cntl.SetFailed(ERESPONSE, "Missing required fields in response: %s", res.InitializationErrorString().c_str()); return false; - } else if (!SerializeAsCompressedData(res, &buf, compress_type)) { - cntl.SetFailed(ERESPONSE, - "Fail to serialize response, CompressType=%s", - CompressTypeToCStr(compress_type)); + } + + ContentType content_type = cntl.response_content_type(); + CompressType compress_type = cntl.response_compress_type(); + if (!SerializeRpcMessage(res, cntl, content_type, compress_type, &buf)) { + cntl.SetFailed( + ERESPONSE, "Fail to serialize response=%s, " + "ContentType=%s, CompressType=%s", + res.GetDescriptor()->full_name().c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type)); return false; } return true; @@ -184,12 +260,9 @@ struct BaiduProxyPBMessages : public RpcPBMessages { } // Used by UT, can't be static. -void SendRpcResponse(int64_t correlation_id, - Controller* cntl, - RpcPBMessages* messages, - const Server* server, - MethodStatus* method_status, - int64_t received_us) { +void SendRpcResponse(int64_t correlation_id, Controller* cntl, + RpcPBMessages* messages, const Server* server, + MethodStatus* method_status, int64_t received_us) { ControllerPrivateAccessor accessor(cntl); Span* span = accessor.span(); if (span) { @@ -197,24 +270,29 @@ void SendRpcResponse(int64_t correlation_id, } Socket* sock = accessor.get_sending_socket(); - std::unique_ptr recycle_cntl(cntl); - ConcurrencyRemover concurrency_remover(method_status, cntl, received_us); + const google::protobuf::Message* req = NULL == messages ? NULL : messages->Request(); + const google::protobuf::Message* res = NULL == messages ? NULL : messages->Response(); + + // Recycle resources at the end of this function. + BRPC_SCOPE_EXIT { + { + // Remove concurrency and record latency at first. + ConcurrencyRemover concurrency_remover(method_status, cntl, received_us); + } + + std::unique_ptr recycle_cntl(cntl); - auto messages_guard = butil::MakeScopeGuard([server, messages] { if (NULL == messages) { return; } - if (NULL != server->options().baidu_master_service) { - BaiduProxyPBMessages::Return(static_cast(messages)); - } else { + + cntl->CallAfterRpcResp(req, res); + if (NULL == server->options().baidu_master_service) { server->options().rpc_pb_message_factory->Return(messages); + } else { + BaiduProxyPBMessages::Return(static_cast(messages)); } - }); - - const google::protobuf::Message* req = NULL == messages ? NULL : messages->Request(); - const google::protobuf::Message* res = NULL == messages ? NULL : messages->Response(); - ClosureGuard guard(brpc::NewCallback( - cntl, &Controller::CallAfterRpcResp, req, res)); + }; StreamIds response_stream_ids = accessor.response_streams(); @@ -232,8 +310,7 @@ void SendRpcResponse(int64_t correlation_id, // response either CompressType compress_type = cntl->response_compress_type(); if (res != NULL && !cntl->Failed()) { - append_body = SerializeResponse( - *res, *cntl, compress_type, res_body); + append_body = SerializeResponse(*res, *cntl, res_body); } // Don't use res->ByteSize() since it may be compressed @@ -260,6 +337,7 @@ void SendRpcResponse(int64_t correlation_id, } meta.set_correlation_id(correlation_id); meta.set_compress_type(compress_type); + meta.set_content_type(cntl->response_content_type()); if (attached_size > 0) { meta.set_attachment_size(attached_size); } @@ -299,32 +377,37 @@ void SendRpcResponse(int64_t correlation_id, } } + ResponseWriteInfo args; + bthread_id_t response_id = INVALID_BTHREAD_ID; if (span) { span->set_response_size(res_buf.size()); + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); } + // Send rpc response over stream even if server side failed to create // stream for some reason. - if(cntl->has_remote_stream()){ + if (cntl->has_remote_stream()) { // Send the response over stream to notify that this stream connection // is successfully built. // Response_stream can be INVALID_STREAM_ID when error occurs. if (SendStreamData(sock, &res_buf, accessor.remote_stream_settings()->stream_id(), - response_stream_id) != 0) { - const int errcode = errno; - std::string error_text = butil::string_printf(64, "Fail to write into %s", - sock->description().c_str()); - PLOG_IF(WARNING, errcode != EPIPE) << error_text; - cntl->SetFailed(errcode, "%s", error_text.c_str()); - Stream::SetFailed(response_stream_ids, errcode, "%s", - error_text.c_str()); + response_stream_id, response_id) != 0) { + error_code = errno; + PLOG_IF(WARNING, error_code != EPIPE) + << "Fail to write into " << sock->description(); + cntl->SetFailed(error_code, "Fail to write into %s", + sock->description().c_str()); + Stream::SetFailed(response_stream_ids, error_code, + "Fail to write into %s", + sock->description().c_str()); return; } // Now it's ok the mark these server-side streams as connected as all the // written user data would follower the RPC response. // Reuse stream_ptr to avoid address first stream id again - if(stream_ptr) { + if (stream_ptr) { ((Stream*)stream_ptr->conn())->SetConnected(); } for (size_t i = 1; i < response_stream_ids.size(); ++i) { @@ -344,6 +427,10 @@ void SendRpcResponse(int64_t correlation_id, // users to set max_concurrency. Socket::WriteOptions wopt; wopt.ignore_eovercrowded = true; + if (INVALID_BTHREAD_ID != response_id) { + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } if (sock->Write(&res_buf, &wopt) != 0) { const int errcode = errno; PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; @@ -354,8 +441,10 @@ void SendRpcResponse(int64_t correlation_id, } if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. // TODO: this is not sent - span->set_sent_us(butil::cpuwide_time_us()); + span->set_sent_us(args.sent_us); } } @@ -395,6 +484,71 @@ void EndRunningCallMethodInPool( return EndRunningUserCodeInPool(CallMethodInBackupThread, args); }; +bool DeserializeRpcMessage(const butil::IOBuf& data, Controller& cntl, + ContentType content_type, CompressType compress_type, + google::protobuf::Message* message) { + auto deserialize = [&](Deserializer& deserializer) -> bool { + bool ok; + if (COMPRESS_TYPE_NONE == compress_type) { + butil::IOBufAsZeroCopyInputStream stream(data); + ok = deserializer.DeserializeFrom(&stream); + } else { + const CompressHandler* handler = FindCompressHandler(compress_type); + if (NULL == handler) { + return false; + } + ok = handler->Decompress(data, &deserializer); + } + return ok; + }; + + if (CONTENT_TYPE_PB == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + return message->ParseFromZeroCopyStream(input); + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_JSON == content_type) { + Deserializer deserializer([message, &cntl]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + json2pb::Json2PbOptions options; + options.base64_to_bytes = cntl.has_pb_bytes_to_base64(); + options.array_to_single_repeated = cntl.has_pb_single_repeated_to_array(); + std::string error; + bool ok = json2pb::JsonToProtoMessage(input, message, options, &error); + if (!ok) { + LOG(INFO) << "Fail to parse json to " + << message->GetDescriptor()->full_name() + << ": "<< error; + } + return ok; + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_PROTO_JSON == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + json2pb::ProtoJson2PbOptions options; + options.ignore_unknown_fields = true; + std::string error; + bool ok = json2pb::ProtoJsonToProtoMessage(input, message, options, &error); + if (!ok) { + LOG(INFO) << "Fail to parse proto-json to " + << message->GetDescriptor()->full_name() + << ": "<< error; + } + return ok; + }); + return deserialize(deserializer); + } else if (CONTENT_TYPE_PROTO_TEXT == content_type) { + Deserializer deserializer([message]( + google::protobuf::io::ZeroCopyInputStream* input) -> bool { + return google::protobuf::TextFormat::Parse(input, message); + }); + return deserialize(deserializer); + } + return false; +} + void ProcessRpcRequest(InputMessageBase* msg_base) { const int64_t start_parse_us = butil::cpuwide_time_us(); DestroyingPtr msg(static_cast(msg_base)); @@ -445,6 +599,7 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { if (request_meta.has_timeout_ms()) { cntl->set_timeout_ms(request_meta.timeout_ms()); } + cntl->set_request_content_type(meta.content_type()); cntl->set_request_compress_type((CompressType)meta.compress_type()); accessor.set_server(server) .set_security_mode(security_mode) @@ -627,13 +782,17 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { cntl->request_attachment().swap(msg->payload); } - auto req_cmp_type = static_cast(meta.compress_type()); + ContentType content_type = meta.content_type(); + auto compress_type = static_cast(meta.compress_type()); messages = server->options().rpc_pb_message_factory->Get(*svc, *method); - if (!ParseFromCompressedData(req_buf, messages->Request(), req_cmp_type)) { - cntl->SetFailed(EREQUEST, "Fail to parse request message, " - "CompressType=%s, request_size=%d", - CompressTypeToCStr(req_cmp_type), req_size); - server->options().rpc_pb_message_factory->Return(messages); + if (!DeserializeRpcMessage(req_buf, *cntl, content_type, + compress_type, messages->Request())) { + cntl->SetFailed( + EREQUEST, "Fail to parse request=%s, ContentType=%s, " + "CompressType=%s, request_size=%d", + messages->Request()->GetDescriptor()->full_name().c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type), req_size); break; } req_buf.clear(); @@ -642,11 +801,9 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { // `socket' will be held until response has been sent google::protobuf::Closure* done = ::brpc::NewCallback< int64_t, Controller*, RpcPBMessages*, - const Server*, MethodStatus*, int64_t>(&SendRpcResponse, - meta.correlation_id(), - cntl.get(), messages, - server, method_status, - msg->received_us()); + const Server*, MethodStatus*, int64_t>( + &SendRpcResponse, meta.correlation_id(),cntl.get(), + messages, server, method_status, msg->received_us()); // optional, just release resource ASAP msg.reset(); @@ -787,8 +944,7 @@ void ProcessRpcResponse(InputMessageBase* msg_base) { if (meta.has_attachment_size()) { if (meta.attachment_size() > res_size) { cntl->SetFailed( - ERESPONSE, - "attachment_size=%d is larger than response_size=%d", + ERESPONSE, "attachment_size=%d is larger than response_size=%d", meta.attachment_size(), res_size); break; } @@ -798,20 +954,24 @@ void ProcessRpcResponse(InputMessageBase* msg_base) { cntl->response_attachment().swap(msg->payload); } - auto res_cmp_type = (CompressType)meta.compress_type(); - cntl->set_response_compress_type(res_cmp_type); + ContentType content_type = meta.content_type(); + auto compress_type = (CompressType)meta.compress_type(); + cntl->set_response_content_type(content_type); + cntl->set_response_compress_type(compress_type); if (cntl->response()) { if (cntl->response()->GetDescriptor() == SerializedResponse::descriptor()) { ((SerializedResponse*)cntl->response())-> serialized_data().append(*res_buf_ptr); - } else if (!ParseFromCompressedData( - *res_buf_ptr, cntl->response(), res_cmp_type)) { + } else if (!DeserializeRpcMessage(*res_buf_ptr, *cntl, content_type, + compress_type, cntl->response())) { cntl->SetFailed( - ERESPONSE, "Fail to parse response message, " - "CompressType=%s, response_size=%d", - CompressTypeToCStr(res_cmp_type), res_size); + EREQUEST, "Fail to parse response=%s, ContentType=%s, " + "CompressType=%s, request_size=%d", + cntl->response()->GetDescriptor()->full_name().c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type), res_size); } - } // else silently ignore the response. + } // else silently ignore the response. } while (0); // Unlocks correlation_id inside. Revert controller's // error code if it version check of `cid' fails @@ -819,6 +979,33 @@ void ProcessRpcResponse(InputMessageBase* msg_base) { accessor.OnResponse(cid, saved_error); } +void SerializeRpcRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* request) { + // Check sanity of request. + if (NULL == request) { + return cntl->SetFailed(EREQUEST, "`request' is NULL"); + } + if (request->GetDescriptor() == SerializedRequest::descriptor()) { + request_buf->append(((SerializedRequest*)request)->serialized_data()); + return; + } + if (!request->IsInitialized()) { + return cntl->SetFailed(EREQUEST, "Missing required fields in request: %s", + request->InitializationErrorString().c_str()); + } + + ContentType content_type = cntl->request_content_type(); + CompressType compress_type = cntl->request_compress_type(); + if (!SerializeRpcMessage(*request, *cntl, content_type, compress_type, request_buf)) { + return cntl->SetFailed( + EREQUEST, "Fail to compress request=%s, " + "ContentType=%s, CompressType=%s", + request->GetDescriptor()->full_name().c_str(), + ContentTypeToCStr(content_type), + CompressTypeToCStr(compress_type)); + } +} + void PackRpcRequest(butil::IOBuf* req_buf, SocketMessage**, uint64_t correlation_id, @@ -892,6 +1079,7 @@ void PackRpcRequest(butil::IOBuf* req_buf, request_meta->set_timeout_ms(accessor.real_timeout_ms()); } } + meta.set_content_type(cntl->request_content_type()); Span* span = accessor.span(); if (span) { @@ -907,5 +1095,20 @@ void PackRpcRequest(butil::IOBuf* req_buf, } } +const char* ContentTypeToCStr(ContentType content_type) { + switch (content_type) { + case CONTENT_TYPE_PB: + return "pb"; + case CONTENT_TYPE_JSON: + return "json"; + case CONTENT_TYPE_PROTO_JSON: + return "proto-json"; + case CONTENT_TYPE_PROTO_TEXT: + return "proto-text"; + default: + return "unknown"; + } +} + } // namespace policy } // namespace brpc diff --git a/src/brpc/policy/baidu_rpc_protocol.h b/src/brpc/policy/baidu_rpc_protocol.h index e3e4954b0c..77ecc780a2 100644 --- a/src/brpc/policy/baidu_rpc_protocol.h +++ b/src/brpc/policy/baidu_rpc_protocol.h @@ -37,6 +37,10 @@ void ProcessRpcResponse(InputMessageBase* msg); // Verify authentication information in baidu_std format bool VerifyRpcRequest(const InputMessageBase* msg); +// Serialize `request' into `buf'. +void SerializeRpcRequest(butil::IOBuf* request_buf, Controller* cntl, + const google::protobuf::Message* request); + // Pack `request' to `method' into `buf'. void PackRpcRequest(butil::IOBuf* buf, SocketMessage**, @@ -46,6 +50,9 @@ void PackRpcRequest(butil::IOBuf* buf, const butil::IOBuf& request, const Authenticator* auth); +// Returns the `name' of the 'content_type'. +const char* ContentTypeToCStr(ContentType content_type); + } // namespace policy } // namespace brpc diff --git a/src/brpc/policy/consistent_hashing_load_balancer.cpp b/src/brpc/policy/consistent_hashing_load_balancer.cpp index 19c8849c7d..2560d8f29b 100644 --- a/src/brpc/policy/consistent_hashing_load_balancer.cpp +++ b/src/brpc/policy/consistent_hashing_load_balancer.cpp @@ -19,6 +19,7 @@ #include // std::set_union #include #include +#include #include "butil/containers/flat_map.h" #include "butil/errno.h" #include "butil/strings/string_number_conversions.h" @@ -102,10 +103,10 @@ bool KetamaReplicaPolicy::Build(ServerId server, CHECK(num_replicas % points_per_hash == 0) << "Ketam hash replicas number(" << num_replicas << ") should be n*4"; for (size_t i = 0; i < num_replicas / points_per_hash; ++i) { - char host[32]; + char host[256]; int len = snprintf(host, sizeof(host), "%s-%lu", endpoint2str(ptr->remote_side()).c_str(), i); - unsigned char digest[16]; + unsigned char digest[MD5_DIGEST_LENGTH]; MD5HashSignature(host, len, digest); for (size_t j = 0; j < points_per_hash; ++j) { ConsistentHashingLoadBalancer::Node node; @@ -266,9 +267,6 @@ size_t ConsistentHashingLoadBalancer::RemoveServersInBatch( const size_t ret = _db_hash_ring.ModifyWithForeground(RemoveBatch, servers, &executed); CHECK(ret % _num_replicas == 0); const size_t n = ret / _num_replicas; - LOG_IF(ERROR, n != servers.size()) - << "Fail to RemoveServersInBatch, expected " << servers.size() - << " actually " << n; return n; } diff --git a/src/brpc/policy/constant_concurrency_limiter.cpp b/src/brpc/policy/constant_concurrency_limiter.cpp index be5f071c34..425c5f34c3 100644 --- a/src/brpc/policy/constant_concurrency_limiter.cpp +++ b/src/brpc/policy/constant_concurrency_limiter.cpp @@ -35,9 +35,15 @@ int ConstantConcurrencyLimiter::MaxConcurrency() { return _max_concurrency.load(butil::memory_order_relaxed); } +int ConstantConcurrencyLimiter::ResetMaxConcurrency( + const AdaptiveMaxConcurrency& amc) { + _max_concurrency.store(static_cast(amc), butil::memory_order_relaxed); + return 0; +} + ConstantConcurrencyLimiter* ConstantConcurrencyLimiter::New(const AdaptiveMaxConcurrency& amc) const { - CHECK_EQ(amc.type(), AdaptiveMaxConcurrency::CONSTANT()); + CHECK_EQ(amc.type(), AdaptiveMaxConcurrency::CONSTANT); return new ConstantConcurrencyLimiter(static_cast(amc)); } diff --git a/src/brpc/policy/constant_concurrency_limiter.h b/src/brpc/policy/constant_concurrency_limiter.h index f58a628636..9bae9393c9 100644 --- a/src/brpc/policy/constant_concurrency_limiter.h +++ b/src/brpc/policy/constant_concurrency_limiter.h @@ -33,6 +33,8 @@ class ConstantConcurrencyLimiter : public ConcurrencyLimiter { int MaxConcurrency() override; + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + ConstantConcurrencyLimiter* New(const AdaptiveMaxConcurrency&) const override; private: diff --git a/src/brpc/policy/dynpart_load_balancer.cpp b/src/brpc/policy/dynpart_load_balancer.cpp index 579ca7dd52..ad3cbbcbff 100644 --- a/src/brpc/policy/dynpart_load_balancer.cpp +++ b/src/brpc/policy/dynpart_load_balancer.cpp @@ -95,9 +95,6 @@ size_t DynPartLoadBalancer::AddServersInBatch( size_t DynPartLoadBalancer::RemoveServersInBatch( const std::vector& servers) { const size_t n = _db_servers.Modify(BatchRemove, servers); - LOG_IF(ERROR, n != servers.size()) - << "Fail to RemoveServersInBatch, expected " << servers.size() - << " actually " << n; return n; } diff --git a/src/brpc/policy/dynpart_load_balancer.h b/src/brpc/policy/dynpart_load_balancer.h index d3fd9d68eb..4e8833bfb9 100644 --- a/src/brpc/policy/dynpart_load_balancer.h +++ b/src/brpc/policy/dynpart_load_balancer.h @@ -33,14 +33,14 @@ namespace policy { class DynPartLoadBalancer : public LoadBalancer { public: - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - int SelectServer(const SelectIn& in, SelectOut* out); - DynPartLoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - void Describe(std::ostream&, const DescribeOptions& options); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + DynPartLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; private: struct Servers { diff --git a/src/brpc/policy/gzip_compress.cpp b/src/brpc/policy/gzip_compress.cpp index 35367e3ea1..e8c77a5563 100644 --- a/src/brpc/policy/gzip_compress.cpp +++ b/src/brpc/policy/gzip_compress.cpp @@ -17,57 +17,89 @@ #include // GzipXXXStream +#include #include "butil/logging.h" #include "brpc/policy/gzip_compress.h" #include "brpc/protocol.h" - +#include "brpc/compress.h" namespace brpc { namespace policy { -static void LogError(const google::protobuf::io::GzipOutputStream& gzip) { - if (gzip.ZlibErrorMessage()) { - LOG(WARNING) << "Fail to decompress: " << gzip.ZlibErrorMessage(); - } else { - LOG(WARNING) << "Fail to decompress."; +const char* Format2CStr(google::protobuf::io::GzipOutputStream::Format format) { + switch (format) { + case google::protobuf::io::GzipOutputStream::GZIP: + return "gzip"; + case google::protobuf::io::GzipOutputStream::ZLIB: + return "zlib"; + default: + return "unknown"; } } -static void LogError(const google::protobuf::io::GzipInputStream& gzip) { - if (gzip.ZlibErrorMessage()) { - LOG(WARNING) << "Fail to decompress: " << gzip.ZlibErrorMessage(); - } else { - LOG(WARNING) << "Fail to decompress."; +const char* Format2CStr(google::protobuf::io::GzipInputStream::Format format) { + switch (format) { + case google::protobuf::io::GzipInputStream::GZIP: + return "gzip"; + case google::protobuf::io::GzipInputStream::ZLIB: + return "zlib"; + default: + return "unknown"; } } -bool GzipCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { +static bool Compress(const google::protobuf::Message& msg, butil::IOBuf* buf, + google::protobuf::io::GzipOutputStream::Format format) { butil::IOBufAsZeroCopyOutputStream wrapper(buf); - google::protobuf::io::GzipOutputStream::Options gzip_opt; - gzip_opt.format = google::protobuf::io::GzipOutputStream::GZIP; - google::protobuf::io::GzipOutputStream gzip(&wrapper, gzip_opt); - if (!msg.SerializeToZeroCopyStream(&gzip)) { - LogError(gzip); - return false; + GzipCompressOptions options; + options.format = format; + google::protobuf::io::GzipOutputStream gzip(&wrapper, options); + bool ok; + if (msg.GetDescriptor() == Serializer::descriptor()) { + ok = ((const Serializer&)msg).SerializeTo(&gzip); + } else { + ok = msg.SerializeToZeroCopyStream(&gzip); + } + if (!ok) { + LOG(WARNING) << "Fail to serialize input message=" + << msg.GetDescriptor()->full_name() + << ", format=" << Format2CStr(format) << " : " + << (NULL == gzip.ZlibErrorMessage() ? "" : gzip.ZlibErrorMessage()); } - return gzip.Close(); + return ok && gzip.Close(); } -bool GzipDecompress(const butil::IOBuf& data, google::protobuf::Message* msg) { +static bool Decompress(const butil::IOBuf& data, google::protobuf::Message* msg, + google::protobuf::io::GzipInputStream::Format format) { butil::IOBufAsZeroCopyInputStream wrapper(data); - google::protobuf::io::GzipInputStream gzip( - &wrapper, google::protobuf::io::GzipInputStream::GZIP); - if (!ParsePbFromZeroCopyStream(msg, &gzip)) { - LogError(gzip); - return false; + google::protobuf::io::GzipInputStream gzip(&wrapper, format); + bool ok; + if (msg->GetDescriptor() == Deserializer::descriptor()) { + ok = ((Deserializer*)msg)->DeserializeFrom(&gzip); + } else { + ok = msg->ParseFromZeroCopyStream(&gzip); } - return true; + if (!ok) { + LOG(WARNING) << "Fail to deserialize input message=" + << msg->GetDescriptor()->full_name() + << ", format=" << Format2CStr(format) << " : " + << (NULL == gzip.ZlibErrorMessage() ? "" : gzip.ZlibErrorMessage()); + } + return ok; +} + +bool GzipCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { + return Compress(msg, buf, google::protobuf::io::GzipOutputStream::GZIP); +} + +bool GzipDecompress(const butil::IOBuf& data, google::protobuf::Message* msg) { + return Decompress(data, msg, google::protobuf::io::GzipInputStream::GZIP); } bool GzipCompress(const butil::IOBuf& msg, butil::IOBuf* buf, const GzipCompressOptions* options_in) { butil::IOBufAsZeroCopyOutputStream wrapper(buf); - google::protobuf::io::GzipOutputStream::Options gzip_opt; + GzipCompressOptions gzip_opt; if (options_in) { gzip_opt = *options_in; } @@ -93,7 +125,8 @@ bool GzipCompress(const butil::IOBuf& msg, butil::IOBuf* buf, } if (size_in != 0 || (size_t)in.ByteCount() != msg.size()) { // If any stage is not fully consumed, something went wrong. - LogError(out); + LOG(WARNING) << "Fail to compress, format=" << Format2CStr(gzip_opt.format) + << " : " << out.ZlibErrorMessage(); return false; } if (size_out != 0) { @@ -132,7 +165,8 @@ inline bool GzipDecompressBase( // If any stage is not fully consumed, something went wrong. // Here we call in.Next addtitionally to make sure that the gzip // "blackbox" does not have buffer left. - LogError(in); + LOG(WARNING) << "Fail to decompress, format=" << Format2CStr(format) + << " : " << in.ZlibErrorMessage(); return false; } if (size_out != 0) { @@ -141,19 +175,13 @@ inline bool GzipDecompressBase( return true; } -bool ZlibCompress(const google::protobuf::Message& res, butil::IOBuf* buf) { - butil::IOBufAsZeroCopyOutputStream wrapper(buf); - google::protobuf::io::GzipOutputStream::Options zlib_opt; - zlib_opt.format = google::protobuf::io::GzipOutputStream::ZLIB; - google::protobuf::io::GzipOutputStream zlib(&wrapper, zlib_opt); - return res.SerializeToZeroCopyStream(&zlib) && zlib.Close(); +bool ZlibCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { + return Compress(msg, buf, google::protobuf::io::GzipOutputStream::ZLIB); } -bool ZlibDecompress(const butil::IOBuf& data, google::protobuf::Message* req) { - butil::IOBufAsZeroCopyInputStream wrapper(data); - google::protobuf::io::GzipInputStream zlib( - &wrapper, google::protobuf::io::GzipInputStream::ZLIB); - return ParsePbFromZeroCopyStream(req, &zlib); +bool ZlibDecompress(const butil::IOBuf& data, + google::protobuf::Message* msg) { + return Decompress(data, msg, google::protobuf::io::GzipInputStream::ZLIB); } bool GzipDecompress(const butil::IOBuf& data, butil::IOBuf* msg) { diff --git a/src/brpc/policy/hasher.cpp b/src/brpc/policy/hasher.cpp index f533099a18..ba33eb54c3 100644 --- a/src/brpc/policy/hasher.cpp +++ b/src/brpc/policy/hasher.cpp @@ -28,12 +28,12 @@ namespace policy { void MD5HashSignature(const void* key, size_t len, unsigned char* results) { MD5_CTX my_md5; MD5_Init(&my_md5); - MD5_Update(&my_md5, (const unsigned char *)key, len); + MD5_Update(&my_md5, key, len); MD5_Final(results, &my_md5); } uint32_t MD5Hash32(const void* key, size_t len) { - unsigned char results[16]; + unsigned char results[MD5_DIGEST_LENGTH]; MD5HashSignature(key, len, results); return ((uint32_t) (results[3] & 0xFF) << 24) | ((uint32_t) (results[2] & 0xFF) << 16) @@ -48,7 +48,7 @@ uint32_t MD5Hash32V(const butil::StringPiece* keys, size_t num_keys) { MD5_Update(&ctx, (const unsigned char *)keys[i].data(), keys[i].size()); } - unsigned char results[16]; + unsigned char results[MD5_DIGEST_LENGTH]; MD5_Final(results, &ctx); return ((uint32_t) (results[3] & 0xFF) << 24) | ((uint32_t) (results[2] & 0xFF) << 16) diff --git a/src/brpc/policy/http_rpc_protocol.cpp b/src/brpc/policy/http_rpc_protocol.cpp index 444be39708..374b1df9fb 100644 --- a/src/brpc/policy/http_rpc_protocol.cpp +++ b/src/brpc/policy/http_rpc_protocol.cpp @@ -19,27 +19,26 @@ #include // MethodDescriptor #include #include -#include // ProtoMessageToJson -#include // JsonToProtoMessage #include - #include "brpc/policy/http_rpc_protocol.h" #include "butil/unique_ptr.h" // std::unique_ptr #include "butil/string_splitter.h" // StringMultiSplitter #include "butil/string_printf.h" #include "butil/time.h" #include "butil/sys_byteorder.h" +#include "json2pb/pb_to_json.h" // ProtoMessageToJson +#include "json2pb/json_to_pb.h" // JsonToProtoMessage #include "brpc/compress.h" -#include "brpc/errno.pb.h" // ENOSERVICE, ENOMETHOD -#include "brpc/controller.h" // Controller -#include "brpc/server.h" // Server +#include "brpc/errno.pb.h" // ENOSERVICE, ENOMETHOD +#include "brpc/controller.h" // Controller +#include "brpc/server.h" // Server #include "brpc/details/server_private_accessor.h" #include "brpc/span.h" -#include "brpc/socket.h" // Socket -#include "brpc/rpc_dump.h" // SampledRequest -#include "brpc/http_status_code.h" // HTTP_STATUS_* +#include "brpc/socket.h" // Socket +#include "brpc/rpc_dump.h" // SampledRequest +#include "brpc/http_status_code.h" // HTTP_STATUS_* #include "brpc/details/controller_private_accessor.h" -#include "brpc/builtin/index_service.h" // IndexService +#include "brpc/builtin/index_service.h" // IndexService #include "brpc/policy/gzip_compress.h" #include "brpc/policy/http2_rpc_protocol.h" #include "brpc/details/usercode_backup_pool.h" @@ -203,6 +202,9 @@ HttpContentType ParseContentType(butil::StringPiece ct, bool* is_grpc_ct) { if (ct.starts_with("json")) { type = HTTP_CONTENT_JSON; ct.remove_prefix(4); + } else if (ct.starts_with("proto-json")) { + type = HTTP_CONTENT_PROTO_JSON; + ct.remove_prefix(10); } else if (ct.starts_with("proto-text")) { type = HTTP_CONTENT_PROTO_TEXT; ct.remove_prefix(10); @@ -271,6 +273,75 @@ static bool RemoveGrpcPrefix(butil::IOBuf* body, bool* compressed) { return (message_length + 5 == sz); } +static bool JsonToProtoMessage(const butil::IOBuf& body, + google::protobuf::Message* message, + Controller* cntl, int error_code) { + butil::IOBufAsZeroCopyInputStream wrapper(body); + json2pb::Json2PbOptions options; + options.base64_to_bytes = cntl->has_pb_bytes_to_base64(); + options.array_to_single_repeated = cntl->has_pb_single_repeated_to_array(); + std::string error; + bool ok = json2pb::JsonToProtoMessage(&wrapper, message, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to parse http json body as %s: %s", + message->GetDescriptor()->full_name().c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoMessageToJson(const google::protobuf::Message& message, + butil::IOBufAsZeroCopyOutputStream* wrapper, + Controller* cntl, int error_code) { + json2pb::Pb2JsonOptions options; + options.bytes_to_base64 = cntl->has_pb_bytes_to_base64(); + options.jsonify_empty_array = cntl->has_pb_jsonify_empty_array(); + options.always_print_primitive_fields = cntl->has_always_print_primitive_fields(); + options.single_repeated_to_array = cntl->has_pb_single_repeated_to_array(); + options.enum_option = FLAGS_pb_enum_as_number + ? json2pb::OUTPUT_ENUM_BY_NUMBER + : json2pb::OUTPUT_ENUM_BY_NAME; + std::string error; + bool ok = json2pb::ProtoMessageToJson(message, wrapper, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to convert %s to json: %s", + message.GetDescriptor()->full_name().c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoJsonToProtoMessage(const butil::IOBuf& body, + google::protobuf::Message* message, + Controller* cntl, int error_code) { + json2pb::ProtoJson2PbOptions options; + options.ignore_unknown_fields = true; + butil::IOBufAsZeroCopyInputStream wrapper(body); + std::string error; + bool ok = json2pb::ProtoJsonToProtoMessage(&wrapper, message, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to parse http proto-json body as %s: %s", + message->GetDescriptor()->full_name().c_str(), + error.c_str()); + } + return ok; +} + +static bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + butil::IOBufAsZeroCopyOutputStream* wrapper, + Controller* cntl, int error_code) { + json2pb::Pb2ProtoJsonOptions options; + AlwaysPrintPrimitiveFields(options) = cntl->has_always_print_primitive_fields(); + options.always_print_enums_as_ints = FLAGS_pb_enum_as_number; + std::string error; + bool ok = json2pb::ProtoMessageToProtoJson(message, wrapper, options, &error); + if (!ok) { + cntl->SetFailed(error_code, "Fail to convert %s to proto-json: %s", + message.GetDescriptor()->full_name().c_str(), error.c_str()); + } + return ok; +} + void ProcessHttpResponse(InputMessageBase* msg) { const int64_t start_parse_us = butil::cpuwide_time_us(); DestroyingPtr imsg_guard(static_cast(msg)); @@ -435,8 +506,8 @@ void ProcessHttpResponse(InputMessageBase* msg) { if (grpc_compressed) { encoding = res_header->GetHeader(common->GRPC_ENCODING); if (encoding == NULL) { - cntl->SetFailed(ERESPONSE, "Fail to find header `grpc-encoding'" - " in compressed gRPC response"); + cntl->SetFailed(ERESPONSE, "Fail to find header `grpc-encoding' " + "in compressed gRPC response"); break; } } @@ -455,23 +526,24 @@ void ProcessHttpResponse(InputMessageBase* msg) { } if (content_type == HTTP_CONTENT_PROTO) { if (!ParsePbFromIOBuf(cntl->response(), res_body)) { - cntl->SetFailed(ERESPONSE, "Fail to parse content"); + cntl->SetFailed(ERESPONSE, "Fail to parse content as %s", + cntl->response()->GetDescriptor()->full_name().c_str()); break; } } else if (content_type == HTTP_CONTENT_PROTO_TEXT) { if (!ParsePbTextFromIOBuf(cntl->response(), res_body)) { - cntl->SetFailed(ERESPONSE, "Fail to parse proto-text content"); + cntl->SetFailed(ERESPONSE, "Fail to parse proto-text content as %s", + cntl->response()->GetDescriptor()->full_name().c_str()); break; } } else if (content_type == HTTP_CONTENT_JSON) { - // message body is json - butil::IOBufAsZeroCopyInputStream wrapper(res_body); - std::string err; - json2pb::Json2PbOptions options; - options.base64_to_bytes = cntl->has_pb_bytes_to_base64(); - options.array_to_single_repeated = cntl->has_pb_single_repeated_to_array(); - if (!json2pb::JsonToProtoMessage(&wrapper, cntl->response(), options, &err)) { - cntl->SetFailed(ERESPONSE, "Fail to parse content, %s", err.c_str()); + // Message body is json. + if (!JsonToProtoMessage(res_body, cntl->response(), cntl, ERESPONSE)) { + break; + } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + // Message body is json. + if (!ProtoJsonToProtoMessage(res_body, cntl->response(), cntl, ERESPONSE)) { break; } } else { @@ -530,8 +602,7 @@ void SerializeHttpRequest(butil::IOBuf* /*not used*/, } } else { bool is_grpc_ct = false; - content_type = ParseContentType(hreq.content_type(), - &is_grpc_ct); + content_type = ParseContentType(hreq.content_type(), &is_grpc_ct); is_grpc = (is_http2 && is_grpc_ct); } @@ -549,21 +620,15 @@ void SerializeHttpRequest(butil::IOBuf* /*not used*/, return cntl->SetFailed(EREQUEST, "Fail to print %s as proto-text", pbreq->GetTypeName().c_str()); } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + if (!ProtoMessageToProtoJson(*pbreq, &wrapper, cntl, EREQUEST)) { + cntl->request_attachment().clear(); + return; + } } else if (content_type == HTTP_CONTENT_JSON) { - std::string err; - json2pb::Pb2JsonOptions opt; - opt.bytes_to_base64 = cntl->has_pb_bytes_to_base64(); - opt.jsonify_empty_array = cntl->has_pb_jsonify_empty_array(); - opt.always_print_primitive_fields = cntl->has_always_print_primitive_fields(); - opt.single_repeated_to_array = cntl->has_pb_single_repeated_to_array(); - - opt.enum_option = (FLAGS_pb_enum_as_number - ? json2pb::OUTPUT_ENUM_BY_NUMBER - : json2pb::OUTPUT_ENUM_BY_NAME); - if (!json2pb::ProtoMessageToJson(*pbreq, &wrapper, opt, &err)) { + if (!ProtoMessageToJson(*pbreq, &wrapper, cntl, EREQUEST)) { cntl->request_attachment().clear(); - return cntl->SetFailed( - EREQUEST, "Fail to convert request to json, %s", err.c_str()); + return; } } else { return cntl->SetFailed( @@ -746,8 +811,10 @@ class HttpResponseSenderAsDone : public google::protobuf::Closure { public: explicit HttpResponseSenderAsDone(HttpResponseSender* s) : _sender(std::move(*s)) {} void Run() override { - _sender._cntl->CallAfterRpcResp( - _sender._messages->Request(), _sender._messages->Response()); + if (NULL != _sender._messages) { + _sender._cntl->CallAfterRpcResp(_sender._messages->Request(), + _sender._messages->Response()); + } delete this; } @@ -819,19 +886,10 @@ HttpResponseSender::~HttpResponseSender() { if (!google::protobuf::TextFormat::Print(*res, &wrapper)) { cntl->SetFailed(ERESPONSE, "Fail to print %s as proto-text", res->GetTypeName().c_str()); } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + ProtoMessageToProtoJson(*res, &wrapper, cntl, ERESPONSE); } else { - std::string err; - json2pb::Pb2JsonOptions opt; - opt.bytes_to_base64 = cntl->has_pb_bytes_to_base64(); - opt.jsonify_empty_array = cntl->has_pb_jsonify_empty_array(); - opt.always_print_primitive_fields = cntl->has_always_print_primitive_fields(); - opt.single_repeated_to_array = cntl->has_pb_single_repeated_to_array(); - opt.enum_option = (FLAGS_pb_enum_as_number - ? json2pb::OUTPUT_ENUM_BY_NUMBER - : json2pb::OUTPUT_ENUM_BY_NAME); - if (!json2pb::ProtoMessageToJson(*res, &wrapper, opt, &err)) { - cntl->SetFailed(ERESPONSE, "Fail to convert response to json, %s", err.c_str()); - } + ProtoMessageToJson(*res, &wrapper, cntl, ERESPONSE); } } @@ -934,8 +992,15 @@ HttpResponseSender::~HttpResponseSender() { int rc = -1; // Have the risk of unlimited pending responses, in which case, tell // users to set max_concurrency. + ResponseWriteInfo args; Socket::WriteOptions wopt; wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } if (is_http2) { if (is_grpc) { // Append compressed and length before body @@ -980,9 +1045,12 @@ HttpResponseSender::~HttpResponseSender() { cntl->SetFailed(errcode, "Fail to write into %s", socket->description().c_str()); return; } + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. // TODO: this is not sent - span->set_sent_us(butil::cpuwide_time_us()); + span->set_sent_us(args.sent_us); } } @@ -1600,17 +1668,14 @@ void ProcessHttpRequest(InputMessageBase *msg) { req->GetDescriptor()->full_name().c_str()); return; } + } else if (content_type == HTTP_CONTENT_PROTO_JSON) { + if (!ProtoJsonToProtoMessage(req_body, req, cntl, EREQUEST)) { + return; + } } else { - butil::IOBufAsZeroCopyInputStream wrapper(req_body); - std::string err; - json2pb::Json2PbOptions options; - options.base64_to_bytes = mp->params.pb_bytes_to_base64; - options.array_to_single_repeated = mp->params.pb_single_repeated_to_array; cntl->set_pb_bytes_to_base64(mp->params.pb_bytes_to_base64); cntl->set_pb_single_repeated_to_array(mp->params.pb_single_repeated_to_array); - if (!json2pb::JsonToProtoMessage(&wrapper, req, options, &err)) { - cntl->SetFailed(EREQUEST, "Fail to parse http body as %s, %s", - req->GetDescriptor()->full_name().c_str(), err.c_str()); + if (!JsonToProtoMessage(req_body, req, cntl, EREQUEST)) { return; } } @@ -1698,7 +1763,7 @@ void HttpContext::CheckProgressiveRead(const void* arg, Socket *socket) { header().uri().path(), (Server *)arg, const_cast(&header().unresolved_path())); if (sp != NULL && sp->params.enable_progressive_read) { - this->set_read_body_progressively(true); + set_read_body_progressively(true); socket->read_will_be_progressive(CONNECTION_TYPE_SHORT); } } diff --git a/src/brpc/policy/http_rpc_protocol.h b/src/brpc/policy/http_rpc_protocol.h index 918e69d0fa..bc8bd06593 100644 --- a/src/brpc/policy/http_rpc_protocol.h +++ b/src/brpc/policy/http_rpc_protocol.h @@ -149,6 +149,7 @@ enum HttpContentType { HTTP_CONTENT_JSON = 1, HTTP_CONTENT_PROTO = 2, HTTP_CONTENT_PROTO_TEXT = 3, + HTTP_CONTENT_PROTO_JSON = 4, }; // Parse from the textual content type. One type may have more than one literals. diff --git a/src/brpc/policy/hulu_pbrpc_protocol.cpp b/src/brpc/policy/hulu_pbrpc_protocol.cpp index bf4bd86f5c..02ec8efcad 100644 --- a/src/brpc/policy/hulu_pbrpc_protocol.cpp +++ b/src/brpc/policy/hulu_pbrpc_protocol.cpp @@ -304,8 +304,15 @@ static void SendHuluResponse(int64_t correlation_id, // Have the risk of unlimited pending responses, in which case, tell // users to set max_concurrency. + ResponseWriteInfo args; Socket::WriteOptions wopt; wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } if (sock->Write(&res_buf, &wopt) != 0) { const int errcode = errno; PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; @@ -313,9 +320,12 @@ static void SendHuluResponse(int64_t correlation_id, sock->description().c_str()); return; } + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. // TODO: this is not sent - span->set_sent_us(butil::cpuwide_time_us()); + span->set_sent_us(args.sent_us); } } diff --git a/src/brpc/policy/locality_aware_load_balancer.h b/src/brpc/policy/locality_aware_load_balancer.h index 4129a2d578..82373a36dd 100644 --- a/src/brpc/policy/locality_aware_load_balancer.h +++ b/src/brpc/policy/locality_aware_load_balancer.h @@ -41,16 +41,16 @@ DECLARE_double(punish_inflight_ratio); class LocalityAwareLoadBalancer : public LoadBalancer { public: LocalityAwareLoadBalancer(); - ~LocalityAwareLoadBalancer(); - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - LocalityAwareLoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - int SelectServer(const SelectIn& in, SelectOut* out); - void Feedback(const CallInfo& info); - void Describe(std::ostream& os, const DescribeOptions& options); + ~LocalityAwareLoadBalancer() override; + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + LocalityAwareLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + void Feedback(const CallInfo& info) override; + void Describe(std::ostream& os, const DescribeOptions& options) override; private: struct TimeInfo { diff --git a/src/brpc/policy/nshead_protocol.cpp b/src/brpc/policy/nshead_protocol.cpp index 4288085d68..a26dc96857 100644 --- a/src/brpc/policy/nshead_protocol.cpp +++ b/src/brpc/policy/nshead_protocol.cpp @@ -95,6 +95,7 @@ void NsheadClosure::Run() { return; } + int64_t sent_us = 0; if (_do_respond) { // response uses request's head as default. // Notice that the response use request.head.log_id directly rather @@ -112,8 +113,15 @@ void NsheadClosure::Run() { write_buf.append(_response.body.movable()); // Have the risk of unlimited pending responses, in which case, tell // users to set max_concurrency. + ResponseWriteInfo args; Socket::WriteOptions wopt; wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } if (sock->Write(&write_buf, &wopt) != 0) { const int errcode = errno; PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; @@ -121,10 +129,16 @@ void NsheadClosure::Run() { sock->description().c_str()); return; } + + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. + sent_us = args.sent_us; + } } if (span) { // TODO: this is not sent - span->set_sent_us(butil::cpuwide_time_us()); + span->set_sent_us(0 == sent_us ? butil::cpuwide_time_us() : sent_us); } } diff --git a/src/brpc/policy/randomized_load_balancer.cpp b/src/brpc/policy/randomized_load_balancer.cpp index cac695af1e..5c4ba447d7 100644 --- a/src/brpc/policy/randomized_load_balancer.cpp +++ b/src/brpc/policy/randomized_load_balancer.cpp @@ -25,14 +25,6 @@ namespace brpc { namespace policy { -const uint32_t prime_offset[] = { -#include "bthread/offset_inl.list" -}; - -inline uint32_t GenRandomStride() { - return prime_offset[butil::fast_rand_less_than(ARRAY_SIZE(prime_offset))]; -} - bool RandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { if (bg.server_list.capacity() < 128) { bg.server_list.reserve(128); @@ -97,9 +89,6 @@ size_t RandomizedLoadBalancer::AddServersInBatch( size_t RandomizedLoadBalancer::RemoveServersInBatch( const std::vector& servers) { const size_t n = _db_servers.Modify(BatchRemove, servers); - LOG_IF(ERROR, n != servers.size()) - << "Fail to RemoveServersInBatch, expected " << servers.size() - << " actually " << n; return n; } diff --git a/src/brpc/policy/randomized_load_balancer.h b/src/brpc/policy/randomized_load_balancer.h index e599648bd6..3787e45a5c 100644 --- a/src/brpc/policy/randomized_load_balancer.h +++ b/src/brpc/policy/randomized_load_balancer.h @@ -33,14 +33,14 @@ namespace policy { // than RoundRobinLoadBalancer. class RandomizedLoadBalancer : public LoadBalancer { public: - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - int SelectServer(const SelectIn& in, SelectOut* out); - RandomizedLoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - void Describe(std::ostream& os, const DescribeOptions&); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + RandomizedLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream& os, const DescribeOptions&) override; private: struct Servers { diff --git a/src/brpc/policy/redis_protocol.cpp b/src/brpc/policy/redis_protocol.cpp index 94524e8b75..f8acf49d6a 100644 --- a/src/brpc/policy/redis_protocol.cpp +++ b/src/brpc/policy/redis_protocol.cpp @@ -54,27 +54,6 @@ struct InputResponse : public InputMessageBase { } }; -// This class is as parsing_context in socket. -class RedisConnContext : public Destroyable { -public: - explicit RedisConnContext(const RedisService* rs) - : redis_service(rs) - , batched_size(0) {} - - ~RedisConnContext(); - // @Destroyable - void Destroy() override; - - const RedisService* redis_service; - // If user starts a transaction, transaction_handler indicates the - // handler pointer that runs the transaction command. - std::unique_ptr transaction_handler; - // >0 if command handler is run in batched mode. - int batched_size; - - RedisCommandParser parser; - butil::Arena arena; -}; int ConsumeCommand(RedisConnContext* ctx, const std::vector& args, @@ -83,7 +62,7 @@ int ConsumeCommand(RedisConnContext* ctx, RedisReply output(&ctx->arena); RedisCommandHandlerResult result = REDIS_CMD_HANDLED; if (ctx->transaction_handler) { - result = ctx->transaction_handler->Run(args, &output, flush_batched); + result = ctx->transaction_handler->Run(ctx, args, &output, flush_batched); if (result == REDIS_CMD_HANDLED) { ctx->transaction_handler.reset(NULL); } else if (result == REDIS_CMD_BATCHED) { @@ -97,7 +76,7 @@ int ConsumeCommand(RedisConnContext* ctx, snprintf(buf, sizeof(buf), "ERR unknown command `%s`", args[0].as_string().c_str()); output.SetError(buf); } else { - result = ch->Run(args, &output, flush_batched); + result = ch->Run(ctx, args, &output, flush_batched); if (result == REDIS_CMD_CONTINUE) { if (ctx->batched_size != 0) { LOG(ERROR) << "CONTINUE should not be returned in a batched process."; @@ -134,15 +113,6 @@ int ConsumeCommand(RedisConnContext* ctx, return 0; } -// ========== impl of RedisConnContext ========== - -RedisConnContext::~RedisConnContext() { } - -void RedisConnContext::Destroy() { - delete this; -} - -// ========== impl of RedisConnContext ========== ParseResult ParseRedisMessage(butil::IOBuf* source, Socket* socket, bool read_eof, const void* arg) { diff --git a/src/brpc/policy/round_robin_load_balancer.cpp b/src/brpc/policy/round_robin_load_balancer.cpp index c7dd972d20..1d16131aeb 100644 --- a/src/brpc/policy/round_robin_load_balancer.cpp +++ b/src/brpc/policy/round_robin_load_balancer.cpp @@ -25,14 +25,6 @@ namespace brpc { namespace policy { -const uint32_t prime_offset[] = { -#include "bthread/offset_inl.list" -}; - -inline uint32_t GenRandomStride() { - return prime_offset[butil::fast_rand_less_than(ARRAY_SIZE(prime_offset))]; -} - bool RoundRobinLoadBalancer::Add(Servers& bg, const ServerId& id) { if (bg.server_list.capacity() < 128) { bg.server_list.reserve(128); @@ -97,9 +89,6 @@ size_t RoundRobinLoadBalancer::AddServersInBatch( size_t RoundRobinLoadBalancer::RemoveServersInBatch( const std::vector& servers) { const size_t n = _db_servers.Modify(BatchRemove, servers); - LOG_IF(ERROR, n != servers.size()) - << "Fail to RemoveServersInBatch, expected " << servers.size() - << " actually " << n; return n; } diff --git a/src/brpc/policy/round_robin_load_balancer.h b/src/brpc/policy/round_robin_load_balancer.h index c5a34a8c5c..f087dcdc42 100644 --- a/src/brpc/policy/round_robin_load_balancer.h +++ b/src/brpc/policy/round_robin_load_balancer.h @@ -32,14 +32,14 @@ namespace policy { // at the same time) are very close. class RoundRobinLoadBalancer : public LoadBalancer { public: - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - int SelectServer(const SelectIn& in, SelectOut* out); - RoundRobinLoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - void Describe(std::ostream&, const DescribeOptions& options); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + RoundRobinLoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; private: struct Servers { diff --git a/src/brpc/policy/snappy_compress.cpp b/src/brpc/policy/snappy_compress.cpp index 77e80170e1..8019b97b3c 100644 --- a/src/brpc/policy/snappy_compress.cpp +++ b/src/brpc/policy/snappy_compress.cpp @@ -20,32 +20,53 @@ #include "butil/third_party/snappy/snappy.h" #include "brpc/policy/snappy_compress.h" #include "brpc/protocol.h" - +#include "brpc/compress.h" namespace brpc { namespace policy { -bool SnappyCompress(const google::protobuf::Message& res, butil::IOBuf* buf) { +bool SnappyCompress(const google::protobuf::Message& msg, butil::IOBuf* buf) { butil::IOBuf serialized_pb; butil::IOBufAsZeroCopyOutputStream wrapper(&serialized_pb); - if (res.SerializeToZeroCopyStream(&wrapper)) { - butil::IOBufAsSnappySource source(serialized_pb); - butil::IOBufAsSnappySink sink(*buf); - return butil::snappy::Compress(&source, &sink); + bool ok; + if (msg.GetDescriptor() == Serializer::descriptor()) { + ok = ((const Serializer&)msg).SerializeTo(&wrapper); + } else { + ok = msg.SerializeToZeroCopyStream(&wrapper); + } + if (!ok) { + LOG(WARNING) << "Fail to serialize input pb=" + << msg.GetDescriptor()->full_name(); + return false; + } + + ok = SnappyCompress(serialized_pb, buf); + if (!ok) { + LOG(WARNING) << "Fail to snappy::Compress, size=" + << serialized_pb.size(); } - LOG(WARNING) << "Fail to serialize input pb=" << &res; - return false; + return ok; } -bool SnappyDecompress(const butil::IOBuf& data, google::protobuf::Message* req) { - butil::IOBufAsSnappySource source(data); +bool SnappyDecompress(const butil::IOBuf& data, google::protobuf::Message* msg) { butil::IOBuf binary_pb; - butil::IOBufAsSnappySink sink(binary_pb); - if (butil::snappy::Uncompress(&source, &sink)) { - return ParsePbFromIOBuf(req, binary_pb); + if (!SnappyDecompress(data, &binary_pb)) { + LOG(WARNING) << "Fail to snappy::Uncompress, size=" << data.size(); + return false; + } + + bool ok; + butil::IOBufAsZeroCopyInputStream stream(binary_pb); + if (msg->GetDescriptor() == Deserializer::descriptor()) { + ok = ((Deserializer*)msg)->DeserializeFrom(&stream); + } else { + ok = msg->ParseFromZeroCopyStream(&stream); + } + if (!ok) { + LOG(WARNING) << "Fail to eserialize input message=" + << msg->GetDescriptor()->full_name(); } - LOG(WARNING) << "Fail to snappy::Uncompress, size=" << data.size(); - return false; + return ok; } bool SnappyCompress(const butil::IOBuf& in, butil::IOBuf* out) { diff --git a/src/brpc/policy/sofa_pbrpc_protocol.cpp b/src/brpc/policy/sofa_pbrpc_protocol.cpp index ae128b4051..9ee772dcff 100644 --- a/src/brpc/policy/sofa_pbrpc_protocol.cpp +++ b/src/brpc/policy/sofa_pbrpc_protocol.cpp @@ -281,8 +281,15 @@ static void SendSofaResponse(int64_t correlation_id, } // Have the risk of unlimited pending responses, in which case, tell // users to set max_concurrency. + ResponseWriteInfo args; Socket::WriteOptions wopt; wopt.ignore_eovercrowded = true; + bthread_id_t response_id = INVALID_BTHREAD_ID; + if (span) { + CHECK_EQ(0, bthread_id_create(&response_id, &args, HandleResponseWritten)); + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } if (sock->Write(&res_buf, &wopt) != 0) { const int errcode = errno; PLOG_IF(WARNING, errcode != EPIPE) << "Fail to write into " << *sock; @@ -290,9 +297,12 @@ static void SendSofaResponse(int64_t correlation_id, sock->description().c_str()); return; } + if (span) { + bthread_id_join(response_id); + // Do not care about the result of background writing. // TODO: this is not sent - span->set_sent_us(butil::cpuwide_time_us()); + span->set_sent_us(args.sent_us); } } diff --git a/src/brpc/policy/streaming_rpc_protocol.cpp b/src/brpc/policy/streaming_rpc_protocol.cpp index 85d2a7d536..0921d005e7 100644 --- a/src/brpc/policy/streaming_rpc_protocol.cpp +++ b/src/brpc/policy/streaming_rpc_protocol.cpp @@ -154,7 +154,8 @@ void SendStreamClose(Socket *sock, int64_t remote_stream_id, } int SendStreamData(Socket* sock, const butil::IOBuf* data, - int64_t remote_stream_id, int64_t source_stream_id) { + int64_t remote_stream_id, int64_t source_stream_id, + bthread_id_t response_id) { CHECK(sock != NULL); StreamFrameMeta fm; fm.set_stream_id(remote_stream_id); @@ -164,6 +165,10 @@ int SendStreamData(Socket* sock, const butil::IOBuf* data, butil::IOBuf out; PackStreamMessage(&out, fm, data); Socket::WriteOptions wopt; + if (INVALID_BTHREAD_ID != response_id) { + wopt.id_wait = response_id; + wopt.notify_on_success = true; + } wopt.ignore_eovercrowded = true; return sock->Write(&out, &wopt); } diff --git a/src/brpc/policy/streaming_rpc_protocol.h b/src/brpc/policy/streaming_rpc_protocol.h index ff9e207a19..22023e7c3f 100644 --- a/src/brpc/policy/streaming_rpc_protocol.h +++ b/src/brpc/policy/streaming_rpc_protocol.h @@ -19,10 +19,10 @@ #ifndef BRPC_STREAMING_RPC_PROTOCOL_H #define BRPC_STREAMING_RPC_PROTOCOL_H +#include "bthread/types.h" #include "brpc/protocol.h" #include "brpc/streaming_rpc_meta.pb.h" - namespace brpc { namespace policy { @@ -41,7 +41,8 @@ void SendStreamClose(Socket *sock, int64_t remote_stream_id, int64_t source_stream_id); int SendStreamData(Socket* sock, const butil::IOBuf* data, - int64_t remote_stream_id, int64_t source_stream_id); + int64_t remote_stream_id, int64_t source_stream_id, + bthread_id_t); } // namespace policy } // namespace brpc diff --git a/src/brpc/policy/timeout_concurrency_limiter.cpp b/src/brpc/policy/timeout_concurrency_limiter.cpp index 98c1a20087..b2582eb12b 100644 --- a/src/brpc/policy/timeout_concurrency_limiter.cpp +++ b/src/brpc/policy/timeout_concurrency_limiter.cpp @@ -117,6 +117,11 @@ int TimeoutConcurrencyLimiter::MaxConcurrency() { return FLAGS_timeout_cl_max_concurrency; } +int TimeoutConcurrencyLimiter::ResetMaxConcurrency( + const AdaptiveMaxConcurrency &) { + return -1; +} + bool TimeoutConcurrencyLimiter::AddSample(int error_code, int64_t latency_us, int64_t sampling_time_us) { std::unique_lock lock_guard(_sw_mutex); diff --git a/src/brpc/policy/timeout_concurrency_limiter.h b/src/brpc/policy/timeout_concurrency_limiter.h index 3f0485eeee..f7e4dde6a0 100644 --- a/src/brpc/policy/timeout_concurrency_limiter.h +++ b/src/brpc/policy/timeout_concurrency_limiter.h @@ -34,6 +34,8 @@ class TimeoutConcurrencyLimiter : public ConcurrencyLimiter { int MaxConcurrency() override; + int ResetMaxConcurrency(const AdaptiveMaxConcurrency&) override; + TimeoutConcurrencyLimiter* New( const AdaptiveMaxConcurrency&) const override; diff --git a/src/brpc/policy/weighted_randomized_load_balancer.cpp b/src/brpc/policy/weighted_randomized_load_balancer.cpp index 0e741ef8e9..28cd7e3f17 100644 --- a/src/brpc/policy/weighted_randomized_load_balancer.cpp +++ b/src/brpc/policy/weighted_randomized_load_balancer.cpp @@ -26,8 +26,9 @@ namespace brpc { namespace policy { -static bool server_compare(const WeightedRandomizedLoadBalancer::Server& lhs, const WeightedRandomizedLoadBalancer::Server& rhs) { - return (lhs.current_weight_sum < rhs.current_weight_sum); +static bool server_compare(const WeightedRandomizedLoadBalancer::Server& lhs, + const WeightedRandomizedLoadBalancer::Server& rhs) { + return lhs.current_weight_sum < rhs.current_weight_sum; } bool WeightedRandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { @@ -38,7 +39,8 @@ bool WeightedRandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { if (!butil::StringToUint(id.tag, &weight) || weight <= 0) { if (FLAGS_default_weight_of_wlb > 0) { LOG(WARNING) << "Invalid weight is set: " << id.tag - << ". Now, 'weight' has been set to 'FLAGS_default_weight_of_wlb' by default."; + << ". Now, 'weight' has been set to " + "FLAGS_default_weight_of_wlb by default."; weight = FLAGS_default_weight_of_wlb; } else { LOG(ERROR) << "Invalid weight is set: " << id.tag; @@ -46,7 +48,7 @@ bool WeightedRandomizedLoadBalancer::Add(Servers& bg, const ServerId& id) { } } bool insert_server = - bg.server_map.emplace(id.id, bg.server_list.size()).second; + bg.server_map.emplace(id.id, bg.server_list.size()).second; if (insert_server) { uint64_t current_weight_sum = bg.weight_sum + weight; bg.server_list.emplace_back(id.id, weight, current_weight_sum); @@ -114,6 +116,10 @@ size_t WeightedRandomizedLoadBalancer::RemoveServersInBatch( return _db_servers.Modify(BatchRemove, servers); } +bool WeightedRandomizedLoadBalancer::IsServerAvailable(SocketId id, SocketUniquePtr* out) { + return Socket::Address(id, out) == 0 && (*out)->IsAvailable(); +} + int WeightedRandomizedLoadBalancer::SelectServer(const SelectIn& in, SelectOut* out) { butil::DoublyBufferedData::ScopedPtr s; if (_db_servers.Read(&s) != 0) { @@ -123,22 +129,49 @@ int WeightedRandomizedLoadBalancer::SelectServer(const SelectIn& in, SelectOut* if (n == 0) { return ENODATA; } + + butil::FlatSet random_traversed; uint64_t weight_sum = s->weight_sum; for (size_t i = 0; i < n; ++i) { uint64_t random_weight = butil::fast_rand_less_than(weight_sum); const Server random_server(0, 0, random_weight); - const auto& server = std::lower_bound(s->server_list.begin(), s->server_list.end(), random_server, server_compare); + const auto& server = + std::lower_bound(s->server_list.begin(), s->server_list.end(), + random_server, server_compare); const SocketId id = server->id; - if (((i + 1) == n // always take last chance - || !ExcludedServers::IsExcluded(in.excluded, id)) - && Socket::Address(id, out->ptr) == 0 - && (*out->ptr)->IsAvailable()) { - // We found an available server + if (ExcludedServers::IsExcluded(in.excluded, id)) { + continue; + } + random_traversed.insert(id); + if (0 == IsServerAvailable(id, out->ptr)) { + // An available server is found. return 0; } } - // After we traversed the whole server list, there is still no - // available server + + if (random_traversed.size() == n) { + // Try to traverse the remaining servers to find an available server. + uint32_t offset = butil::fast_rand_less_than(n); + uint32_t stride = GenRandomStride(); + for (size_t i = 0; i < n; ++i) { + offset = (offset + stride) % n; + SocketId id = s->server_list[offset].id; + if (NULL != random_traversed.seek(id)) { + continue; + } + if (IsServerAvailable(id, out->ptr)) { + // An available server is found. + return 0; + } + } + } + + if (NULL != out->ptr) { + // Use the excluded but available server. + return 0; + } + + // After traversing the whole server list, no available server is found. return EHOSTDOWN; } diff --git a/src/brpc/policy/weighted_randomized_load_balancer.h b/src/brpc/policy/weighted_randomized_load_balancer.h index 2c8b0fd46f..3842affa0a 100644 --- a/src/brpc/policy/weighted_randomized_load_balancer.h +++ b/src/brpc/policy/weighted_randomized_load_balancer.h @@ -31,17 +31,18 @@ namespace policy { // Weight is got from tag of ServerId. class WeightedRandomizedLoadBalancer : public LoadBalancer { public: - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - int SelectServer(const SelectIn& in, SelectOut* out); - LoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - void Describe(std::ostream& os, const DescribeOptions&); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + LoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream& os, const DescribeOptions&) override; struct Server { - Server(SocketId s_id = 0, uint32_t s_w = 0, uint64_t s_c_w_s = 0): id(s_id), weight(s_w), current_weight_sum(s_c_w_s) {} + Server(SocketId s_id = 0, uint32_t s_w = 0, uint64_t s_c_w_s = 0) + : id(s_id), weight(s_w), current_weight_sum(s_c_w_s) {} SocketId id; uint32_t weight; uint64_t current_weight_sum; @@ -60,6 +61,7 @@ class WeightedRandomizedLoadBalancer : public LoadBalancer { static bool Remove(Servers& bg, const ServerId& id); static size_t BatchAdd(Servers& bg, const std::vector& servers); static size_t BatchRemove(Servers& bg, const std::vector& servers); + static bool IsServerAvailable(SocketId id, SocketUniquePtr* out); butil::DoublyBufferedData _db_servers; }; diff --git a/src/brpc/policy/weighted_round_robin_load_balancer.cpp b/src/brpc/policy/weighted_round_robin_load_balancer.cpp index 2a98e7fbe8..44d8a957b3 100644 --- a/src/brpc/policy/weighted_round_robin_load_balancer.cpp +++ b/src/brpc/policy/weighted_round_robin_load_balancer.cpp @@ -58,8 +58,8 @@ uint64_t GetStride(const uint64_t weight_sum, const size_t num) { return 1; } uint32_t average_weight = weight_sum / num; - auto iter = std::lower_bound(prime_stride.begin(), prime_stride.end(), - average_weight); + auto iter = std::lower_bound( + prime_stride.begin(), prime_stride.end(), average_weight); while (iter != prime_stride.end() && !IsCoprime(weight_sum, *iter)) { ++iter; @@ -150,9 +150,6 @@ size_t WeightedRoundRobinLoadBalancer::AddServersInBatch( size_t WeightedRoundRobinLoadBalancer::RemoveServersInBatch( const std::vector& servers) { const size_t n = _db_servers.Modify(BatchRemove, servers); - LOG_IF(ERROR, n != servers.size()) - << "Fail to RemoveServersInBatch, expected " << servers.size() - << " actually " << n; return n; } @@ -200,7 +197,7 @@ int WeightedRoundRobinLoadBalancer::SelectServer(const SelectIn& in, SelectOut* } filter.emplace(server_id); remain_weight -= (s->server_list[s->server_map.at(server_id)]).weight; - // Select from begining status. + // Select from beginning status. tls_temp.stride = GetStride(remain_weight, remain_servers); tls_temp.position = tls.position; tls_temp.remain_server = tls.remain_server; diff --git a/src/brpc/policy/weighted_round_robin_load_balancer.h b/src/brpc/policy/weighted_round_robin_load_balancer.h index fc3df2da2a..828de65965 100644 --- a/src/brpc/policy/weighted_round_robin_load_balancer.h +++ b/src/brpc/policy/weighted_round_robin_load_balancer.h @@ -32,14 +32,14 @@ namespace policy { // Weight is got from tag of ServerId. class WeightedRoundRobinLoadBalancer : public LoadBalancer { public: - bool AddServer(const ServerId& id); - bool RemoveServer(const ServerId& id); - size_t AddServersInBatch(const std::vector& servers); - size_t RemoveServersInBatch(const std::vector& servers); - int SelectServer(const SelectIn& in, SelectOut* out); - LoadBalancer* New(const butil::StringPiece&) const; - void Destroy(); - void Describe(std::ostream&, const DescribeOptions& options); + bool AddServer(const ServerId& id) override; + bool RemoveServer(const ServerId& id) override; + size_t AddServersInBatch(const std::vector& servers) override; + size_t RemoveServersInBatch(const std::vector& servers) override; + int SelectServer(const SelectIn& in, SelectOut* out) override; + LoadBalancer* New(const butil::StringPiece&) const override; + void Destroy() override; + void Describe(std::ostream&, const DescribeOptions& options) override; private: struct Server { diff --git a/src/brpc/proto_base.proto b/src/brpc/proto_base.proto index 30033d49b6..b278ddb6bf 100644 --- a/src/brpc/proto_base.proto +++ b/src/brpc/proto_base.proto @@ -33,6 +33,9 @@ message NsheadMessageBase {} message SerializedRequestBase {} message SerializedResponseBase {} +message SerializerBase {} +message DeserializerBase {} + message ThriftFramedMessageBase {} service BaiduMasterServiceBase {} diff --git a/src/brpc/protocol.cpp b/src/brpc/protocol.cpp index e0468c22ff..9bb1fde315 100644 --- a/src/brpc/protocol.cpp +++ b/src/brpc/protocol.cpp @@ -130,17 +130,12 @@ void ListProtocols(std::vector >* vec) { } } -void SerializeRequestDefault(butil::IOBuf* buf, - Controller* cntl, +void SerializeRequestDefault(butil::IOBuf* buf, Controller* cntl, const google::protobuf::Message* request) { // Check sanity of request. if (!request) { return cntl->SetFailed(EREQUEST, "`request' is NULL"); } - if (request->GetDescriptor() == SerializedRequest::descriptor()) { - buf->append(((SerializedRequest*)request)->serialized_data()); - return; - } if (!request->IsInitialized()) { return cntl->SetFailed( EREQUEST, "Missing required fields in request: %s", diff --git a/src/brpc/redis.cpp b/src/brpc/redis.cpp index 777e199986..9af036f857 100644 --- a/src/brpc/redis.cpp +++ b/src/brpc/redis.cpp @@ -35,7 +35,7 @@ RedisRequest::RedisRequest() } RedisRequest::RedisRequest(const RedisRequest& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } @@ -200,7 +200,7 @@ RedisResponse::RedisResponse() SharedCtor(); } RedisResponse::RedisResponse(const RedisResponse& from) - : NonreflectableMessage() + : NonreflectableMessage(from) , _first_reply(&_arena) { SharedCtor(); MergeFrom(from); @@ -370,4 +370,21 @@ RedisCommandHandler* RedisCommandHandler::NewTransactionHandler() { return NULL; } +// ========== impl of RedisConnContext ========== +RedisConnContext::~RedisConnContext() { } + +void RedisConnContext::Destroy() { + if (session) { + session->Destroy(); + } + delete this; +} + +void RedisConnContext::reset_session(Destroyable* s){ + if (session) { + session->Destroy(); + } + session = s; +} + } // namespace brpc diff --git a/src/brpc/redis.h b/src/brpc/redis.h index c6b0ea21f2..50064519f7 100644 --- a/src/brpc/redis.h +++ b/src/brpc/redis.h @@ -21,8 +21,10 @@ #include +#include "brpc/destroyable.h" #include "brpc/nonreflectable_message.h" #include "brpc/parse_result.h" +#include "brpc/redis_command.h" #include "brpc/pb_compat.h" #include "brpc/redis_reply.h" #include "butil/arena.h" @@ -210,6 +212,39 @@ enum RedisCommandHandlerResult { REDIS_CMD_BATCHED = 2, }; +class RedisCommandParser; + +// This class is as parsing_context in socket. +class RedisConnContext : public Destroyable { +public: + explicit RedisConnContext(const RedisService* rs) + : redis_service(rs) + , batched_size(0) + , session(nullptr) {} + + ~RedisConnContext(); + // @Destroyable + void Destroy() override; + void reset_session(Destroyable* s); + + Destroyable* get_session() { return session; } + + const RedisService* redis_service; + // If user starts a transaction, transaction_handler indicates the + // handler pointer that runs the transaction command. + std::unique_ptr transaction_handler; + // >0 if command handler is run in batched mode. + int batched_size; + + RedisCommandParser parser; + butil::Arena arena; + +private: + // If user is authenticated, session is set. + // Keep auth session info in RedisConnContext to distinguish diffrent users( or diffrent db). + Destroyable* session; +}; + // The Command handler for a redis request. User should impletement Run(). class RedisCommandHandler { public: @@ -235,8 +270,15 @@ class RedisCommandHandler { // it returns REDIS_CMD_HANDLED. Read the comment below. virtual RedisCommandHandlerResult Run(const std::vector& args, brpc::RedisReply* output, - bool flush_batched) = 0; - + bool flush_batched) { + return REDIS_CMD_HANDLED; + }; + virtual RedisCommandHandlerResult Run(RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + return Run(args, output, flush_batched); + } // The Run() returns CONTINUE for "multi", which makes brpc call this method to // create a transaction_handler to process following commands until transaction_handler // returns OK. For example, for command "multi; set k1 v1; set k2 v2; set k3 v3; diff --git a/src/brpc/restful.cpp b/src/brpc/restful.cpp index 32554e820f..36b31e9dd7 100644 --- a/src/brpc/restful.cpp +++ b/src/brpc/restful.cpp @@ -314,29 +314,22 @@ void RestfulMap::ClearMethods() { struct CompareItemInPathList { bool operator()(const RestfulMethodProperty* e1, const RestfulMethodProperty* e2) const { - const int rc1 = e1->path.prefix.compare(e2->path.prefix); + const RestfulMethodPath& path1 = e1->path; + const RestfulMethodPath& path2 = e2->path; + const int rc1 = path1.prefix.compare(path2.prefix); if (rc1 != 0) { return rc1 < 0; } // /A/*/B is put before /A/B so that we try exact patterns first // (the matching is in reversed order) - if (e1->path.has_wildcard != e2->path.has_wildcard) { - return e1->path.has_wildcard > e2->path.has_wildcard; + if (path1.has_wildcard != path2.has_wildcard) { + return path1.has_wildcard > path2.has_wildcard; } // Compare postfix from back to front. - // TODO: Optimize this. - std::string::const_reverse_iterator it1 = e1->path.postfix.rbegin(); - std::string::const_reverse_iterator it2 = e2->path.postfix.rbegin(); - while (it1 != e1->path.postfix.rend() && - it2 != e2->path.postfix.rend()) { - if (*it1 != *it2) { - return (*it1 < *it2); - } - ++it1; - ++it2; - } - return (it1 == e1->path.postfix.rend()) - > (it2 == e2->path.postfix.rend()); + const bool postfix_result = std::lexicographical_compare( + path1.postfix.rbegin(), path1.postfix.rend(), + path2.postfix.rbegin(), path2.postfix.rend()); + return postfix_result; } }; diff --git a/src/brpc/selective_channel.cpp b/src/brpc/selective_channel.cpp index 5a81582108..8877994369 100644 --- a/src/brpc/selective_channel.cpp +++ b/src/brpc/selective_channel.cpp @@ -334,7 +334,8 @@ int Sender::IssueRPC(int64_t start_realtime_us) { sub_cntl->set_request_code(_main_cntl->request_code()); // Forward request attachment to the subcall sub_cntl->request_attachment().append(_main_cntl->request_attachment()); - + sub_cntl->http_request() = _main_cntl->http_request(); + sel_out.channel()->CallMethod(_main_cntl->_method, &r.sub_done->_cntl, _request, diff --git a/src/brpc/serialized_request.cpp b/src/brpc/serialized_request.cpp index f4cabad28e..ac55e31ed9 100644 --- a/src/brpc/serialized_request.cpp +++ b/src/brpc/serialized_request.cpp @@ -28,7 +28,7 @@ SerializedRequest::SerializedRequest() } SerializedRequest::SerializedRequest(const SerializedRequest& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } diff --git a/src/brpc/serialized_request.h b/src/brpc/serialized_request.h index 00d959f36f..4d69aa42a0 100644 --- a/src/brpc/serialized_request.h +++ b/src/brpc/serialized_request.h @@ -54,7 +54,6 @@ class SerializedRequest : public NonreflectableMessage { void SharedCtor(); void SharedDtor(); -private: butil::IOBuf _serialized; }; diff --git a/src/brpc/serialized_response.cpp b/src/brpc/serialized_response.cpp index 6d5d8fef92..c8466451c2 100644 --- a/src/brpc/serialized_response.cpp +++ b/src/brpc/serialized_response.cpp @@ -28,7 +28,7 @@ SerializedResponse::SerializedResponse() } SerializedResponse::SerializedResponse(const SerializedResponse& from) - : NonreflectableMessage() { + : NonreflectableMessage(from) { SharedCtor(); MergeFrom(from); } diff --git a/src/brpc/serialized_response.h b/src/brpc/serialized_response.h index a724be4d40..acd18a2a0d 100644 --- a/src/brpc/serialized_response.h +++ b/src/brpc/serialized_response.h @@ -54,7 +54,6 @@ class SerializedResponse : public NonreflectableMessage { void SharedCtor(); void SharedDtor(); -private: butil::IOBuf _serialized; }; diff --git a/src/brpc/server.cpp b/src/brpc/server.cpp index ade8e274d5..aa55c858aa 100644 --- a/src/brpc/server.cpp +++ b/src/brpc/server.cpp @@ -151,7 +151,7 @@ ServerOptions::ServerOptions() , rtmp_service(NULL) , redis_service(NULL) , bthread_tag(BTHREAD_TAG_DEFAULT) - , rpc_pb_message_factory(new DefaultRpcPBMessageFactory()) + , rpc_pb_message_factory(NULL) , ignore_eovercrowded(false) { if (s_ncore > 0) { num_threads = s_ncore + 1; @@ -424,7 +424,7 @@ Server::Server(ProfilerLinker) , _eps_bvar(&_nerror_bvar) , _concurrency(0) , _concurrency_bvar(cast_no_barrier_int, &_concurrency) - ,_has_progressive_read_method(false) { + , _has_progressive_read_method(false) { BAIDU_CASSERT(offsetof(Server, _concurrency) % 64 == 0, Server_concurrency_must_be_aligned_by_cacheline); } @@ -738,7 +738,7 @@ static int get_port_from_fd(int fd) { bool Server::CreateConcurrencyLimiter(const AdaptiveMaxConcurrency& amc, ConcurrencyLimiter** out) { - if (amc.type() == AdaptiveMaxConcurrency::UNLIMITED()) { + if (amc.type() == AdaptiveMaxConcurrency::UNLIMITED) { *out = NULL; return true; } @@ -782,6 +782,52 @@ static bool OptionsAvailableOverRdma(const ServerOptions* opt) { static AdaptiveMaxConcurrency g_default_max_concurrency_of_method(0); static bool g_default_ignore_eovercrowded(false); +inline void copy_and_fill_server_options(ServerOptions& dst, const ServerOptions& src) { +// follow Server::~Server() +#define FREE_PTR_IF_NOT_REUSED(ptr) \ + if (dst.ptr != src.ptr) { \ + delete dst.ptr; \ + dst.ptr = NULL; \ + } + + if (&dst != &src) { + FREE_PTR_IF_NOT_REUSED(nshead_service); + + #ifdef ENABLE_THRIFT_FRAMED_PROTOCOL + FREE_PTR_IF_NOT_REUSED(thrift_service); + #endif + + FREE_PTR_IF_NOT_REUSED(baidu_master_service); + FREE_PTR_IF_NOT_REUSED(http_master_service); + FREE_PTR_IF_NOT_REUSED(rpc_pb_message_factory); + + if (dst.pid_file != src.pid_file && !dst.pid_file.empty()) { + unlink(dst.pid_file.c_str()); + } + + if (dst.server_owns_auth) { + FREE_PTR_IF_NOT_REUSED(auth); + } + + if (dst.server_owns_interceptor) { + FREE_PTR_IF_NOT_REUSED(interceptor); + } + + FREE_PTR_IF_NOT_REUSED(redis_service); + + // copy data members directly + dst = src; + } +#undef FREE_PTR_IF_NOT_REUSED + + // Create the resource if: + // 1. `dst` copied from user and user forgot to create + // 2. `dst` created by our + if (!dst.rpc_pb_message_factory) { + dst.rpc_pb_message_factory = new DefaultRpcPBMessageFactory(); + } +} + int Server::StartInternal(const butil::EndPoint& endpoint, const PortRange& port_range, const ServerOptions *opt) { @@ -813,13 +859,8 @@ int Server::StartInternal(const butil::EndPoint& endpoint, } return -1; } - if (opt) { - _options = *opt; - } else { - // Always reset to default options explicitly since `_options' - // may be the options for the last run or even bad options - _options = ServerOptions(); - } + + copy_and_fill_server_options(_options, opt ? *opt : ServerOptions()); if (!_options.h2_settings.IsValid(true/*log_error*/)) { LOG(ERROR) << "Invalid h2_settings"; @@ -1045,7 +1086,7 @@ int Server::StartInternal(const butil::EndPoint& endpoint, it->second.status->SetConcurrencyLimiter(NULL); } else { const AdaptiveMaxConcurrency* amc = &it->second.max_concurrency; - if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED()) { + if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED) { amc = &_options.method_max_concurrency; } ConcurrencyLimiter* cl = NULL; @@ -1054,6 +1095,7 @@ int Server::StartInternal(const butil::EndPoint& endpoint, return -1; } it->second.status->SetConcurrencyLimiter(cl); + it->second.max_concurrency.SetConcurrencyLimiter(cl); } } if (0 != SetServiceMaxConcurrency(_options.nshead_service)) { @@ -1930,7 +1972,7 @@ bool IsDummyServerRunning() { } const Server::MethodProperty* -Server::FindMethodPropertyByFullName(const butil::StringPiece&fullname) const { +Server::FindMethodPropertyByFullName(const butil::StringPiece& fullname) const { return _method_map.seek(fullname); } @@ -2180,10 +2222,6 @@ int Server::ResetMaxConcurrency(int max_concurrency) { } AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(MethodProperty* mp) { - if (IsRunning()) { - LOG(WARNING) << "MaxConcurrencyOf is only allowed before Server started"; - return g_default_max_concurrency_of_method; - } if (mp->status == NULL) { LOG(ERROR) << "method=" << mp->method->full_name() << " does not support max_concurrency"; @@ -2194,10 +2232,6 @@ AdaptiveMaxConcurrency& Server::MaxConcurrencyOf(MethodProperty* mp) { } int Server::MaxConcurrencyOf(const MethodProperty* mp) const { - if (IsRunning()) { - LOG(WARNING) << "MaxConcurrencyOf is only allowed before Server started"; - return g_default_max_concurrency_of_method; - } if (mp == NULL || mp->status == NULL) { return 0; } diff --git a/src/brpc/server.h b/src/brpc/server.h index 8d1b093cc7..2cf34dbd82 100644 --- a/src/brpc/server.h +++ b/src/brpc/server.h @@ -716,7 +716,7 @@ friend class Controller; int SetServiceMaxConcurrency(T* service) { if (NULL != service) { const AdaptiveMaxConcurrency* amc = &service->_max_concurrency; - if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED()) { + if (amc->type() == AdaptiveMaxConcurrency::UNLIMITED) { amc = &_options.method_max_concurrency; } ConcurrencyLimiter* cl = NULL; diff --git a/src/brpc/socket.cpp b/src/brpc/socket.cpp index ac4c6892c0..e7cc336a5d 100644 --- a/src/brpc/socket.cpp +++ b/src/brpc/socket.cpp @@ -508,9 +508,11 @@ void Socket::ReturnSuccessfulWriteRequest(Socket::WriteRequest* p) { DCHECK(p->data.empty()); AddOutputMessages(1); const bthread_id_t id_wait = p->id_wait; + bool is_notify_on_success = p->is_notify_on_success(); butil::return_object(p); + // Do not access `p' after it is returned to ObjectPool. if (id_wait != INVALID_BTHREAD_ID) { - if (p->is_notify_on_success() && !Failed()) { + if (is_notify_on_success && !Failed()) { bthread_id_error(id_wait, 0); } else { NotifyOnFailed(id_wait); @@ -810,7 +812,7 @@ int Socket::OnCreated(const SocketOptions& options) { } // Must be the last one! Internal fields of this Socket may be accessed // just after calling ResetFileDescriptor. - if (ResetFileDescriptor(options.fd) != 0) { + if (ResetFileDescriptor(fd) != 0) { const int saved_errno = errno; PLOG(ERROR) << "Fail to ResetFileDescriptor"; SetFailed(saved_errno, "Fail to ResetFileDescriptor: %s", @@ -960,9 +962,9 @@ std::string Socket::OnDescription() const { result.reserve(64); const int saved_fd = fd(); if (saved_fd >= 0) { - butil::string_appendf(&result, "fd=%d", saved_fd); + butil::string_appendf(&result, "fd=%d ", saved_fd); } - butil::string_appendf(&result, " addr=%s", + butil::string_appendf(&result, "addr=%s", butil::endpoint2str(remote_side()).c_str()); const int local_port = local_side().port; if (local_port > 0) { diff --git a/src/brpc/socket.h b/src/brpc/socket.h index a84c0abdcf..875f3f8565 100644 --- a/src/brpc/socket.h +++ b/src/brpc/socket.h @@ -284,7 +284,7 @@ struct SocketOptions { // Only linux supports TCP_USER_TIMEOUT. int tcp_user_timeout_ms{ -1}; // Tag of this socket - bthread_tag_t bthread_tag{BTHREAD_TAG_DEFAULT}; + bthread_tag_t bthread_tag{bthread_self_tag()}; }; // Abstractions on reading from and writing into file descriptors. diff --git a/src/brpc/stream.cpp b/src/brpc/stream.cpp index 73d6405190..2a4430548f 100644 --- a/src/brpc/stream.cpp +++ b/src/brpc/stream.cpp @@ -36,6 +36,9 @@ namespace brpc { DECLARE_bool(usercode_in_pthread); DECLARE_int64(socket_max_streams_unconsumed_bytes); +DEFINE_uint64(stream_write_max_segment_size, 512 * 1024 * 1024, + "Stream message exceeding this size will be automatically split into smaller segments"); +BRPC_VALIDATE_GFLAG(stream_write_max_segment_size, PositiveInteger); const static butil::IOBuf *TIMEOUT_TASK = (butil::IOBuf*)-1L; @@ -60,6 +63,11 @@ Stream::Stream() } Stream::~Stream() { + // Clear pending buffer + if (_pending_buf != NULL) { + delete _pending_buf; + _pending_buf = NULL; + } CHECK(_host_socket == NULL); bthread_mutex_destroy(&_connect_mutex); bthread_mutex_destroy(&_congestion_control_mutex); @@ -154,18 +162,54 @@ ssize_t Stream::CutMessageIntoFileDescriptor(int /*fd*/, } butil::IOBuf out; ssize_t len = 0; + ssize_t unwritten_data_size = 0; for (size_t i = 0; i < size; ++i) { - StreamFrameMeta fm; - fm.set_stream_id(_remote_settings.stream_id()); - fm.set_source_stream_id(id()); - fm.set_frame_type(FRAME_TYPE_DATA); - // TODO: split large data - fm.set_has_continuation(false); - policy::PackStreamMessage(&out, fm, data_list[i]); - len += data_list[i]->length(); - data_list[i]->clear(); + butil::IOBuf *data = data_list[i]; + size_t length = data->length(); + if (length > FLAGS_stream_write_max_segment_size) { + if (unwritten_data_size) { + WriteToHostSocket(&out); + unwritten_data_size = 0; + out.clear(); + } + // segmenting large data into multiple parts + butil::IOBuf segment_buf; + bool has_continuation = true; + while (has_continuation) { + data->cutn(&segment_buf, FLAGS_stream_write_max_segment_size); + StreamFrameMeta fm; + fm.set_stream_id(_remote_settings.stream_id()); + fm.set_source_stream_id(id()); + fm.set_frame_type(FRAME_TYPE_DATA); + has_continuation = !data->empty(); + fm.set_has_continuation(has_continuation); + policy::PackStreamMessage(&out, fm, &segment_buf); + len += segment_buf.length(); + segment_buf.clear(); + WriteToHostSocket(&out); + out.clear(); + } + } else { + if (unwritten_data_size + length > FLAGS_stream_write_max_segment_size) { + WriteToHostSocket(&out); + unwritten_data_size = 0; + out.clear(); + } + unwritten_data_size += length; + StreamFrameMeta fm; + fm.set_stream_id(_remote_settings.stream_id()); + fm.set_source_stream_id(id()); + fm.set_frame_type(FRAME_TYPE_DATA); + fm.set_has_continuation(false); + policy::PackStreamMessage(&out, fm, data_list[i]); + len += length; + data_list[i]->clear(); + } + } + + if (!out.empty()) { + WriteToHostSocket(&out); } - WriteToHostSocket(&out); return len; } @@ -596,17 +640,16 @@ void Stream::SendFeedback() { } int Stream::SetHostSocket(Socket *host_socket) { - if (_host_socket != NULL) { - CHECK(false) << "SetHostSocket has already been called"; - return -1; - } - SocketUniquePtr ptr; - host_socket->ReAddress(&ptr); - // TODO add *this to host socke - if (ptr->AddStream(id()) != 0) { - return -1; - } - _host_socket = ptr.release(); + std::call_once(_set_host_socket_flag, [this, host_socket]() { + SocketUniquePtr ptr; + host_socket->ReAddress(&ptr); + // TODO add *this to host socke + if (ptr->AddStream(id()) != 0) { + CHECK(false) << id() << " fail to add stream to host socket"; + return; + } + _host_socket = ptr.release(); + }); return 0; } @@ -666,10 +709,6 @@ void Stream::Close(int error_code, const char* reason_fmt, ...) { return TriggerOnConnectIfNeed(); } -int Stream::ShareHostSocket(Stream& other_stream) { - return other_stream.SetHostSocket(_host_socket); -} - int Stream::SetFailed(StreamId id, int error_code, const char* reason_fmt, ...) { SocketUniquePtr ptr; if (Socket::AddressFailedAsWell(id, &ptr) == -1) { diff --git a/src/brpc/stream_impl.h b/src/brpc/stream_impl.h index 66e0d7191b..5ff7cb04a2 100644 --- a/src/brpc/stream_impl.h +++ b/src/brpc/stream_impl.h @@ -19,6 +19,7 @@ #ifndef BRPC_STREAM_IMPL_H #define BRPC_STREAM_IMPL_H +#include #include "bthread/bthread.h" #include "bthread/execution_queue.h" #include "brpc/socket.h" @@ -67,7 +68,6 @@ class BAIDU_CACHELINE_ALIGNMENT Stream : public SocketConnection { __attribute__ ((__format__ (__printf__, 3, 4))); void Close(int error_code, const char* reason_fmt, ...) __attribute__ ((__format__ (__printf__, 3, 4))); - int ShareHostSocket(Stream& other_stream); private: friend void StreamWait(StreamId stream_id, const timespec *due_time, @@ -134,6 +134,7 @@ friend struct butil::DefaultDeleter; butil::IOBuf *_pending_buf; int64_t _start_idle_timer_us; bthread_timer_t _idle_timer; + std::once_flag _set_host_socket_flag; }; } // namespace brpc diff --git a/src/brpc/versioned_ref_with_id.h b/src/brpc/versioned_ref_with_id.h index e7c3ef8d71..20e0106d93 100644 --- a/src/brpc/versioned_ref_with_id.h +++ b/src/brpc/versioned_ref_with_id.h @@ -102,17 +102,17 @@ typename std::enable_if::value, Ret>::type ReturnEmpty() {} template \ typename std::enable_if(0))::value, return_type>::type \ - Call(class_type* obj, Args&&... args) { \ + Call(class_type* obj, Args&&... args) { \ BAIDU_CASSERT((butil::is_result_same< \ return_type, decltype(&T::func_name), T, Args...>::value), \ - "Params or return type mismatch"); \ + "Params or return type mismatch"); \ return obj->func_name(std::forward(args)...); \ } \ \ template \ typename std::enable_if(0))::value, return_type>::type \ - Call(class_type* obj, Args&&... args) { \ + Call(class_type* obj, Args&&...) { \ return ReturnEmpty(); \ } \ } @@ -356,7 +356,7 @@ int VersionedRefWithId::Create(VRefId* id, Args&&... args) { T* const t = butil::get_resource(&slot, Forbidden()); if (t == NULL) { LOG(FATAL) << "Fail to get_resource<" - << butil::class_name_str() << ">"; + << butil::class_name() << ">"; return -1; } // nref can be non-zero due to concurrent Address(). @@ -617,10 +617,10 @@ template std::string VersionedRefWithId::description() const { std::string result; result.reserve(128); - butil::string_appendf(&result, "Socket{id=%" PRIu64, id()); + butil::string_appendf(&result, "%s{id=%" PRIu64 " ", butil::class_name(), id()); result.append(WRAPPER_CALL( OnDescription, const_cast(static_cast(this)))); - butil::string_appendf(&result, "} (0x%p)", this); + butil::string_appendf(&result, "} (%p)", this); return result; } diff --git a/src/bthread/bthread.cpp b/src/bthread/bthread.cpp index e124f4bc6f..35d8747751 100644 --- a/src/bthread/bthread.cpp +++ b/src/bthread/bthread.cpp @@ -70,6 +70,7 @@ pthread_mutex_t g_task_control_mutex = PTHREAD_MUTEX_INITIALIZER; TaskControl* g_task_control = NULL; extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group; +EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group); extern void (*g_worker_startfn)(); extern void (*g_tagged_worker_startfn)(bthread_tag_t); extern void* (*g_create_span_func)(); @@ -350,7 +351,7 @@ bthread_t bthread_self(void) { bthread::TaskGroup* g = bthread::tls_task_group; // note: return 0 for main tasks now, which include main thread and // all work threads. So that we can identify main tasks from logs - // more easily. This is probably questionable in future. + // more easily. This is probably questionable in the future. if (g != NULL && !g->is_current_main_task()/*note*/) { return g->current_tid(); } @@ -361,6 +362,13 @@ int bthread_equal(bthread_t t1, bthread_t t2) { return t1 == t2; } +#ifdef BUTIL_USE_ASAN +// Fixme!!! +// The noreturn `bthread_exit' may cause a warning of ASan, but does not abort the program. +// +// ==94463==WARNING: ASan is ignoring requested __asan_handle_no_return: stack type: default top: 0x00016dd7f000; bottom 0x00010b1a4000; size: 0x000062bdb000 (1656598528) +// False positive error reports may follow +#endif // BUTIL_USE_ASAN void bthread_exit(void* retval) { bthread::TaskGroup* g = bthread::tls_task_group; if (g != NULL && !g->is_current_main_task()) { @@ -521,7 +529,7 @@ int bthread_timer_del(bthread_timer_t id) { } int bthread_usleep(uint64_t microseconds) { - bthread::TaskGroup* g = bthread::tls_task_group; + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); if (NULL != g && !g->is_current_pthread_task()) { return bthread::TaskGroup::usleep(&g, microseconds); } @@ -529,7 +537,7 @@ int bthread_usleep(uint64_t microseconds) { } int bthread_yield(void) { - bthread::TaskGroup* g = bthread::tls_task_group; + bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); if (NULL != g && !g->is_current_pthread_task()) { bthread::TaskGroup::yield(&g); return 0; diff --git a/src/bthread/execution_queue.cpp b/src/bthread/execution_queue.cpp index bb01882cb2..88d96c2b36 100644 --- a/src/bthread/execution_queue.cpp +++ b/src/bthread/execution_queue.cpp @@ -22,7 +22,6 @@ #include "bthread/execution_queue.h" #include "butil/memory/singleton_on_pthread_once.h" -#include "butil/object_pool.h" // butil::get_object #include "butil/resource_pool.h" // butil::get_resource #include "butil/threading/platform_thread.h" diff --git a/src/bthread/execution_queue_inl.h b/src/bthread/execution_queue_inl.h index f5998a2628..cf7d2ee5be 100644 --- a/src/bthread/execution_queue_inl.h +++ b/src/bthread/execution_queue_inl.h @@ -27,8 +27,9 @@ #include "butil/memory/scoped_ptr.h" // butil::scoped_ptr #include "butil/logging.h" // LOG #include "butil/time.h" // butil::cpuwide_time_ns -#include "bvar/bvar.h" // bvar::Adder -#include "bthread/butex.h" // butex_construct +#include "butil/object_pool.h" // butil::get_object +#include "bvar/bvar.h" // bvar::Adder +#include "bthread/butex.h" // butex_construct #include "butil/synchronization/condition_variable.h" namespace bthread { @@ -585,4 +586,11 @@ inline int ExecutionQueueBase::dereference() { } // namespace bthread +namespace butil { +// `TaskNode::cancel' may access the TaskNode object returned to the ObjectPool, +// so ObjectPool can not poison the memory region of TaskNode. +template <> +struct ObjectPoolWithASanPoison : false_type {}; +} // namespace butil + #endif //BTHREAD_EXECUTION_QUEUE_INL_H diff --git a/src/bthread/key.cpp b/src/bthread/key.cpp index 1bf5ec899a..00215d7f3b 100644 --- a/src/bthread/key.cpp +++ b/src/bthread/key.cpp @@ -222,8 +222,7 @@ class BAIDU_CACHELINE_ALIGNMENT KeyTable { class BAIDU_CACHELINE_ALIGNMENT KeyTableList { public: KeyTableList() : - _head(NULL), _tail(NULL), _length(0) { - } + _head(NULL), _tail(NULL), _length(0) {} ~KeyTableList() { TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); @@ -305,7 +304,7 @@ class BAIDU_CACHELINE_ALIGNMENT KeyTableList { return count; } - inline uint32_t get_length() { + inline uint32_t get_length() const { return _length; } diff --git a/src/bthread/parking_lot.h b/src/bthread/parking_lot.h index d42a560e4d..9bb48ad722 100644 --- a/src/bthread/parking_lot.h +++ b/src/bthread/parking_lot.h @@ -40,12 +40,15 @@ class BAIDU_CACHELINE_ALIGNMENT ParkingLot { int val; }; - ParkingLot() : _pending_signal(0) {} + ParkingLot() : _pending_signal(0), _waiter_num(0) {} // Wake up at most `num_task' workers. // Returns #workers woken up. int signal(int num_task) { _pending_signal.fetch_add((num_task << 1), butil::memory_order_release); + if (_waiter_num.load(butil::memory_order_relaxed) == 0) { + return 0; + } return futex_wake_private(&_pending_signal, num_task); } @@ -57,7 +60,9 @@ class BAIDU_CACHELINE_ALIGNMENT ParkingLot { // Wait for tasks. // If the `expected_state' does not match, wait() may finish directly. void wait(const State& expected_state) { + _waiter_num.fetch_add(1, butil::memory_order_relaxed); futex_wait_private(&_pending_signal, expected_state.val, NULL); + _waiter_num.fetch_sub(1, butil::memory_order_relaxed); } // Wakeup suspended wait() and make them unwaitable ever. @@ -68,6 +73,7 @@ class BAIDU_CACHELINE_ALIGNMENT ParkingLot { private: // higher 31 bits for signalling, LSB for stopping. butil::atomic _pending_signal; + butil::atomic _waiter_num; }; } // namespace bthread diff --git a/src/bthread/stack.cpp b/src/bthread/stack.cpp index c312ef83d0..71daf9d7c4 100644 --- a/src/bthread/stack.cpp +++ b/src/bthread/stack.cpp @@ -139,7 +139,7 @@ void deallocate_stack_storage(StackStorage* s) { return; } s_stack_count.fetch_sub(1, butil::memory_order_relaxed); - if (s->guardsize <= 0) { + if (s->guardsize == 0) { free((char*)s->bottom - memsize); } else { munmap((char*)s->bottom - memsize, memsize); diff --git a/src/bthread/stack.h b/src/bthread/stack.h index 46cafb4acd..91d1df6066 100644 --- a/src/bthread/stack.h +++ b/src/bthread/stack.h @@ -31,8 +31,8 @@ namespace bthread { struct StackStorage { - int stacksize; - int guardsize; + unsigned stacksize; + unsigned guardsize; // Assume stack grows upwards. // http://www.boost.org/doc/libs/1_55_0/libs/context/doc/html/context/stack.html void* bottom; @@ -62,6 +62,7 @@ enum StackType { }; struct ContextualStack { + virtual ~ContextualStack() = default; bthread_fcontext_t context; StackType stacktype; StackStorage storage; diff --git a/src/bthread/stack_inl.h b/src/bthread/stack_inl.h index 868775d4dc..faa5de07c4 100644 --- a/src/bthread/stack_inl.h +++ b/src/bthread/stack_inl.h @@ -28,6 +28,81 @@ DECLARE_int32(tc_stack_normal); namespace bthread { +#ifdef BUTIL_USE_ASAN +namespace internal { + +BUTIL_FORCE_INLINE void ASanPoisonMemoryRegion(const StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + + CHECK_GT((void*)storage.bottom, + reinterpret_cast(storage.stacksize + + storage.guardsize)); + BUTIL_ASAN_POISON_MEMORY_REGION( + (char*)storage.bottom - storage.stacksize, storage.stacksize); +} + +BUTIL_FORCE_INLINE void ASanUnpoisonMemoryRegion(const StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + CHECK_GT(storage.bottom, + reinterpret_cast(storage.stacksize + storage.guardsize)); + BUTIL_ASAN_UNPOISON_MEMORY_REGION( + (char*)storage.bottom - storage.stacksize, storage.stacksize); +} + + +BUTIL_FORCE_INLINE void StartSwitchFiber(void** fake_stack_save, StackStorage& storage) { + if (NULL == storage.bottom) { + return; + } + RELEASE_ASSERT(storage.bottom > + reinterpret_cast(storage.stacksize + storage.guardsize)); + // Lowest address of this stack. + void* asan_stack_bottom = (char*)storage.bottom - storage.stacksize; + BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, asan_stack_bottom, storage.stacksize); +} + +BUTIL_FORCE_INLINE void FinishSwitchFiber(void* fake_stack_save) { + BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, NULL, NULL); +} + +class ScopedASanFiberSwitcher { +public: + ScopedASanFiberSwitcher(StackStorage& next_storage) { + StartSwitchFiber(&_fake_stack, next_storage); + } + + ~ScopedASanFiberSwitcher() { + FinishSwitchFiber(_fake_stack); + } + + DISALLOW_COPY_AND_ASSIGN(ScopedASanFiberSwitcher); + +private: + void* _fake_stack{NULL}; +}; + +#define BTHREAD_ASAN_POISON_MEMORY_REGION(storage) \ + ::bthread::internal::ASanPoisonMemoryRegion(storage) + +#define BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage) \ + ::bthread::internal::ASanUnpoisonMemoryRegion(storage) + +#define BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(storage) \ + ::bthread::internal::ScopedASanFiberSwitcher switcher(storage) + +} // namespace internal +#else + +// If ASan are used, the annotations should be no-ops. +#define BTHREAD_ASAN_POISON_MEMORY_REGION(storage) ((void)(storage)) +#define BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage) ((void)(storage)) +#define BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(storage) ((void)(storage)) + +#endif // BUTIL_USE_ASAN + struct MainStackClass {}; struct SmallStackClass { @@ -57,10 +132,14 @@ template struct StackFactory { } context = bthread_make_fcontext(storage.bottom, storage.stacksize, entry); stacktype = (StackType)StackClass::stacktype; + // It's poisoned prior to use. + BTHREAD_ASAN_POISON_MEMORY_REGION(storage); } ~Wrapper() { if (context) { context = NULL; + // Unpoison to avoid affecting other allocator. + BTHREAD_ASAN_UNPOISON_MEMORY_REGION(storage); deallocate_stack_storage(&storage); storage.zeroize(); } @@ -68,11 +147,16 @@ template struct StackFactory { }; static ContextualStack* get_stack(void (*entry)(intptr_t)) { - return butil::get_object(entry); + ContextualStack* cs = butil::get_object(entry); + // Marks stack as addressable. + BTHREAD_ASAN_UNPOISON_MEMORY_REGION(cs->storage); + return cs; } - static void return_stack(ContextualStack* sc) { - butil::return_object(static_cast(sc)); + static void return_stack(ContextualStack* cs) { + // Marks stack as unaddressable. + BTHREAD_ASAN_POISON_MEMORY_REGION(cs->storage); + butil::return_object(static_cast(cs)); } }; diff --git a/src/bthread/task_control.cpp b/src/bthread/task_control.cpp index 55ed1f2e42..545bc32236 100644 --- a/src/bthread/task_control.cpp +++ b/src/bthread/task_control.cpp @@ -228,7 +228,7 @@ int TaskControl::init(int concurrency) { const int rc = pthread_create(&_workers[i], NULL, worker_thread, arg); if (rc) { delete arg; - LOG(ERROR) << "Fail to create _workers[" << i << "], " << berror(rc); + PLOG(ERROR) << "Fail to create _workers[" << i << "]"; return -1; } } @@ -272,8 +272,7 @@ int TaskControl::add_workers(int num, bthread_tag_t tag) { &_workers[i + old_concurency], NULL, worker_thread, arg); if (rc) { delete arg; - LOG(WARNING) << "Fail to create _workers[" << i + old_concurency - << "], " << berror(rc); + PLOG(WARNING) << "Fail to create _workers[" << i + old_concurency << "]"; _concurrency.fetch_sub(1, butil::memory_order_release); break; } diff --git a/src/bthread/task_group.cpp b/src/bthread/task_group.cpp index 361efa5936..6abc444baf 100644 --- a/src/bthread/task_group.cpp +++ b/src/bthread/task_group.cpp @@ -58,7 +58,7 @@ BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group, NULL); // Sync with TaskMeta::local_storage when a bthread is created or destroyed. // During running, the two fields may be inconsistent, use tls_bls as the // groundtruth. -__thread LocalStorage tls_bls = BTHREAD_LOCAL_STORAGE_INITIALIZER; +BAIDU_VOLATILE_THREAD_LOCAL(LocalStorage, tls_bls, BTHREAD_LOCAL_STORAGE_INITIALIZER); // defined in bthread/key.cpp extern void return_keytable(bthread_keytable_pool_t*, KeyTable*); @@ -79,7 +79,7 @@ void* run_create_span_func() { if (g_create_span_func) { return g_create_span_func(); } - return tls_bls.rpcz_parent_span; + return BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls).rpcz_parent_span; } int TaskGroup::get_attr(bthread_t tid, bthread_attr_t* out) { @@ -208,12 +208,39 @@ TaskGroup::~TaskGroup() { if (_main_tid) { TaskMeta* m = address_meta(_main_tid); CHECK(_main_stack == m->stack); +#ifdef BUTIL_USE_ASAN + _main_stack->storage.bottom = NULL; + _main_stack->storage.stacksize = 0; +#endif // BUTIL_USE_ASAN return_stack(m->release_stack()); return_resource(get_slot(_main_tid)); _main_tid = 0; } } +#ifdef BUTIL_USE_ASAN +int PthreadAttrGetStack(void*& stack_addr, size_t& stack_size) { +#if defined(OS_MACOSX) + stack_addr = pthread_get_stackaddr_np(pthread_self()); + stack_size = pthread_get_stacksize_np(pthread_self()); + return 0; +#else + pthread_attr_t attr; + int rc = pthread_getattr_np(pthread_self(), &attr); + if (0 != rc) { + LOG(ERROR) << "Fail to get pthread attributes: " << berror(rc); + return rc; + } + rc = pthread_attr_getstack(&attr, &stack_addr, &stack_size); + if (0 != rc) { + LOG(ERROR) << "Fail to get pthread stack: " << berror(rc); + } + pthread_attr_destroy(&attr); + return rc; +#endif // OS_MACOSX +} +#endif // BUTIL_USE_ASAN + int TaskGroup::init(size_t runqueue_capacity) { if (_rq.init(runqueue_capacity) != 0) { LOG(FATAL) << "Fail to init _rq"; @@ -223,6 +250,15 @@ int TaskGroup::init(size_t runqueue_capacity) { LOG(FATAL) << "Fail to init _remote_rq"; return -1; } + +#ifdef BUTIL_USE_ASAN + void* stack_addr = NULL; + size_t stack_size = 0; + if (0 != PthreadAttrGetStack(stack_addr, stack_size)) { + return -1; + } +#endif // BUTIL_USE_ASAN + ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL); if (NULL == stk) { LOG(FATAL) << "Fail to get main stack container"; @@ -247,6 +283,12 @@ int TaskGroup::init(size_t runqueue_capacity) { m->tid = make_tid(*m->version_butex, slot); m->set_stack(stk); +#ifdef BUTIL_USE_ASAN + stk->storage.bottom = stack_addr; + stk->storage.stacksize = stack_size; + // No guard size required for ASan. +#endif // BUTIL_USE_ASAN + _cur_meta = m; _main_tid = m->tid; _main_stack = stk; @@ -255,6 +297,15 @@ int TaskGroup::init(size_t runqueue_capacity) { return 0; } +#ifdef BUTIL_USE_ASAN +void TaskGroup::asan_task_runner(intptr_t) { + // This is a new thread, and it doesn't have the fake stack yet. ASan will + // create it lazily, for now just pass NULL. + internal::FinishSwitchFiber(NULL); + task_runner(0); +} +#endif // BUTIL_USE_ASAN + void TaskGroup::task_runner(intptr_t skip_remained) { // NOTE: tls_task_group is volatile since tasks are moved around // different groups. @@ -321,11 +372,13 @@ void TaskGroup::task_runner(intptr_t skip_remained) { // Clean tls variables, must be done before changing version_butex // otherwise another thread just joined this thread may not see side // effects of destructing tls variables. - KeyTable* kt = tls_bls.keytable; + LocalStorage* tls_bls_ptr = BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(tls_bls); + KeyTable* kt = tls_bls_ptr->keytable; if (kt != NULL) { return_keytable(m->attr.keytable_pool, kt); // After deletion: tls may be set during deletion. - tls_bls.keytable = NULL; + tls_bls_ptr = BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(tls_bls); + tls_bls_ptr->keytable = NULL; m->local_storage.keytable = NULL; // optional } @@ -564,11 +617,18 @@ void TaskGroup::ending_sched(TaskGroup** pg) { TaskMeta* next_meta = address_meta(next_tid); if (next_meta->stack == NULL) { if (next_meta->stack_type() == cur_meta->stack_type()) { + // Reuse the stack of the current ending task. + // // also works with pthread_task scheduling to pthread_task, the // transfered stack is just _main_stack. next_meta->set_stack(cur_meta->release_stack()); } else { +#ifdef BUTIL_USE_ASAN + ContextualStack* stk = get_stack( + next_meta->stack_type(), asan_task_runner); +#else ContextualStack* stk = get_stack(next_meta->stack_type(), task_runner); +#endif // BUTIL_USE_ASAN if (stk) { next_meta->set_stack(stk); } else { @@ -581,7 +641,7 @@ void TaskGroup::ending_sched(TaskGroup** pg) { } } } - sched_to(pg, next_meta); + sched_to(pg, next_meta, true); } void TaskGroup::sched(TaskGroup** pg) { @@ -602,7 +662,7 @@ void TaskGroup::sched(TaskGroup** pg) { extern void CheckBthreadScheSafety(); -void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta) { +void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta, bool cur_ending) { TaskGroup* g = *pg; #ifndef NDEBUG if ((++g->_sched_recursive_guard) > 1) { @@ -639,8 +699,8 @@ void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta) { if (__builtin_expect(next_meta != cur_meta, 1)) { g->_cur_meta = next_meta; // Switch tls_bls - cur_meta->local_storage = tls_bls; - tls_bls = next_meta->local_storage; + cur_meta->local_storage = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); + BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_bls, next_meta->local_storage); // Logging must be done after switching the local storage, since the logging lib // use bthread local storage internally, or will cause memory leak. @@ -657,7 +717,10 @@ void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta) { g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, cur_meta); g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, next_meta); #endif // BRPC_BTHREAD_TRACER - jump_stack(cur_meta->stack, next_meta->stack); + { + BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(next_meta->stack->storage); + jump_stack(cur_meta->stack, next_meta->stack); + } // probably went to another group, need to assign g again. g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); #ifdef BRPC_BTHREAD_TRACER diff --git a/src/bthread/task_group.h b/src/bthread/task_group.h index c3d2ae463e..488ad4922c 100644 --- a/src/bthread/task_group.h +++ b/src/bthread/task_group.h @@ -81,7 +81,7 @@ class TaskGroup { // Suspend caller and run bthread `next_tid' in TaskGroup *pg. // Purpose of this function is to avoid pushing `next_tid' to _rq and // then being popped by sched(pg), which is not necessary. - static void sched_to(TaskGroup** pg, TaskMeta* next_meta); + static void sched_to(TaskGroup** pg, TaskMeta* next_meta, bool cur_ending); static void sched_to(TaskGroup** pg, bthread_t next_tid); static void exchange(TaskGroup** pg, TaskMeta* next_meta); @@ -204,7 +204,7 @@ class TaskGroup { friend class TaskControl; // You shall use TaskControl::create_group to create new instance. - explicit TaskGroup(TaskControl*); + explicit TaskGroup(TaskControl* c); int init(size_t runqueue_capacity); @@ -212,6 +212,9 @@ friend class TaskControl; // of groups are postponed to avoid race. ~TaskGroup(); +#ifdef BUTIL_USE_ASAN + static void asan_task_runner(intptr_t); +#endif // BUTIL_USE_ASAN static void task_runner(intptr_t skip_remained); // Callbacks for set_remained() diff --git a/src/bthread/task_group_inl.h b/src/bthread/task_group_inl.h index 75c377e12f..4842bf696c 100644 --- a/src/bthread/task_group_inl.h +++ b/src/bthread/task_group_inl.h @@ -56,13 +56,17 @@ inline void TaskGroup::exchange(TaskGroup** pg, TaskMeta* next_meta) { ? ready_to_run_in_worker_ignoresignal : ready_to_run_in_worker), &args); - TaskGroup::sched_to(pg, next_meta); + TaskGroup::sched_to(pg, next_meta, false); } inline void TaskGroup::sched_to(TaskGroup** pg, bthread_t next_tid) { TaskMeta* next_meta = address_meta(next_tid); if (next_meta->stack == NULL) { +#ifdef BUTIL_USE_ASAN + ContextualStack* stk = get_stack(next_meta->stack_type(), asan_task_runner); +#else ContextualStack* stk = get_stack(next_meta->stack_type(), task_runner); +#endif // BUTIL_USE_ASAN if (stk) { next_meta->set_stack(stk); } else { @@ -75,7 +79,7 @@ inline void TaskGroup::sched_to(TaskGroup** pg, bthread_t next_tid) { } } // Update now_ns only when wait_task did yield. - sched_to(pg, next_meta); + sched_to(pg, next_meta, false); } inline void TaskGroup::push_rq(bthread_t tid) { diff --git a/src/bthread/task_tracer.cpp b/src/bthread/task_tracer.cpp index acdf920891..af88ba6447 100644 --- a/src/bthread/task_tracer.cpp +++ b/src/bthread/task_tracer.cpp @@ -305,6 +305,8 @@ TaskTracer::Result TaskTracer::TraceImpl(bthread_t tid) { return result; } +// Instruct ASan to ignore this function. +BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS unw_cursor_t TaskTracer::MakeCursor(bthread_fcontext_t fcontext) { unw_cursor_t cursor; unw_init_local(&cursor, &_context); diff --git a/src/bthread/task_tracer.h b/src/bthread/task_tracer.h index 8c84f7b9a7..f154c3ee0c 100644 --- a/src/bthread/task_tracer.h +++ b/src/bthread/task_tracer.h @@ -80,8 +80,8 @@ class TaskTracer { bool OK() const { return err_count == 0; } - static const size_t MAX_TRACE_NUM = 64; - static const size_t MAX_ERROR_NUM = 2; + static constexpr size_t MAX_TRACE_NUM = 64; + static constexpr size_t MAX_ERROR_NUM = 2; unw_word_t ips[MAX_TRACE_NUM]; char mangled[MAX_TRACE_NUM][256]{}; diff --git a/src/butil/compiler_specific.h b/src/butil/compiler_specific.h index 924fe4395d..7c1c26628e 100644 --- a/src/butil/compiler_specific.h +++ b/src/butil/compiler_specific.h @@ -208,10 +208,21 @@ #endif // Instruct ASan is enabled. -#if BUTIL_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -#define BUTIL_USE_ASAN 1 +#if defined(BUTIL_USE_ASAN) +#error "BUTIL_USE_ASAN cannot be set directly." +#elif BUTIL_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#define BUTIL_USE_ASAN #endif +// https://github.com/google/sanitizers/wiki/AddressSanitizer#turning-off-instrumentation +// Attribute to instruct ASan to ignore a function. +#if defined(COMPILER_GCC) +# define BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else +# define BUTIL_ATTRIBUTE_NO_SANITIZE_ADDRESS +#endif + + // Tell the compiler a function is using a printf-style format string. // |format_param| is the one-based index of the format string parameter; // |dots_param| is the one-based index of the "..." parameter. diff --git a/src/butil/containers/doubly_buffered_data.h b/src/butil/containers/doubly_buffered_data.h index 62f67eadc5..ff96a90395 100644 --- a/src/butil/containers/doubly_buffered_data.h +++ b/src/butil/containers/doubly_buffered_data.h @@ -21,7 +21,8 @@ #define BUTIL_DOUBLY_BUFFERED_DATA_H #include -#include // std::vector +#include +#include #include #include "butil/scoped_lock.h" #include "butil/thread_local.h" @@ -87,6 +88,8 @@ class DoublyBufferedData { class Wrapper; class WrapperTLSGroup; typedef int WrapperTLSId; + typedef std::shared_ptr WrapperSharedPtr; + typedef std::weak_ptr WrapperWeakPtr; public: class ScopedPtr { friend class DoublyBufferedData; @@ -111,7 +114,7 @@ class DoublyBufferedData { const T* _data; // Index of foreground instance used by ScopedPtr. int _index; - Wrapper* _w; + WrapperSharedPtr _w; }; DoublyBufferedData(); @@ -123,83 +126,26 @@ class DoublyBufferedData { // Returns 0 on success, -1 otherwise. int Read(ScopedPtr* ptr); + // `fn(const T&)' will be called with foreground instance. + // This function is not blocked by Read() and Modify() in other threads. + // Returns 0 on success, otherwise on error. + template + int Read(Fn&& fn); + // Modify background and foreground instances. fn(T&, ...) will be called // twice. Modify() from different threads are exclusive from each other. // NOTE: Call same series of fn to different equivalent instances should // result in equivalent instances, otherwise foreground and background // instance will be inconsistent. - template size_t Modify(Fn& fn); - template size_t Modify(Fn& fn, const Arg1&); - template - size_t Modify(Fn& fn, const Arg1&, const Arg2&); + template + size_t Modify(Fn&& fn, Args&&... args); // fn(T& background, const T& foreground, ...) will be called to background // and foreground instances respectively. - template size_t ModifyWithForeground(Fn& fn); - template - size_t ModifyWithForeground(Fn& fn, const Arg1&); - template - size_t ModifyWithForeground(Fn& fn, const Arg1&, const Arg2&); + template + size_t ModifyWithForeground(Fn&& fn, Args&&... args); private: - template - struct WithFG0 { - WithFG0(Fn& fn, T* data) : _fn(fn), _data(data) { } - size_t operator()(T& bg) { - return _fn(bg, (const T&)_data[&bg == _data]); - } - private: - Fn& _fn; - T* _data; - }; - - template - struct WithFG1 { - WithFG1(Fn& fn, T* data, const Arg1& arg1) - : _fn(fn), _data(data), _arg1(arg1) {} - size_t operator()(T& bg) { - return _fn(bg, (const T&)_data[&bg == _data], _arg1); - } - private: - Fn& _fn; - T* _data; - const Arg1& _arg1; - }; - - template - struct WithFG2 { - WithFG2(Fn& fn, T* data, const Arg1& arg1, const Arg2& arg2) - : _fn(fn), _data(data), _arg1(arg1), _arg2(arg2) {} - size_t operator()(T& bg) { - return _fn(bg, (const T&)_data[&bg == _data], _arg1, _arg2); - } - private: - Fn& _fn; - T* _data; - const Arg1& _arg1; - const Arg2& _arg2; - }; - - template - struct Closure1 { - Closure1(Fn& fn, const Arg1& arg1) : _fn(fn), _arg1(arg1) {} - size_t operator()(T& bg) { return _fn(bg, _arg1); } - private: - Fn& _fn; - const Arg1& _arg1; - }; - - template - struct Closure2 { - Closure2(Fn& fn, const Arg1& arg1, const Arg2& arg2) - : _fn(fn), _arg1(arg1), _arg2(arg2) {} - size_t operator()(T& bg) { return _fn(bg, _arg1, _arg2); } - private: - Fn& _fn; - const Arg1& _arg1; - const Arg2& _arg2; - }; - const T* UnsafeRead() const { return _data + _index.load(butil::memory_order_acquire); } @@ -209,8 +155,7 @@ class DoublyBufferedData { return _data + index; } - Wrapper* AddWrapper(Wrapper*); - void RemoveWrapper(Wrapper*); + WrapperSharedPtr GetWrapper(); // Foreground and background void. T _data[2]; @@ -222,7 +167,7 @@ class DoublyBufferedData { WrapperTLSId _wrapper_key; // All thread-local instances. - std::vector _wrappers; + std::vector _wrappers; // Sequence access to _wrappers. pthread_mutex_t _wrappers_mutex{}; @@ -231,8 +176,6 @@ class DoublyBufferedData { pthread_mutex_t _modify_mutex{}; }; -static const pthread_key_t INVALID_PTHREAD_KEY = (pthread_key_t)-1; - template class DoublyBufferedDataWrapperBase { public: @@ -253,18 +196,23 @@ template class DoublyBufferedData::WrapperTLSGroup { public: const static size_t RAW_BLOCK_SIZE = 4096; - const static size_t ELEMENTS_PER_BLOCK = RAW_BLOCK_SIZE / sizeof(Wrapper) > 0 ? RAW_BLOCK_SIZE / sizeof(Wrapper) : 1; + const static size_t ELEMENTS_PER_BLOCK = + RAW_BLOCK_SIZE / sizeof(WrapperSharedPtr) > 0 ? + RAW_BLOCK_SIZE / sizeof(WrapperSharedPtr) : 1; struct BAIDU_CACHELINE_ALIGNMENT ThreadBlock { - inline DoublyBufferedData::Wrapper* at(size_t offset) { - return _data + offset; + WrapperSharedPtr at(size_t offset) { + if (NULL == _data[offset]) { + _data[offset] = std::make_shared(); + } + return _data[offset]; }; private: - DoublyBufferedData::Wrapper _data[ELEMENTS_PER_BLOCK]; + WrapperSharedPtr _data[ELEMENTS_PER_BLOCK]; }; - inline static WrapperTLSId key_create() { + static WrapperTLSId key_create() { BAIDU_SCOPED_LOCK(_s_mutex); WrapperTLSId id = 0; if (!_get_free_ids().empty()) { @@ -276,7 +224,7 @@ class DoublyBufferedData::WrapperTLSGroup { return id; } - inline static int key_delete(WrapperTLSId id) { + static int key_delete(WrapperTLSId id) { BAIDU_SCOPED_LOCK(_s_mutex); if (id < 0 || id >= _s_id) { errno = EINVAL; @@ -286,17 +234,13 @@ class DoublyBufferedData::WrapperTLSGroup { return 0; } - inline static DoublyBufferedData::Wrapper* get_or_create_tls_data(WrapperTLSId id) { + static WrapperSharedPtr get_or_create_tls_data(WrapperTLSId id) { if (BAIDU_UNLIKELY(id < 0)) { CHECK(false) << "Invalid id=" << id; return NULL; } if (_s_tls_blocks == NULL) { - _s_tls_blocks = new (std::nothrow) std::vector; - if (BAIDU_UNLIKELY(_s_tls_blocks == NULL)) { - LOG(FATAL) << "Fail to create vector, " << berror(); - return NULL; - } + _s_tls_blocks = new std::vector; butil::thread_atexit(_destroy_tls_blocks); } const size_t block_id = (size_t)id / ELEMENTS_PER_BLOCK; @@ -306,12 +250,8 @@ class DoublyBufferedData::WrapperTLSGroup { } ThreadBlock* tb = (*_s_tls_blocks)[block_id]; if (tb == NULL) { - ThreadBlock* new_block = new (std::nothrow) ThreadBlock; - if (BAIDU_UNLIKELY(new_block == NULL)) { - return NULL; - } - tb = new_block; - (*_s_tls_blocks)[block_id] = new_block; + tb = new ThreadBlock; + (*_s_tls_blocks)[block_id] = tb; } return tb->at(id - block_id * ELEMENTS_PER_BLOCK); } @@ -374,10 +314,6 @@ friend class DoublyBufferedData; } ~Wrapper() { - if (_control != NULL) { - _control->RemoveWrapper(this); - } - if (AllowBthreadSuspended) { WaitReadDone(0); WaitReadDone(1); @@ -464,9 +400,9 @@ friend class DoublyBufferedData; // Called when thread initializes thread-local wrapper. template -typename DoublyBufferedData::Wrapper* -DoublyBufferedData::AddWrapper( - typename DoublyBufferedData::Wrapper* w) { +typename DoublyBufferedData::WrapperSharedPtr +DoublyBufferedData::GetWrapper() { + WrapperSharedPtr w = WrapperTLSGroup::get_or_create_tls_data(_wrapper_key); if (NULL == w) { return NULL; } @@ -481,29 +417,19 @@ DoublyBufferedData::AddWrapper( w->_control = this; BAIDU_SCOPED_LOCK(_wrappers_mutex); _wrappers.push_back(w); + // The chance to remove expired weak_ptr. + _wrappers.erase( + std::remove_if(_wrappers.begin(), _wrappers.end(), + [](const WrapperWeakPtr& w) { + return w.expired(); + }), + _wrappers.end()); } catch (std::exception& e) { return NULL; } return w; } -// Called when thread quits. -template -void DoublyBufferedData::RemoveWrapper( - typename DoublyBufferedData::Wrapper* w) { - if (NULL == w) { - return; - } - BAIDU_SCOPED_LOCK(_wrappers_mutex); - for (size_t i = 0; i < _wrappers.size(); ++i) { - if (_wrappers[i] == w) { - _wrappers[i] = _wrappers.back(); - _wrappers.pop_back(); - return; - } - } -} - template DoublyBufferedData::DoublyBufferedData() : _index(0) @@ -532,7 +458,10 @@ DoublyBufferedData::~DoublyBufferedData() { { BAIDU_SCOPED_LOCK(_wrappers_mutex); for (size_t i = 0; i < _wrappers.size(); ++i) { - _wrappers[i]->_control = NULL; // hack: disable removal. + WrapperSharedPtr w = _wrappers[i].lock(); + if (NULL != w) { + w->_control = NULL; // hack: disable removal. + } } _wrappers.clear(); } @@ -545,43 +474,55 @@ DoublyBufferedData::~DoublyBufferedData() { template int DoublyBufferedData::Read( typename DoublyBufferedData::ScopedPtr* ptr) { - Wrapper* p = WrapperTLSGroup::get_or_create_tls_data(_wrapper_key); - Wrapper* w = AddWrapper(p); - if (BAIDU_LIKELY(w != NULL)) { - if (AllowBthreadSuspended) { - // Use reference count instead of mutex to indicate read of - // foreground instance, so during the read process, there is - // no need to lock mutex and bthread is allowed to be suspended. - w->BeginRead(); - int index = -1; - ptr->_data = UnsafeRead(index); - ptr->_index = index; - w->AddRef(index); - ptr->_w = w; - w->BeginReadRelease(); - } else { - w->BeginRead(); - ptr->_data = UnsafeRead(); - ptr->_w = w; - } + WrapperSharedPtr w = GetWrapper(); + if (BAIDU_UNLIKELY(w == NULL)) { + return -1; + } - return 0; + if (AllowBthreadSuspended) { + // Use reference count instead of mutex to indicate read of + // foreground instance, so during the read process, there is + // no need to lock mutex and bthread is allowed to be suspended. + w->BeginRead(); + int index = -1; + ptr->_data = UnsafeRead(index); + ptr->_index = index; + w->AddRef(index); + ptr->_w = w; + w->BeginReadRelease(); + } else { + w->BeginRead(); + ptr->_data = UnsafeRead(); + ptr->_w = w; } - return -1; + return 0; } template template -size_t DoublyBufferedData::Modify(Fn& fn) { +int DoublyBufferedData::Read(Fn&& fn) { + BAIDU_CASSERT((is_result_void::value), + "Fn must accept `const T&' and return void"); + ScopedPtr ptr; + if (Read(&ptr) != 0) { + return -1; + } + fn(*ptr); + return 0; +} + +template +template +size_t DoublyBufferedData::Modify(Fn&& fn, Args&&... args) { // _modify_mutex sequences modifications. Using a separate mutex rather // than _wrappers_mutex is to avoid blocking threads calling - // AddWrapper() or RemoveWrapper() too long. Most of the time, modifications + // GetWrapper() too long. Most of the time, modifications // are done by one thread, contention should be negligible. BAIDU_SCOPED_LOCK(_modify_mutex); int bg_index = !_index.load(butil::memory_order_relaxed); // background instance is not accessed by other threads, being safe to // modify. - const size_t ret = fn(_data[bg_index]); + const size_t ret = fn(_data[bg_index], std::forward(args)...); if (!ret) { return 0; } @@ -597,56 +538,37 @@ size_t DoublyBufferedData::Modify(Fn& fn) { // read, they should see updated _index. { BAIDU_SCOPED_LOCK(_wrappers_mutex); - for (size_t i = 0; i < _wrappers.size(); ++i) { - // Wait read of old foreground instance done. - if (AllowBthreadSuspended) { - _wrappers[i]->WaitReadDone(bg_index); - } else { - _wrappers[i]->WaitReadDone(); - } - } + // The chance to remove expired weak_ptr. + _wrappers.erase( + std::remove_if(_wrappers.begin(), _wrappers.end(), + [bg_index](const WrapperWeakPtr& weak) { + WrapperSharedPtr w = weak.lock(); + bool expired = NULL == w; + if (!expired) { + // Notify all threads waiting for read done. + if (AllowBthreadSuspended) { + w->WaitReadDone(bg_index); + } else { + w->WaitReadDone(); + } + } + // Remove expired weak_ptr. + return expired; + }), + _wrappers.end()); } - const size_t ret2 = fn(_data[bg_index]); + const size_t ret2 = fn(_data[bg_index], std::forward(args)...); CHECK_EQ(ret2, ret) << "index=" << _index.load(butil::memory_order_relaxed); return ret2; } template -template -size_t DoublyBufferedData::Modify(Fn& fn, const Arg1& arg1) { - Closure1 c(fn, arg1); - return Modify(c); -} - -template -template -size_t DoublyBufferedData::Modify( - Fn& fn, const Arg1& arg1, const Arg2& arg2) { - Closure2 c(fn, arg1, arg2); - return Modify(c); -} - -template -template -size_t DoublyBufferedData::ModifyWithForeground(Fn& fn) { - WithFG0 c(fn, _data); - return Modify(c); -} - -template -template -size_t DoublyBufferedData::ModifyWithForeground(Fn& fn, const Arg1& arg1) { - WithFG1 c(fn, _data, arg1); - return Modify(c); -} - -template -template -size_t DoublyBufferedData::ModifyWithForeground( - Fn& fn, const Arg1& arg1, const Arg2& arg2) { - WithFG2 c(fn, _data, arg1, arg2); - return Modify(c); +template +size_t DoublyBufferedData::ModifyWithForeground(Fn&& fn, Args&&... args) { + return Modify([this, &fn](T& bg, Args&&... args) { + return fn(bg, (const T&)_data[&bg == _data], std::forward(args)...); + }, std::forward(args)...); } } // namespace butil diff --git a/src/butil/debug/address_annotations.h b/src/butil/debug/address_annotations.h new file mode 100644 index 0000000000..81e7ab11b2 --- /dev/null +++ b/src/butil/debug/address_annotations.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ +#define BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ + +#include "butil/macros.h" + +// It provides AddressSanitizer annotations for bthread and memory management. +// See for detail of these annotations. + +#ifdef BUTIL_USE_ASAN + +#include + +#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) \ + __asan_poison_memory_region(addr, size) + +#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + __asan_unpoison_memory_region(addr, size) + +#define BUTIL_ASAN_ADDRESS_IS_POISONED(addr) \ + __asan_address_is_poisoned(addr) + +#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \ + __sanitizer_start_switch_fiber(fake_stack_save, bottom, size) + +#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \ + __sanitizer_finish_switch_fiber(fake_stack_save, bottom_old, size_old) + +#else +// If ASan is not used, these annotations are no-ops. +#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) +#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) +#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \ + ((void)(fake_stack_save), (void)(bottom), (void)(size)) +#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \ + ((void)(fake_stack_save), (void)(bottom_old), (void)(size_old)) +#endif // BUTIL_USE_ASAN + +#endif // BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_ diff --git a/src/butil/debug/leak_annotations.h b/src/butil/debug/leak_annotations.h index 231e9982b6..47387acbb7 100644 --- a/src/butil/debug/leak_annotations.h +++ b/src/butil/debug/leak_annotations.h @@ -32,23 +32,23 @@ void __lsan_do_leak_check(); } // extern "C" class ScopedLeakSanitizerDisabler { - public: +public: ScopedLeakSanitizerDisabler() { __lsan_disable(); } ~ScopedLeakSanitizerDisabler() { __lsan_enable(); } - private: +private: DISALLOW_COPY_AND_ASSIGN(ScopedLeakSanitizerDisabler); }; #define ANNOTATE_SCOPED_MEMORY_LEAK \ ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast(0) -#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X); +#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X) #else // If neither HeapChecker nor LSan are used, the annotations should be no-ops. #define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0) -#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)0) +#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)(X)) #endif diff --git a/src/butil/file_util_posix.cc b/src/butil/file_util_posix.cc index 3e83336386..322e48a0ab 100644 --- a/src/butil/file_util_posix.cc +++ b/src/butil/file_util_posix.cc @@ -601,7 +601,7 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, if (mkdir(i->value().c_str(), 0755/*others can read the dir*/) == 0) continue; // Mkdir failed, but it might have failed with EEXIST, or some other error - // due to the the directory appearing out of thin air. This can occur if + // due to the directory appearing out of thin air. This can occur if // two processes are trying to create the same file system tree at the same // time. Check to see if it exists and make sure it is a directory. int saved_errno = errno; diff --git a/src/butil/iobuf.cpp b/src/butil/iobuf.cpp index 8895fb164c..59ad61a21b 100644 --- a/src/butil/iobuf.cpp +++ b/src/butil/iobuf.cpp @@ -355,6 +355,23 @@ inline IOBuf::Block* create_block() { return create_block(IOBuf::DEFAULT_BLOCK_SIZE); } +inline IOBuf::Block* create_block_aligned(size_t block_size, size_t alignment) { + if (block_size > 0xFFFFFFFFULL) { + LOG(FATAL) << "block_size=" << block_size << " is too large"; + return NULL; + } + char* mem = (char*)iobuf::blockmem_allocate(block_size); + if (mem == NULL) { + return NULL; + } + char* data = mem + sizeof(IOBuf::Block); + // change data pointer & data size make align satisfied + size_t adder = (-reinterpret_cast(data)) & (alignment - 1); + size_t size = + (block_size - sizeof(IOBuf::Block) - adder) & ~(alignment - 1); + return new (mem) IOBuf::Block(data + adder, size); +} + // === Share TLS blocks between appending operations === // Max number of blocks in each TLS. This is a soft limit namely // release_tls_block_chain() may exceed this limit sometimes. @@ -1785,6 +1802,45 @@ void IOPortal::return_cached_blocks_impl(Block* b) { iobuf::release_tls_block_chain(b); } +IOBuf::Area IOReserveAlignedBuf::reserve(size_t count) { + IOBuf::Area result = INVALID_AREA; + if (_reserved == true) { + LOG(ERROR) << "Already call reserved"; + return result; + } + _reserved = true; + bool is_power_two = _alignment > 0 && (_alignment & (_alignment - 1)); + if (is_power_two != 0) { + LOG(ERROR) << "Invalid alignment, must power of two"; + return INVALID_AREA; + } + count = (count + _alignment - 1) & ~(_alignment - 1); + size_t total_nc = 0; + while (total_nc < count) { + const auto block_size = + std::max(_alignment, 4096UL) * 2 + sizeof(IOBuf::Block); + auto b = iobuf::create_block_aligned(block_size, _alignment); + if (BAIDU_UNLIKELY(!b)) { + LOG(ERROR) << "Create block failed"; + return result; + } + const size_t nc = std::min(count - total_nc, b->left_space()); + const IOBuf::BlockRef r = {(uint32_t)b->size, (uint32_t)nc, b}; + _push_back_ref(r); + // aligned block is not from tls, release block ref + b->dec_ref(); + if (total_nc == 0) { + // Encode the area at first time. Notice that the pushed ref may + // be merged with existing ones. + result = make_area(_ref_num() - 1, _back_ref().length - nc, count); + } + // add total nc + total_nc += nc; + b->size += nc; + }; + return result; +} + //////////////// IOBufCutter //////////////// IOBufCutter::IOBufCutter(butil::IOBuf* buf) diff --git a/src/butil/iobuf.h b/src/butil/iobuf.h index 978f9758b2..3682f61ecc 100644 --- a/src/butil/iobuf.h +++ b/src/butil/iobuf.h @@ -489,6 +489,17 @@ class IOPortal : public IOBuf { Block* _block; }; +class IOReserveAlignedBuf : public IOBuf { +public: + IOReserveAlignedBuf(size_t alignment) + : _alignment(alignment), _reserved(false) {} + Area reserve(size_t count); + +private: + size_t _alignment; + bool _reserved; +}; + // Specialized utility to cut from IOBuf faster than using corresponding // methods in IOBuf. // Designed for efficiently parsing data from IOBuf. diff --git a/src/butil/logging.cc b/src/butil/logging.cc index a15251b2db..2f759f6a42 100644 --- a/src/butil/logging.cc +++ b/src/butil/logging.cc @@ -395,7 +395,7 @@ bool InitializeLogFileHandle() { #elif defined(OS_POSIX) log_file = fopen(log_file_name->c_str(), "a"); if (log_file == NULL) { - fprintf(stderr, "Fail to fopen %s", log_file_name->c_str()); + fprintf(stderr, "Fail to fopen %s: %s", log_file_name->c_str(), berror()); return false; } #endif @@ -464,6 +464,13 @@ TimeVal GetTimestamp() { } struct BAIDU_CACHELINE_ALIGNMENT LogInfo { + ~LogInfo() = default; + void clear() { + file.clear(); + func.clear(); + content.clear(); + } + std::string file; std::string func; std::string content; @@ -751,7 +758,7 @@ bool AsyncLogger::IsLogComplete(LogRequest* old_head) { void AsyncLogger::DoLog(LogRequest* req) { DoLog(req->log_info); - req->log_info.content.clear(); + req->log_info.clear(); } void AsyncLogger::DoLog(const LogInfo& log_info) { diff --git a/src/butil/memory/singleton_on_pthread_once.h b/src/butil/memory/singleton_on_pthread_once.h index 378d708b4d..9699bba7cb 100644 --- a/src/butil/memory/singleton_on_pthread_once.h +++ b/src/butil/memory/singleton_on_pthread_once.h @@ -25,7 +25,13 @@ namespace butil { -template class GetLeakySingleton { +template +T* create_leaky_singleton_obj() { + return new T(); +} + +template +class GetLeakySingleton { public: static butil::subtle::AtomicWord g_leaky_singleton_untyped; static pthread_once_t g_create_leaky_singleton_once; @@ -39,7 +45,7 @@ pthread_once_t GetLeakySingleton::g_create_leaky_singleton_once = PTHREAD_ONC template void GetLeakySingleton::create_leaky_singleton() { - T* obj = new T; + T* obj = create_leaky_singleton_obj(); butil::subtle::Release_Store( &g_leaky_singleton_untyped, reinterpret_cast(obj)); diff --git a/src/butil/object_pool.h b/src/butil/object_pool.h index aa265dfae8..92bc4036c3 100644 --- a/src/butil/object_pool.h +++ b/src/butil/object_pool.h @@ -23,6 +23,7 @@ #define BUTIL_OBJECT_POOL_H #include // size_t +#include "butil/type_traits.h" // ObjectPool is a derivative class of ResourcePool to allocate and // reuse fixed-size objects without identifiers. @@ -63,6 +64,10 @@ template struct ObjectPoolValidator { static bool validate(const T*) { return true; } }; +// +template +struct ObjectPoolWithASanPoison : true_type {}; + } // namespace butil #include "butil/object_pool_inl.h" diff --git a/src/butil/object_pool_inl.h b/src/butil/object_pool_inl.h index f4853262fe..20d4aea567 100644 --- a/src/butil/object_pool_inl.h +++ b/src/butil/object_pool_inl.h @@ -22,15 +22,16 @@ #ifndef BUTIL_OBJECT_POOL_INL_H #define BUTIL_OBJECT_POOL_INL_H -#include // std::ostream -#include // pthread_mutex_t -#include // std::max, std::min -#include "butil/atomicops.h" // butil::atomic -#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT -#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK -#include "butil/thread_local.h" // BAIDU_THREAD_LOCAL -#include "butil/memory/aligned_memory.h" // butil::AlignedMemory +#include // std::ostream +#include // pthread_mutex_t +#include // std::max, std::min #include +#include "butil/atomicops.h" // butil::atomic +#include "butil/macros.h" // BAIDU_CACHELINE_ALIGNMENT +#include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK +#include "butil/thread_local.h" // BAIDU_THREAD_LOCAL +#include "butil/memory/aligned_memory.h" // butil::AlignedMemory +#include "butil/debug/address_annotations.h" #ifdef BUTIL_OBJECT_POOL_NEED_FREE_ITEM_NUM #define BAIDU_OBJECT_POOL_FREE_ITEM_NUM_ADD1 \ @@ -54,7 +55,7 @@ template struct ObjectPoolFreeChunk { size_t nfree; T* ptrs[0]; -}; +}; struct ObjectPoolInfo { size_t local_pool_num; @@ -85,6 +86,29 @@ class ObjectPoolBlockItemNum { template class BAIDU_CACHELINE_ALIGNMENT ObjectPool { +private: +#ifdef BUTIL_USE_ASAN + static void asan_poison_memory_region(T* ptr) { + if (!ObjectPoolWithASanPoison::value || NULL == ptr) { + return; + } + // Marks the object as addressable. + BUTIL_ASAN_POISON_MEMORY_REGION(ptr, sizeof(T)); + } + static void asan_unpoison_memory_region(T* ptr) { + if (!ObjectPoolWithASanPoison::value || NULL == ptr) { + return; + } + // Marks the object as unaddressable. + BUTIL_ASAN_UNPOISON_MEMORY_REGION(ptr, sizeof(T)); + } +#define OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr) asan_poison_memory_region(ptr) +#define OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr) asan_unpoison_memory_region(ptr) +#else +#define OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr) ((void)(ptr)) +#define OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr) ((void)(ptr)) +#endif // BUTIL_USE_ASAN + public: static const size_t BLOCK_NITEM = ObjectPoolBlockItemNum::value; static const size_t FREE_CHUNK_NITEM = BLOCK_NITEM; @@ -93,8 +117,15 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { // global list(_free_chunks). typedef ObjectPoolFreeChunk FreeChunk; typedef ObjectPoolFreeChunk DynamicFreeChunk; - +#ifdef BUTIL_USE_ASAN + // According to https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning , + // The allocated chunks should start with 8-aligned addresses, + // so that AlignedMemory starts with at least 8-aligned addresses. + typedef AlignedMemory BlockItem; +#else typedef AlignedMemory BlockItem; +#endif + // When a thread needs memory, it allocates a Block. To improve locality, // items in the Block are only used by the thread. // To support cache-aligned objects, align Block.items by cacheline. @@ -169,6 +200,8 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { obj->~T(); \ return NULL; \ } \ + /* It's poisoned prior to use. */ \ + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(obj); \ ++_cur_block->nitem; \ return obj; \ } \ @@ -181,6 +214,8 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { obj->~T(); \ return NULL; \ } \ + /* It's poisoned prior to use. */ \ + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(obj); \ ++_cur_block->nitem; \ return obj; \ } \ @@ -199,6 +234,9 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { #undef BAIDU_OBJECT_POOL_GET inline int return_object(T* ptr) { + // TODO. Refer to ASan to implement a efficient quarantine mechanism. + OBJECT_POOL_ASAN_POISON_MEMORY_REGION(ptr); + // Return to local free list if (_cur_free.nfree < ObjectPool::free_chunk_nitem()) { _cur_free.ptrs[_cur_free.nfree++] = ptr; @@ -238,10 +276,12 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { template inline T* get_object(Args&&... args) { LocalPool* lp = get_or_new_local_pool(); + T* ptr = NULL; if (BAIDU_LIKELY(lp != NULL)) { - return lp->get(std::forward(args)...); + ptr = lp->get(std::forward(args)...); + OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(ptr); } - return NULL; + return ptr; } inline int return_object(T* ptr) { @@ -432,8 +472,10 @@ class BAIDU_CACHELINE_ALIGNMENT ObjectPool { continue; } for (size_t k = 0; k < b->nitem; ++k) { - T* const objs = (T*)b->items; - objs[k].~T(); + T* obj = (T*)&b->items[k]; + // Unpoison to avoid affecting other allocator. + OBJECT_POOL_ASAN_UNPOISON_MEMORY_REGION(obj); + obj->~T(); } delete b; } diff --git a/src/butil/thread_local.h b/src/butil/thread_local.h index 8c83947ce2..c33c7285f1 100644 --- a/src/butil/thread_local.h +++ b/src/butil/thread_local.h @@ -60,7 +60,7 @@ extern void set_##var_name(type v) #else #define BAIDU_GET_VOLATILE_THREAD_LOCAL(var_name) var_name -#define BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(var_name) &##var_name +#define BAIDU_GET_PTR_VOLATILE_THREAD_LOCAL(var_name) &var_name #define BAIDU_SET_VOLATILE_THREAD_LOCAL(var_name, value) var_name = value #define EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(type, var_name) \ extern BAIDU_THREAD_LOCAL type var_name diff --git a/src/butil/type_traits.h b/src/butil/type_traits.h index 0ae91135de..735e1e55ce 100644 --- a/src/butil/type_traits.h +++ b/src/butil/type_traits.h @@ -99,7 +99,7 @@ template struct is_pod std::is_trivial::value)> {}; #else template struct is_pod : std::is_pod {}; -#endif +#endif // __cplusplus #else // We can't get is_pod right without compiler help, so fail conservatively. @@ -334,7 +334,7 @@ template struct remove_cvref { typedef typename remove_cv::type>::type type; }; -#endif +#endif // __cplusplus // is_reference is false except for reference types. template struct is_reference : false_type {}; @@ -378,7 +378,7 @@ template using result_of = std::result_of; #else #error Only C++11 or later is supported. -#endif +#endif // __cplusplus template using result_of_t = typename result_of::type; diff --git a/src/bvar/detail/combiner.h b/src/bvar/detail/combiner.h index 6a6ab80330..07baa89158 100644 --- a/src/bvar/detail/combiner.h +++ b/src/bvar/detail/combiner.h @@ -22,6 +22,7 @@ #include // std::string #include // std::vector +#include #include "butil/atomicops.h" // butil::atomic #include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK #include "butil/type_traits.h" // butil::add_cr_non_integral @@ -42,7 +43,6 @@ class GlobalValue { typedef typename Combiner::Agent agent_type; GlobalValue(agent_type* a, Combiner* c) : _a(a), _c(c) {} - ~GlobalValue() {} // Call this method to unlock tls element and lock the combiner. // Unlocking tls element avoids potential deadlock with @@ -153,24 +153,26 @@ class ElementContainer< }; template -class AgentCombiner { +class AgentCombiner + : public std::enable_shared_from_this> { + public: typedef ResultTp result_type; typedef ElementTp element_type; typedef AgentCombiner self_type; + typedef std::shared_ptr self_shared_type; + typedef std::weak_ptr self_weak_type; friend class GlobalValue; - - struct Agent : public butil::LinkNode { - Agent() : combiner(NULL) {} + struct Agent : public butil::LinkNode { ~Agent() { - if (combiner) { - combiner->commit_and_erase(this); - combiner = NULL; + self_shared_type c = combiner.lock(); + if (NULL != c) { + c->commit_and_erase(this); } } - void reset(const ElementTp& val, self_type* c) { + void reset(const ElementTp& val, self_shared_type c) { combiner = c; element.store(val); } @@ -203,13 +205,20 @@ friend class GlobalValue; // // NOTE: Only available to non-atomic types. template - void merge_global(const Op &op) { - GlobalValue g(this, combiner); - element.merge_global(op, g); + void merge_global(const Op &op, self_shared_type c = NULL) { + if (NULL == c) { + c = combiner.lock(); + } + if (NULL != c) { + GlobalValue g(this, c.get()); + element.merge_global(op, g); + } } - self_type *combiner; ElementContainer element; + private: + friend class AgentCombiner; + self_weak_type combiner; }; typedef detail::AgentGroup AgentGroup; @@ -245,10 +254,10 @@ friend class GlobalValue; return ret; } - typename butil::add_cr_non_integral::type element_identity() const - { return _element_identity; } - typename butil::add_cr_non_integral::type result_identity() const - { return _result_identity; } + typename butil::add_cr_non_integral::type + element_identity() const { return _element_identity; } + typename butil::add_cr_non_integral::type + result_identity() const { return _result_identity; } // [Threadsafe] May be called from anywhere. ResultTp reset_all_agents() { @@ -265,7 +274,7 @@ friend class GlobalValue; } // Always called from the thread owning the agent. - void commit_and_erase(Agent *agent) { + void commit_and_erase(Agent* agent) { if (NULL == agent) { return; } @@ -279,7 +288,7 @@ friend class GlobalValue; } // Always called from the thread owning the agent - void commit_and_clear(Agent *agent) { + void commit_and_clear(Agent* agent) { if (NULL == agent) { return; } @@ -290,7 +299,7 @@ friend class GlobalValue; } // We need this function to be as fast as possible. - inline Agent* get_or_create_tls_agent() { + Agent* get_or_create_tls_agent() { Agent* agent = AgentGroup::get_tls_agent(_id); if (!agent) { // Create the agent @@ -300,10 +309,10 @@ friend class GlobalValue; return NULL; } } - if (agent->combiner) { + if (!agent->combiner.expired()) { return agent; } - agent->reset(_element_identity, this); + agent->reset(_element_identity, this->shared_from_this()); // TODO: Is uniqueness-checking necessary here? { butil::AutoLock guard(_lock); @@ -314,11 +323,10 @@ friend class GlobalValue; void clear_all_agents() { butil::AutoLock guard(_lock); - // reseting agents is must because the agent object may be reused. + // Resting agents is must because the agent object may be reused. // Set element to be default-constructed so that if it's non-pod, // internal allocations should be released. - for (butil::LinkNode* - node = _agents.head(); node != _agents.end();) { + for (butil::LinkNode* node = _agents.head(); node != _agents.end();) { node->value()->reset(ElementTp(), NULL); butil::LinkNode* const saved_next = node->next(); node->RemoveFromList(); diff --git a/src/bvar/detail/percentile.cpp b/src/bvar/detail/percentile.cpp index e0412cbe42..37181cc3a8 100644 --- a/src/bvar/detail/percentile.cpp +++ b/src/bvar/detail/percentile.cpp @@ -85,9 +85,8 @@ class AddLatency { int64_t _latency; }; -Percentile::Percentile() : _combiner(NULL), _sampler(NULL) { - _combiner = new combiner_type; -} +Percentile::Percentile() + : _combiner(std::make_shared()), _sampler(NULL) {} Percentile::~Percentile() { // Have to destroy sampler first to avoid the race between destruction and @@ -96,7 +95,6 @@ Percentile::~Percentile() { _sampler->destroy(); _sampler = NULL; } - delete _combiner; } Percentile::value_type Percentile::reset() { @@ -126,7 +124,7 @@ Percentile &Percentile::operator<<(int64_t latency) { } return *this; } - agent->merge_global(AddLatency(latency)); + agent->merge_global(AddLatency(latency), _combiner); return *this; } diff --git a/src/bvar/detail/percentile.h b/src/bvar/detail/percentile.h index 5fcc180ab6..186103b4af 100644 --- a/src/bvar/detail/percentile.h +++ b/src/bvar/detail/percentile.h @@ -462,6 +462,7 @@ class Percentile { typedef AgentCombiner combiner_type; + typedef typename combiner_type::self_shared_type shared_combiner_type; typedef combiner_type::Agent agent_type; Percentile(); ~Percentile(); @@ -494,8 +495,8 @@ class Percentile { private: DISALLOW_COPY_AND_ASSIGN(Percentile); - combiner_type* _combiner; - sampler_type* _sampler; + shared_combiner_type _combiner; + sampler_type* _sampler; std::string _debug_name; }; diff --git a/src/bvar/multi_dimension.h b/src/bvar/multi_dimension.h index 43be82ccfd..cc249c5f5c 100644 --- a/src/bvar/multi_dimension.h +++ b/src/bvar/multi_dimension.h @@ -29,8 +29,6 @@ namespace bvar { -constexpr uint64_t MAX_MULTI_DIMENSION_STATS_COUNT = 20000; - template class MultiDimension : public MVariable { public: @@ -150,4 +148,4 @@ class MultiDimension : public MVariable { #include "bvar/multi_dimension_inl.h" -#endif // BVAR_MULTI_DIMENSION_H \ No newline at end of file +#endif // BVAR_MULTI_DIMENSION_H diff --git a/src/bvar/multi_dimension_inl.h b/src/bvar/multi_dimension_inl.h index a0158b15db..b7ef2b2d30 100644 --- a/src/bvar/multi_dimension_inl.h +++ b/src/bvar/multi_dimension_inl.h @@ -27,6 +27,7 @@ namespace bvar { DECLARE_int32(bvar_latency_p1); DECLARE_int32(bvar_latency_p2); DECLARE_int32(bvar_latency_p3); +DECLARE_uint32(max_multi_dimension_stats_count); static const std::string ALLOW_UNUSED METRIC_TYPE_COUNTER = "counter"; static const std::string ALLOW_UNUSED METRIC_TYPE_SUMMARY = "summary"; @@ -37,7 +38,7 @@ template inline MultiDimension::MultiDimension(const key_type& labels) : Base(labels) - , _max_stats_count(MAX_MULTI_DIMENSION_STATS_COUNT) + , _max_stats_count(FLAGS_max_multi_dimension_stats_count) { _metric_map.Modify(init_flatmap); } @@ -251,7 +252,7 @@ size_t MultiDimension::dump(Dumper* dumper, const DumpOptions* options) { bvar->describe(oss, options->quote_string); std::ostringstream oss_key; make_dump_key(oss_key, label_name); - if (!dumper->dump(oss_key.str(), oss.str())) { + if (!dumper->dump_mvar(oss_key.str(), oss.str())) { continue; } n++; @@ -268,20 +269,20 @@ size_t MultiDimension::dump(Dumper* dumper, const DumpOpt return 0; } size_t n = 0; + // To meet prometheus specification, we must guarantee no second TYPE line for one metric name + + // latency comment + dumper->dump_comment(name() + "_latency", METRIC_TYPE_GAUGE); for (auto &label_name : label_names) { bvar::LatencyRecorder* bvar = get_stats_impl(label_name); if (!bvar) { continue; } - // latency comment - if (!dumper->dump_comment(name() + "_latency", METRIC_TYPE_GAUGE)) { - continue; - } // latency std::ostringstream oss_latency_key; make_dump_key(oss_latency_key, label_name, "_latency"); - if (dumper->dump(oss_latency_key.str(), std::to_string(bvar->latency()))) { + if (dumper->dump_mvar(oss_latency_key.str(), std::to_string(bvar->latency()))) { n++; } // latency_percentiles @@ -290,53 +291,62 @@ size_t MultiDimension::dump(Dumper* dumper, const DumpOpt for (auto lp : latency_percentiles) { std::ostringstream oss_lp_key; make_dump_key(oss_lp_key, label_name, "_latency", lp); - if (dumper->dump(oss_lp_key.str(), std::to_string(bvar->latency_percentile(lp / 100.0)))) { + if (dumper->dump_mvar(oss_lp_key.str(), std::to_string(bvar->latency_percentile(lp / 100.0)))) { n++; } } // 999 std::ostringstream oss_p999_key; make_dump_key(oss_p999_key, label_name, "_latency", 999); - if (dumper->dump(oss_p999_key.str(), std::to_string(bvar->latency_percentile(0.999)))) { + if (dumper->dump_mvar(oss_p999_key.str(), std::to_string(bvar->latency_percentile(0.999)))) { n++; } // 9999 std::ostringstream oss_p9999_key; make_dump_key(oss_p9999_key, label_name, "_latency", 9999); - if (dumper->dump(oss_p9999_key.str(), std::to_string(bvar->latency_percentile(0.9999)))) { + if (dumper->dump_mvar(oss_p9999_key.str(), std::to_string(bvar->latency_percentile(0.9999)))) { n++; } + } - // max_latency comment - if (!dumper->dump_comment(name() + "_max_latency", METRIC_TYPE_GAUGE)) { + // max_latency comment + dumper->dump_comment(name() + "_max_latency", METRIC_TYPE_GAUGE); + for (auto &label_name : label_names) { + bvar::LatencyRecorder* bvar = get_stats_impl(label_name); + if (!bvar) { continue; } - // max_latency std::ostringstream oss_max_latency_key; make_dump_key(oss_max_latency_key, label_name, "_max_latency"); - if (dumper->dump(oss_max_latency_key.str(), std::to_string(bvar->max_latency()))) { + if (dumper->dump_mvar(oss_max_latency_key.str(), std::to_string(bvar->max_latency()))) { n++; } - - // qps comment - if (!dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE)) { + } + + // qps comment + dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE); + for (auto &label_name : label_names) { + bvar::LatencyRecorder* bvar = get_stats_impl(label_name); + if (!bvar) { continue; } - // qps std::ostringstream oss_qps_key; make_dump_key(oss_qps_key, label_name, "_qps"); - if (dumper->dump(oss_qps_key.str(), std::to_string(bvar->qps()))) { + if (dumper->dump_mvar(oss_qps_key.str(), std::to_string(bvar->qps()))) { n++; } + } - // qps comment - if (!dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER)) { + // count comment + dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER); + for (auto &label_name : label_names) { + bvar::LatencyRecorder* bvar = get_stats_impl(label_name); + if (!bvar) { continue; } - // count std::ostringstream oss_count_key; make_dump_key(oss_count_key, label_name, "_count"); - if (dumper->dump(oss_count_key.str(), std::to_string(bvar->count()))) { + if (dumper->dump_mvar(oss_count_key.str(), std::to_string(bvar->count()))) { n++; } } diff --git a/src/bvar/mvariable.cpp b/src/bvar/mvariable.cpp index 86eadf1333..26bb97c46f 100644 --- a/src/bvar/mvariable.cpp +++ b/src/bvar/mvariable.cpp @@ -60,6 +60,17 @@ DEFINE_int32(bvar_max_dump_multi_dimension_metric_number, 1024, BUTIL_VALIDATE_GFLAG(bvar_max_dump_multi_dimension_metric_number, validator_bvar_max_dump_multi_dimension_metric_number); +static bool validator_max_multi_dimension_stats_count(const char*, uint32_t v) { + if (v < 1) { + LOG(ERROR) << "Invalid max_multi_dimension_stats_count=" << v; + return false; + } + return true; +} +DEFINE_uint32(max_multi_dimension_stats_count, 20000, "Max stats count of a multi dimension metric."); +BUTIL_VALIDATE_GFLAG(max_multi_dimension_stats_count, + validator_max_multi_dimension_stats_count); + class MVarEntry { public: MVarEntry() : var(NULL) {} diff --git a/src/bvar/recorder.h b/src/bvar/recorder.h index 9b73a19b54..c2c18bd144 100644 --- a/src/bvar/recorder.h +++ b/src/bvar/recorder.h @@ -113,9 +113,10 @@ class IntRecorder : public Variable { }; typedef detail::AgentCombiner combiner_type; + typedef typename combiner_type::self_shared_type shared_combiner_type; typedef combiner_type::Agent agent_type; - IntRecorder() : _sampler(NULL) {} + IntRecorder() : _combiner(std::make_shared()), _sampler(NULL) {} explicit IntRecorder(const butil::StringPiece& name) : _sampler(NULL) { expose(name); @@ -126,7 +127,7 @@ class IntRecorder : public Variable { expose_as(prefix, name); } - ~IntRecorder() { + ~IntRecorder() override { hide(); if (_sampler) { _sampler->destroy(); @@ -138,19 +139,19 @@ class IntRecorder : public Variable { IntRecorder& operator<<(int64_t/*note*/ sample); int64_t average() const { - return _combiner.combine_agents().get_average_int(); + return _combiner->combine_agents().get_average_int(); } double average(double) const { - return _combiner.combine_agents().get_average_double(); + return _combiner->combine_agents().get_average_double(); } Stat get_value() const { - return _combiner.combine_agents(); + return _combiner->combine_agents(); } Stat reset() { - return _combiner.reset_all_agents(); + return _combiner->reset_all_agents(); } AddStat op() const { return AddStat(); } @@ -160,7 +161,7 @@ class IntRecorder : public Variable { os << get_value(); } - bool valid() const { return _combiner.valid(); } + bool valid() const { return _combiner->valid(); } sampler_type* get_sampler() { if (NULL == _sampler) { @@ -230,7 +231,7 @@ class IntRecorder : public Variable { } private: - combiner_type _combiner; + shared_combiner_type _combiner; sampler_type* _sampler; std::string _debug_name; }; @@ -258,7 +259,7 @@ inline IntRecorder& IntRecorder::operator<<(int64_t sample) { << (void*)this << ") " << reason; } } - agent_type* agent = _combiner.get_or_create_tls_agent(); + agent_type* agent = _combiner->get_or_create_tls_agent(); if (BAIDU_UNLIKELY(!agent)) { LOG(FATAL) << "Fail to create agent"; return *this; @@ -276,7 +277,7 @@ inline IntRecorder& IntRecorder::operator<<(int64_t sample) { // Although agent->element might have been cleared at this // point, it is just OK because the very value is 0 in // this case - agent->combiner->commit_and_clear(agent); + _combiner->commit_and_clear(agent); sum = 0; num = 0; n = 0; diff --git a/src/bvar/reducer.h b/src/bvar/reducer.h index fbd4fa78d1..ccf7805420 100644 --- a/src/bvar/reducer.h +++ b/src/bvar/reducer.h @@ -69,13 +69,13 @@ template class Reducer : public Variable { public: typedef typename detail::AgentCombiner combiner_type; + typedef typename combiner_type::self_shared_type shared_combiner_type; typedef typename combiner_type::Agent agent_type; typedef detail::ReducerSampler sampler_type; class SeriesSampler : public detail::Sampler { public: SeriesSampler(Reducer* owner, const Op& op) : _owner(owner), _series(op) {} - ~SeriesSampler() {} void take_sample() override { _series.append(_owner->get_value()); } void describe(std::ostream& os) { _series.describe(os, NULL); } private: @@ -85,16 +85,12 @@ class Reducer : public Variable { public: // The `identify' must satisfy: identity Op a == a - Reducer(typename butil::add_cr_non_integral::type identity = T(), - const Op& op = Op(), - const InvOp& inv_op = InvOp()) - : _combiner(identity, identity, op) - , _sampler(NULL) - , _series_sampler(NULL) - , _inv_op(inv_op) { - } + explicit Reducer(typename butil::add_cr_non_integral::type identity = T(), + const Op& op = Op(), const InvOp& inv_op = InvOp()) + : _combiner(std::make_shared(identity, identity, op)) + , _sampler(NULL) , _series_sampler(NULL) , _inv_op(inv_op) {} - ~Reducer() { + ~Reducer() override { // Calling hide() manually is a MUST required by Variable. hide(); if (_sampler) { @@ -119,13 +115,13 @@ class Reducer : public Variable { << "You should not call Reducer<" << butil::class_name_str() << ", " << butil::class_name_str() << ">::get_value() when a" << " Window<> is used because the operator does not have inverse."; - return _combiner.combine_agents(); + return _combiner->combine_agents(); } // Reset the reduced value to T(). // Returns the reduced value before reset. - T reset() { return _combiner.reset_all_agents(); } + T reset() { return _combiner->reset_all_agents(); } void describe(std::ostream& os, bool quote_string) const override { if (butil::is_same::value && quote_string) { @@ -140,10 +136,10 @@ class Reducer : public Variable { #endif // True if this reducer is constructed successfully. - bool valid() const { return _combiner.valid(); } + bool valid() const { return _combiner->valid(); } // Get instance of Op. - const Op& op() const { return _combiner.op(); } + const Op& op() const { return _combiner->op(); } const InvOp& inv_op() const { return _inv_op; } sampler_type* get_sampler() { @@ -174,14 +170,14 @@ class Reducer : public Variable { !butil::is_same::value && !butil::is_same::value && FLAGS_save_series) { - _series_sampler = new SeriesSampler(this, _combiner.op()); + _series_sampler = new SeriesSampler(this, _combiner->op()); _series_sampler->schedule(); } return rc; } private: - combiner_type _combiner; + shared_combiner_type _combiner; sampler_type* _sampler; SeriesSampler* _series_sampler; InvOp _inv_op; @@ -191,12 +187,12 @@ template inline Reducer& Reducer::operator<<( typename butil::add_cr_non_integral::type value) { // It's wait-free for most time - agent_type* agent = _combiner.get_or_create_tls_agent(); + agent_type* agent = _combiner->get_or_create_tls_agent(); if (__builtin_expect(!agent, 0)) { LOG(FATAL) << "Fail to create agent"; return *this; } - agent->element.modify(_combiner.op(), value); + agent->element.modify(_combiner->op(), value); return *this; } diff --git a/src/bvar/variable.h b/src/bvar/variable.h index ce6ff4ac0d..86e9cd0c34 100644 --- a/src/bvar/variable.h +++ b/src/bvar/variable.h @@ -53,6 +53,12 @@ class Dumper { virtual ~Dumper() { } virtual bool dump(const std::string& name, const butil::StringPiece& description) = 0; + // Only for dumping value of multiple dimension var to prometheus service + virtual bool dump_mvar(const std::string& name, + const butil::StringPiece& description) { + return true; + } + // Only for dumping comment of multiple dimension var to prometheus service virtual bool dump_comment(const std::string&, const std::string& /*type*/) { return true; } diff --git a/src/json2pb/json_to_pb.cpp b/src/json2pb/json_to_pb.cpp index 60ba4fdfc9..82327cd6d3 100644 --- a/src/json2pb/json_to_pb.cpp +++ b/src/json2pb/json_to_pb.cpp @@ -24,17 +24,24 @@ #include #include #include +#include +#include #include "butil/strings/string_number_conversions.h" #include "butil/third_party/rapidjson/error/error.h" #include "butil/third_party/rapidjson/rapidjson.h" -#include "json_to_pb.h" -#include "zero_copy_stream_reader.h" // ZeroCopyStreamReader -#include "encode_decode.h" +#include "json2pb/json_to_pb.h" +#include "json2pb/zero_copy_stream_reader.h" // ZeroCopyStreamReader +#include "json2pb/encode_decode.h" +#include "json2pb/protobuf_map.h" +#include "json2pb/rapidjson.h" +#include "json2pb/protobuf_type_resolver.h" #include "butil/base64.h" -#include "butil/string_printf.h" -#include "protobuf_map.h" -#include "rapidjson.h" +#include "butil/iobuf.h" +#ifdef __GNUC__ +// Ignore -Wnonnull for `(::google::protobuf::Message*)nullptr' of J2PERROR by design. +#pragma GCC diagnostic ignored "-Wnonnull" +#endif #define J2PERROR(perr, fmt, ...) \ J2PERROR_WITH_PB((::google::protobuf::Message*)nullptr, perr, fmt, ##__VA_ARGS__) @@ -707,6 +714,39 @@ bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream *stream, std::string* error) { return JsonToProtoMessage(stream, message, Json2PbOptions(), error, nullptr); } + +bool ProtoJsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const ProtoJson2PbOptions& options, + std::string* error) { + TypeResolverUniqueptr type_resolver = GetTypeResolver(*message); + std::string type_url = GetTypeUrl(*message); + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream output_stream(&buf); + auto st = google::protobuf::util::JsonToBinaryStream( + type_resolver.get(), type_url, json, &output_stream, options); + if (!st.ok()) { + if (NULL != error) { + *error = st.ToString(); + } + return false; + } + + butil::IOBufAsZeroCopyInputStream input_stream(buf); + google::protobuf::io::CodedInputStream decoder(&input_stream); + bool ok = message->ParseFromCodedStream(&decoder); + if (!ok && NULL != error) { + *error = "Fail to ParseFromCodedStream"; + } + return ok; +} + +bool ProtoJsonToProtoMessage(const std::string& json, google::protobuf::Message* message, + const ProtoJson2PbOptions& options, std::string* error) { + google::protobuf::io::ArrayInputStream input_stream(json.data(), json.size()); + return ProtoJsonToProtoMessage(&input_stream, message, options, error); +} + } //namespace json2pb #undef J2PERROR diff --git a/src/json2pb/json_to_pb.h b/src/json2pb/json_to_pb.h index 44203e080c..3734ef313e 100644 --- a/src/json2pb/json_to_pb.h +++ b/src/json2pb/json_to_pb.h @@ -23,6 +23,7 @@ #include "json2pb/zero_copy_stream_reader.h" #include #include // ZeroCopyInputStream +#include namespace json2pb { @@ -43,7 +44,7 @@ struct Json2PbOptions { bool allow_remaining_bytes_after_parsing; }; -// Convert `json' to protobuf `message'. +// Convert `json' to protobuf `message' according to `options'. // Returns true on success. `error' (if not NULL) will be set with error // message on failure. // @@ -58,18 +59,18 @@ bool JsonToProtoMessage(const std::string& json, size_t* parsed_offset = nullptr); // Use ZeroCopyInputStream as input instead of std::string. -bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream *json, - google::protobuf::Message *message, - const Json2PbOptions &options, - std::string *error = nullptr, - size_t *parsed_offset = nullptr); +bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const Json2PbOptions& options, + std::string* error = nullptr, + size_t* parsed_offset = nullptr); // Use ZeroCopyStreamReader as input instead of std::string. // If you need to parse multiple jsons from IOBuf, you should use this // overload instead of the ZeroCopyInputStream one which bases on this // and recreates a ZeroCopyStreamReader internally that can't be reused // between continuous calls. -bool JsonToProtoMessage(ZeroCopyStreamReader *json, +bool JsonToProtoMessage(ZeroCopyStreamReader* json, google::protobuf::Message* message, const Json2PbOptions& options, std::string* error = nullptr, @@ -83,6 +84,20 @@ bool JsonToProtoMessage(const std::string& json, bool JsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* stream, google::protobuf::Message* message, std::string* error = nullptr); + +// See for details. +using ProtoJson2PbOptions = google::protobuf::util::JsonParseOptions; + +// Convert ProtoJSON formatted `json' to protobuf `message' according to `options'. +// See https://protobuf.dev/programming-guides/json/ for details. +bool ProtoJsonToProtoMessage(google::protobuf::io::ZeroCopyInputStream* json, + google::protobuf::Message* message, + const ProtoJson2PbOptions& options = ProtoJson2PbOptions(), + std::string* error = NULL); +bool ProtoJsonToProtoMessage(const std::string& json, google::protobuf::Message* message, + const ProtoJson2PbOptions& options = ProtoJson2PbOptions(), + std::string* error = NULL); + } // namespace json2pb #endif // BRPC_JSON2PB_JSON_TO_PB_H diff --git a/src/json2pb/pb_to_json.cpp b/src/json2pb/pb_to_json.cpp index b0066dc6dc..9671979cc0 100644 --- a/src/json2pb/pb_to_json.cpp +++ b/src/json2pb/pb_to_json.cpp @@ -22,12 +22,15 @@ #include #include #include +#include +#include "json2pb/zero_copy_stream_writer.h" +#include "json2pb/encode_decode.h" +#include "json2pb/protobuf_map.h" +#include "json2pb/rapidjson.h" +#include "json2pb/pb_to_json.h" +#include "json2pb/protobuf_type_resolver.h" +#include "butil/iobuf.h" #include "butil/base64.h" -#include "zero_copy_stream_writer.h" -#include "encode_decode.h" -#include "protobuf_map.h" -#include "rapidjson.h" -#include "pb_to_json.h" namespace json2pb { Pb2JsonOptions::Pb2JsonOptions() @@ -334,15 +337,43 @@ bool ProtoMessageToJson(const google::protobuf::Message& message, } bool ProtoMessageToJson(const google::protobuf::Message& message, - google::protobuf::io::ZeroCopyOutputStream *stream, + google::protobuf::io::ZeroCopyOutputStream* stream, const Pb2JsonOptions& options, std::string* error) { json2pb::ZeroCopyStreamWriter wrapper(stream); return json2pb::ProtoMessageToJsonStream(message, options, wrapper, error); } bool ProtoMessageToJson(const google::protobuf::Message& message, - google::protobuf::io::ZeroCopyOutputStream *stream, + google::protobuf::io::ZeroCopyOutputStream* stream, std::string* error) { return ProtoMessageToJson(message, stream, Pb2JsonOptions(), error); } + +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + const Pb2ProtoJsonOptions& options, std::string* error) { + butil::IOBuf buf; + butil::IOBufAsZeroCopyOutputStream output_stream(&buf); + if (!message.SerializeToZeroCopyStream(&output_stream)) { + return false; + } + + TypeResolverUniqueptr type_resolver = GetTypeResolver(message); + butil::IOBufAsZeroCopyInputStream input_stream(buf); + auto st = google::protobuf::util::BinaryToJsonStream( + type_resolver.get(), GetTypeUrl(message), &input_stream, json, options); + + bool ok = st.ok(); + if (!ok && NULL != error) { + *error = st.ToString(); + } + return ok; +} + +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, std::string* json, + const Pb2ProtoJsonOptions& options, std::string* error) { + google::protobuf::io::StringOutputStream output_stream(json); + return ProtoMessageToProtoJson(message, &output_stream, options, error); +} + } // namespace json2pb diff --git a/src/json2pb/pb_to_json.h b/src/json2pb/pb_to_json.h index 97057d0fcb..8de635170e 100644 --- a/src/json2pb/pb_to_json.h +++ b/src/json2pb/pb_to_json.h @@ -23,6 +23,7 @@ #include #include #include // ZeroCopyOutputStream +#include namespace json2pb { @@ -79,7 +80,7 @@ bool ProtoMessageToJson(const google::protobuf::Message& message, std::string* error = NULL); // send output to ZeroCopyOutputStream instead of std::string. bool ProtoMessageToJson(const google::protobuf::Message& message, - google::protobuf::io::ZeroCopyOutputStream *json, + google::protobuf::io::ZeroCopyOutputStream* json, const Pb2JsonOptions& options, std::string* error = NULL); @@ -90,6 +91,25 @@ bool ProtoMessageToJson(const google::protobuf::Message& message, bool ProtoMessageToJson(const google::protobuf::Message& message, google::protobuf::io::ZeroCopyOutputStream* json, std::string* error = NULL); + +// See for details. +using Pb2ProtoJsonOptions = google::protobuf::util::JsonOptions; + +#if GOOGLE_PROTOBUF_VERSION >= 5026002 +#define AlwaysPrintPrimitiveFields(options) options.always_print_fields_with_no_presence +#else +#define AlwaysPrintPrimitiveFields(options) options.always_print_primitive_fields +#endif + +// Convert protobuf `messge' to `json' in ProtoJSON format according to `options'. +// See https://protobuf.dev/programming-guides/json/ for details. +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, + google::protobuf::io::ZeroCopyOutputStream* json, + const Pb2ProtoJsonOptions& options = Pb2ProtoJsonOptions(), + std::string* error = NULL); +bool ProtoMessageToProtoJson(const google::protobuf::Message& message, std::string* json, + const Pb2ProtoJsonOptions& options = Pb2ProtoJsonOptions(), + std::string* error = NULL); } // namespace json2pb #endif // BRPC_JSON2PB_PB_TO_JSON_H diff --git a/src/json2pb/protobuf_type_resolver.cpp b/src/json2pb/protobuf_type_resolver.cpp new file mode 100644 index 0000000000..a75974fc99 --- /dev/null +++ b/src/json2pb/protobuf_type_resolver.cpp @@ -0,0 +1,36 @@ +// 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 "json2pb/protobuf_type_resolver.h" + +namespace json2pb { + +using google::protobuf::DescriptorPool; +using google::protobuf::util::TypeResolver; +using google::protobuf::util::NewTypeResolverForDescriptorPool; + +TypeResolverUniqueptr GetTypeResolver(const google::protobuf::Message& message) { + auto pool = message.GetDescriptor()->file()->pool(); + bool is_generated_pool = pool == DescriptorPool::generated_pool(); + TypeResolver* resolver = is_generated_pool + ? butil::get_leaky_singleton() + : NewTypeResolverForDescriptorPool(PROTOBUF_TYPE_URL_PREFIX, pool); + return { resolver, TypeResolverDeleter(is_generated_pool) }; +} + +} // namespace json2pb + diff --git a/src/json2pb/protobuf_type_resolver.h b/src/json2pb/protobuf_type_resolver.h new file mode 100644 index 0000000000..a73a42315e --- /dev/null +++ b/src/json2pb/protobuf_type_resolver.h @@ -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. + +#ifndef BRPC_PROTOBUF_TYPE_RESOLVER_H +#define BRPC_PROTOBUF_TYPE_RESOLVER_H + +#include +#include +#include +#include +#include +#include "butil/string_printf.h" +#include "butil/memory/singleton_on_pthread_once.h" + +namespace json2pb { + +#define PROTOBUF_TYPE_URL_PREFIX "type.googleapis.com" + +inline std::string GetTypeUrl(const google::protobuf::Message& message) { + return butil::string_printf(PROTOBUF_TYPE_URL_PREFIX"/%s", + message.GetDescriptor()->full_name().c_str()); +} + +// unique_ptr deleter for TypeResolver only deletes the object +// when it's not from the generated pool. +class TypeResolverDeleter { +public: + explicit TypeResolverDeleter(bool is_generated_pool) + : _is_generated_pool(is_generated_pool) {} + + void operator()(google::protobuf::util::TypeResolver* resolver) const { + if (!_is_generated_pool) { + delete resolver; + } + } +private: + bool _is_generated_pool; +}; + +using TypeResolverUniqueptr = std::unique_ptr< + google::protobuf::util::TypeResolver, TypeResolverDeleter>; + +TypeResolverUniqueptr GetTypeResolver(const google::protobuf::Message& message); + +} // namespace json2pb + +namespace butil { + +// Customized singleton object creation for google::protobuf::util::TypeResolver. +template<> +inline google::protobuf::util::TypeResolver* +create_leaky_singleton_obj() { + return google::protobuf::util::NewTypeResolverForDescriptorPool( + PROTOBUF_TYPE_URL_PREFIX, google::protobuf::DescriptorPool::generated_pool()); +} + +} // namespace butil + +#endif // BRPC_PROTOBUF_TYPE_RESOLVER_H diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 9817b45f10..05420ae310 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -154,11 +154,12 @@ proto_library( [ "*.proto", ], - exclude = [ - "echo.proto", - ], ), + strip_import_prefix = "/test", visibility = ["//visibility:public"], + deps = [ + "//:brpc_idl_options_proto", + ] ) cc_proto_library( @@ -176,6 +177,13 @@ cc_library( ], ) +cc_library( + name = "gperftools_helper", + hdrs = [ + "gperftools_helper.h", + ], +) + cc_test( name = "butil_test", srcs = TEST_BUTIL_SOURCES + [ @@ -187,11 +195,13 @@ cc_test( deps = [ ":cc_test_proto", ":sstream_workaround", + ":gperftools_helper", "//:brpc", "@com_google_googletest//:gtest", ], ) + cc_test( name = "bvar_test", srcs = glob( @@ -206,6 +216,7 @@ cc_test( copts = COPTS, deps = [ ":sstream_workaround", + ":gperftools_helper", "//:bvar", "@com_google_googletest//:gtest", ], @@ -233,6 +244,24 @@ cc_test( ), copts = COPTS, deps = [ + ":sstream_workaround", + ":gperftools_helper", + "//:brpc", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "brpc_prometheus_test", + srcs = glob( + [ + "brpc_prometheus_*_unittest.cpp", + ], + ), + copts = COPTS, + deps = [ + ":cc_test_proto", ":sstream_workaround", "//:brpc", "@com_google_googletest//:gtest", diff --git a/test/Makefile b/test/Makefile index 874e6928e6..0f1534b713 100644 --- a/test/Makefile +++ b/test/Makefile @@ -19,7 +19,7 @@ NEED_GPERFTOOLS=1 NEED_GTEST=1 include ../config.mk CPPFLAGS+=-DBTHREAD_USE_FAST_PTHREAD_MUTEX -D_GNU_SOURCE -DUSE_SYMBOLIZE -DNO_TCMALLOC -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -DUNIT_TEST -Dprivate=public -Dprotected=public -DBVAR_NOT_LINK_DEFAULT_VARIABLES --include sstream_workaround.h -CXXFLAGS=$(CPPFLAGS) -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-invalid-offsetof -Wno-unused-parameter -fno-omit-frame-pointer -std=c++0x +CXXFLAGS+=$(CPPFLAGS) -pipe -Wall -W -fPIC -fstrict-aliasing -Wno-invalid-offsetof -Wno-unused-parameter -fno-omit-frame-pointer -std=c++0x #required by butil/crc32.cc to boost performance for 10x ifeq ($(shell test $(GCC_VERSION) -ge 40400; echo $$?),0) diff --git a/test/addressbook_map.proto b/test/addressbook_map.proto index d3b154669c..4d18e68c98 100644 --- a/test/addressbook_map.proto +++ b/test/addressbook_map.proto @@ -52,6 +52,16 @@ message AddressComplex { repeated FriendEntry friends = 2; } +message AddressIntMapStd { + required string addr = 1; + map numbers = 2; +} + +message AddressStringMapStd { + required string addr = 1; + map contacts = 2; +} + message haha { repeated int32 a = 1; } \ No newline at end of file diff --git a/test/brpc_adaptive_class_unittest.cpp b/test/brpc_adaptive_class_unittest.cpp index 18128f2a5e..c0d76c0044 100644 --- a/test/brpc_adaptive_class_unittest.cpp +++ b/test/brpc_adaptive_class_unittest.cpp @@ -31,13 +31,13 @@ const std::string kPooled = "PoOled"; TEST(AdaptiveMaxConcurrencyTest, ShouldConvertCorrectly) { brpc::AdaptiveMaxConcurrency amc(0); - EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED(), amc.type()); - EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED(), amc.value()); + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED, amc.type()); + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::UNLIMITED, amc.value()); EXPECT_EQ(0, int(amc)); - EXPECT_TRUE(amc == brpc::AdaptiveMaxConcurrency::UNLIMITED()); + EXPECT_TRUE(amc == brpc::AdaptiveMaxConcurrency::UNLIMITED); amc = 10; - EXPECT_EQ(brpc::AdaptiveMaxConcurrency::CONSTANT(), amc.type()); + EXPECT_EQ(brpc::AdaptiveMaxConcurrency::CONSTANT, amc.type()); EXPECT_EQ("10", amc.value()); EXPECT_EQ(10, int(amc)); EXPECT_EQ(amc, "10"); diff --git a/test/brpc_builtin_service_unittest.cpp b/test/brpc_builtin_service_unittest.cpp index 8ea17a471b..76b99811e3 100644 --- a/test/brpc_builtin_service_unittest.cpp +++ b/test/brpc_builtin_service_unittest.cpp @@ -25,7 +25,7 @@ #include #include #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/time.h" #include "butil/macros.h" #include "brpc/socket.h" @@ -113,7 +113,7 @@ void MyVLogSite() { void CheckContent(const brpc::Controller& cntl, const char* name) { const std::string& content = cntl.response_attachment().to_string(); std::size_t pos = content.find(name); - ASSERT_TRUE(pos != std::string::npos) << "name=" << name; + ASSERT_TRUE(pos != std::string::npos) << "name=" << name << " content=" << content; } void CheckErrorText(const brpc::Controller& cntl, const char* error) { @@ -717,6 +717,7 @@ TEST_F(BuiltinServiceTest, rpcz) { } } +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) TEST_F(BuiltinServiceTest, pprof) { brpc::PProfService service; { @@ -758,6 +759,7 @@ TEST_F(BuiltinServiceTest, pprof) { CheckContent(cntl, "brpc_builtin_service_unittest"); } } +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER TEST_F(BuiltinServiceTest, dir) { brpc::DirService service; @@ -839,8 +841,10 @@ void* dummy_bthread(void*) { #ifdef BRPC_BTHREAD_TRACER +bool g_bthread_trace_start = false; bool g_bthread_trace_stop = false; void* bthread_trace(void*) { + g_bthread_trace_start = true; while (!g_bthread_trace_stop) { bthread_usleep(1000 * 100); } @@ -881,9 +885,13 @@ TEST_F(BuiltinServiceTest, bthreads) { } #ifdef BRPC_BTHREAD_TRACER - { + bool ok = false; + for (int i = 0; i < 10; ++i) { bthread_t th; EXPECT_EQ(0, bthread_start_background(&th, NULL, bthread_trace, NULL)); + while (!g_bthread_trace_start) { + bthread_usleep(1000 * 10); + } ClosureChecker done; brpc::Controller cntl; std::string id_string; @@ -893,9 +901,14 @@ TEST_F(BuiltinServiceTest, bthreads) { service.default_method(&cntl, &req, &res, &done); g_bthread_trace_stop = true; EXPECT_FALSE(cntl.Failed()); - CheckContent(cntl, "stop=0"); - CheckContent(cntl, "bthread_trace"); + const std::string& content = cntl.response_attachment().to_string(); + ok = content.find("stop=0") != std::string::npos && + content.find("bthread_trace") != std::string::npos; + if (ok) { + break; + } } + ASSERT_TRUE(ok); #endif // BRPC_BTHREAD_TRACER } @@ -933,6 +946,7 @@ TEST_F(BuiltinServiceTest, sockets) { } } +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) TEST_F(BuiltinServiceTest, memory) { brpc::MemoryService service; brpc::MemoryRequest req; @@ -950,3 +964,4 @@ TEST_F(BuiltinServiceTest, memory) { CheckContent(cntl, "tcmalloc.pageheap_free_bytes"); CheckContent(cntl, "tcmalloc.pageheap_unmapped_bytes"); } +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER diff --git a/test/brpc_channel_unittest.cpp b/test/brpc_channel_unittest.cpp index 43d0ab7f1f..66d1fbad9b 100644 --- a/test/brpc_channel_unittest.cpp +++ b/test/brpc_channel_unittest.cpp @@ -40,11 +40,7 @@ #include "brpc/selective_channel.h" #include "brpc/socket_map.h" #include "brpc/controller.h" -#if BAZEL_TEST -#include "test/echo.pb.h" -#else #include "echo.pb.h" -#endif // BAZEL_TEST #include "brpc/options.pb.h" namespace brpc { @@ -201,6 +197,10 @@ class ChannelTest : public ::testing::Test{ ChannelTest() : _ep(butil::IP_ANY, 8787) , _close_fd_once(false) { + if (!_dummy.options().rpc_pb_message_factory) { + _dummy._options.rpc_pb_message_factory = new brpc::DefaultRpcPBMessageFactory(); + } + pthread_once(®ister_mock_protocol, register_protocol); const brpc::InputMessageHandler pairs[] = { { brpc::policy::ParseRpcMessage, diff --git a/test/brpc_event_dispatcher_unittest.cpp b/test/brpc_event_dispatcher_unittest.cpp index 185e9f2dc8..5c0aa064d8 100644 --- a/test/brpc_event_dispatcher_unittest.cpp +++ b/test/brpc_event_dispatcher_unittest.cpp @@ -23,7 +23,7 @@ #include #include #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/time.h" #include "butil/macros.h" #include "butil/fd_utility.h" @@ -345,13 +345,11 @@ TEST_F(EventDispatcherTest, dispatch_tasks) { ProfilerStart("event_dispatcher.prof"); butil::Timer tm; tm.start(); - sleep(5); - tm.stop(); ProfilerStop(); LOG(INFO) << "End profiling"; - + size_t client_bytes = 0; size_t server_bytes = 0; for (size_t i = 0; i < NCLIENT; ++i) { diff --git a/test/brpc_h2_unsent_message_unittest.cpp b/test/brpc_h2_unsent_message_unittest.cpp index 79b56f55b4..acb79039ff 100644 --- a/test/brpc_h2_unsent_message_unittest.cpp +++ b/test/brpc_h2_unsent_message_unittest.cpp @@ -25,7 +25,7 @@ #include "butil/atomicops.h" #include "brpc/policy/http_rpc_protocol.h" #include "brpc/policy/http2_rpc_protocol.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); diff --git a/test/brpc_http_message_unittest.cpp b/test/brpc_http_message_unittest.cpp index d6f206cd07..9933a8d1bb 100644 --- a/test/brpc_http_message_unittest.cpp +++ b/test/brpc_http_message_unittest.cpp @@ -224,8 +224,10 @@ TEST(HttpMessageTest, parse_from_iobuf) { "Content-Length: %lu\r\n" "\r\n", content_length); - std::string content; - for (size_t i = 0; i < content_length; ++i) content.push_back('2'); + butil::IOBuf content; + for (size_t i = 0; i < content_length; ++i) { + content.push_back('2'); + } butil::IOBuf request; request.append(header); request.append(content); @@ -233,6 +235,7 @@ TEST(HttpMessageTest, parse_from_iobuf) { brpc::HttpMessage http_message; ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0); ASSERT_TRUE(http_message.Completed()); + ASSERT_EQ(content, http_message.body()); ASSERT_EQ(content, http_message.body().to_string()); ASSERT_EQ("text/plain", http_message.header().content_type()); } diff --git a/test/brpc_http_rpc_protocol_unittest.cpp b/test/brpc_http_rpc_protocol_unittest.cpp index d2dcf972c6..0eca15326a 100644 --- a/test/brpc_http_rpc_protocol_unittest.cpp +++ b/test/brpc_http_rpc_protocol_unittest.cpp @@ -132,7 +132,10 @@ class HttpTest : public ::testing::Test{ // Hack: Regard `_server' as running _server._status = brpc::Server::RUNNING; _server._options.auth = &_auth; - + if (!_server._options.rpc_pb_message_factory) { + _server._options.rpc_pb_message_factory = new brpc::DefaultRpcPBMessageFactory(); + } + EXPECT_EQ(0, pipe(_pipe_fds)); brpc::SocketId id; @@ -186,6 +189,21 @@ class HttpTest : public ::testing::Test{ return msg; } + brpc::policy::HttpContext* MakePostProtoJsonRequestMessage(const std::string& path) { + brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); + msg->header().uri().set_path(path); + msg->header().set_content_type("application/proto-json"); + msg->header().set_method(brpc::HTTP_METHOD_POST); + + test::EchoRequest req; + req.set_message(EXP_REQUEST); + butil::IOBufAsZeroCopyOutputStream req_stream(&msg->body()); + json2pb::Pb2ProtoJsonOptions options; + std::string error; + EXPECT_TRUE(json2pb::ProtoMessageToProtoJson(req, &req_stream, options, &error)) << error; + return msg; + } + brpc::policy::HttpContext* MakePostProtoTextRequestMessage( const std::string& path) { brpc::policy::HttpContext* msg = new brpc::policy::HttpContext(false); @@ -331,7 +349,7 @@ TEST_F(HttpTest, parse_http_address) { TEST_F(HttpTest, verify_request) { { brpc::policy::HttpContext* msg = - MakePostRequestMessage("/EchoService/Echo"); + MakePostRequestMessage("/EchoService/Echo"); VerifyMessage(msg, false); msg->Destroy(); } @@ -347,6 +365,12 @@ TEST_F(HttpTest, verify_request) { VerifyMessage(msg, false); msg->Destroy(); } + { + brpc::policy::HttpContext* msg = + MakePostProtoJsonRequestMessage("/EchoService/Echo"); + VerifyMessage(msg, false); + msg->Destroy(); + } { brpc::policy::HttpContext* msg = MakePostProtoTextRequestMessage("/EchoService/Echo"); @@ -730,15 +754,19 @@ class ReadBody : public brpc::ProgressiveReader, butil::Status _destroying_st; }; +#ifdef BUTIL_USE_ASAN +static const int GENERAL_DELAY_US = 1000000; // 1s +#else static const int GENERAL_DELAY_US = 300000; // 0.3s +#endif TEST_F(HttpTest, read_long_body_progressively) { + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); butil::intrusive_ptr reader; { const int port = 8923; brpc::Server server; - DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, - std::numeric_limits::max()); EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); EXPECT_EQ(0, server.Start(port, NULL)); { @@ -820,12 +848,12 @@ TEST_F(HttpTest, read_short_body_progressively) { } TEST_F(HttpTest, read_progressively_after_cntl_destroys) { + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); butil::intrusive_ptr reader; { const int port = 8923; brpc::Server server; - DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, - std::numeric_limits::max()); EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); EXPECT_EQ(0, server.Start(port, NULL)); { @@ -867,11 +895,11 @@ TEST_F(HttpTest, read_progressively_after_cntl_destroys) { TEST_F(HttpTest, read_progressively_after_long_delay) { butil::intrusive_ptr reader; + DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, + std::numeric_limits::max()); { const int port = 8923; brpc::Server server; - DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, - std::numeric_limits::max()); EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); EXPECT_EQ(0, server.Start(port, NULL)); { @@ -916,10 +944,10 @@ TEST_F(HttpTest, read_progressively_after_long_delay) { } TEST_F(HttpTest, skip_progressive_reading) { - const int port = 8923; - brpc::Server server; DownloadServiceImpl svc(DONE_BEFORE_CREATE_PA, std::numeric_limits::max()); + const int port = 8923; + brpc::Server server; EXPECT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE)); EXPECT_EQ(0, server.Start(port, NULL)); brpc::Channel channel; @@ -1026,6 +1054,7 @@ TEST_F(HttpTest, broken_socket_stops_progressive_reading) { ASSERT_EQ(ECONNRESET, reader->destroying_status().error_code()); } +#ifndef BUTIL_USE_ASAN static const std::string TEST_PROGRESSIVE_HEADER = "Progressive"; static const std::string TEST_PROGRESSIVE_HEADER_VAL = "Progressive-val"; @@ -1141,6 +1170,8 @@ TEST_F(HttpTest, server_end_read_short_body_progressively) { ASSERT_FALSE(cntl.Failed()); } +// Fixme!!! Server progressive reader has a heap-use-after-free bug detected by ASan. +// For details, see https://github.com/apache/brpc/issues/2145#issuecomment-2329413363 TEST_F(HttpTest, server_end_read_failed) { const int port = 8923; brpc::ServiceOptions opt; @@ -1177,6 +1208,7 @@ TEST_F(HttpTest, server_end_read_failed) { channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); ASSERT_TRUE(cntl.Failed()); } +#endif // BUTIL_USE_ASAN TEST_F(HttpTest, http2_sanity) { const int port = 8923; @@ -1666,7 +1698,6 @@ TEST_F(HttpTest, spring_protobuf_content_type) { brpc::Controller cntl2; test::EchoService_Stub stub(&channel); - req.set_message(EXP_REQUEST); res.Clear(); cntl2.http_request().set_content_type("application/x-protobuf"); stub.Echo(&cntl2, &req, &res, nullptr); @@ -1759,7 +1790,7 @@ TEST_F(HttpTest, dump_http_request) { brpc::g_rpc_dump_sl.sampling_range = 0; } -TEST_F(HttpTest, spring_protobuf_text_content_type) { +TEST_F(HttpTest, proto_text_content_type) { const int port = 8923; brpc::Server server; EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); @@ -1784,6 +1815,55 @@ TEST_F(HttpTest, spring_protobuf_text_content_type) { ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( cntl.response_attachment().to_string(), &res)); ASSERT_EQ(EXP_RESPONSE, res.message()); + + test::EchoService_Stub stub(&channel); + cntl.Reset(); + cntl.http_request().set_content_type("application/proto-text"); + res.Clear(); + stub.Echo(&cntl, &req, &res, NULL); + ASSERT_FALSE(cntl.Failed()); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ("application/proto-text", cntl.http_response().content_type()); +} + +TEST_F(HttpTest, proto_json_content_type) { + const int port = 8923; + brpc::Server server; + EXPECT_EQ(0, server.AddService(&_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + EXPECT_EQ(0, server.Start(port, nullptr)); + + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = "http"; + ASSERT_EQ(0, channel.Init(butil::EndPoint(butil::my_ip(), port), &options)); + + brpc::Controller cntl; + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + cntl.http_request().set_method(brpc::HTTP_METHOD_POST); + cntl.http_request().uri() = "/EchoService/Echo"; + cntl.http_request().set_content_type("application/proto-json"); + json2pb::Pb2ProtoJsonOptions json_options; + butil::IOBufAsZeroCopyOutputStream output_stream(&cntl.request_attachment()); + ASSERT_TRUE(json2pb::ProtoMessageToProtoJson(req, &output_stream, json_options)); + channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ("application/proto-json", cntl.http_response().content_type()); + json2pb::ProtoJson2PbOptions parse_options; + parse_options.ignore_unknown_fields = true; + butil::IOBufAsZeroCopyInputStream input_stream(cntl.response_attachment()); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream, &res, parse_options)); + ASSERT_EQ(EXP_RESPONSE, res.message()); + + test::EchoService_Stub stub(&channel); + cntl.Reset(); + cntl.http_request().set_content_type("application/proto-json"); + res.Clear(); + stub.Echo(&cntl, &req, &res, nullptr); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + ASSERT_EQ(EXP_RESPONSE, res.message()); + ASSERT_EQ("application/proto-json", cntl.http_response().content_type()); } class HttpServiceImpl : public ::test::HttpService { diff --git a/test/brpc_hulu_pbrpc_protocol_unittest.cpp b/test/brpc_hulu_pbrpc_protocol_unittest.cpp index 7c971dc2a8..d00b9d4528 100644 --- a/test/brpc_hulu_pbrpc_protocol_unittest.cpp +++ b/test/brpc_hulu_pbrpc_protocol_unittest.cpp @@ -27,7 +27,6 @@ #include #include "butil/time.h" #include "butil/macros.h" -#include "butil/gperftools_profiler.h" #include "brpc/socket.h" #include "brpc/acceptor.h" #include "brpc/server.h" diff --git a/test/brpc_input_messenger_unittest.cpp b/test/brpc_input_messenger_unittest.cpp index 00b14ed41e..812c499a57 100644 --- a/test/brpc_input_messenger_unittest.cpp +++ b/test/brpc_input_messenger_unittest.cpp @@ -21,9 +21,9 @@ #include #include -#include // +#include #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/time.h" #include "butil/macros.h" #include "butil/fd_utility.h" @@ -180,7 +180,8 @@ TEST_F(MessengerTest, dispatch_tasks) { } sleep(1); - + + LOG(INFO) << "Begin to profile... (5 seconds)"; ProfilerStart("input_messenger.prof"); diff --git a/test/brpc_load_balancer_unittest.cpp b/test/brpc_load_balancer_unittest.cpp index 509456e71c..eac8d347b4 100644 --- a/test/brpc_load_balancer_unittest.cpp +++ b/test/brpc_load_balancer_unittest.cpp @@ -23,7 +23,8 @@ #include #include #include "bthread/bthread.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" +#include "butil/compiler_specific.h" #include "butil/containers/doubly_buffered_data.h" #include "brpc/describable.h" #include "brpc/socket.h" @@ -80,6 +81,16 @@ bool AddN(Foo& f, int n) { return true; } +void read_cb(const Foo& f) { + ASSERT_EQ(0, f.x); +} + +struct CallableObj { + void operator()(const Foo& f) { + ASSERT_EQ(0, f.x); + } +}; + template void test_doubly_buffered_data() { // test doubly_buffered_data TLS limits @@ -97,6 +108,15 @@ void test_doubly_buffered_data() { ASSERT_EQ(0, d.Read(&ptr)); ASSERT_EQ(0, ptr->x); } + { + ASSERT_EQ(0, d.Read([](const Foo& f) { + ASSERT_EQ(0, f.x); + })); + ASSERT_EQ(0, d.Read(read_cb)); + ASSERT_EQ(0, d.Read(CallableObj())); + CallableObj co; + ASSERT_EQ(0, d.Read(co)); + } { typename DBD::ScopedPtr ptr; ASSERT_EQ(0, d.Read(&ptr)); @@ -104,10 +124,14 @@ void test_doubly_buffered_data() { } d.Modify(AddN, 10); + d.Modify([](Foo& f, int n) -> size_t { + f.x += n; + return 1; + }, 10); { typename DBD::ScopedPtr ptr; ASSERT_EQ(0, d.Read(&ptr)); - ASSERT_EQ(10, ptr->x); + ASSERT_EQ(20, ptr->x); } } @@ -1132,6 +1156,7 @@ TEST_F(LoadBalancerTest, revived_from_all_failed_sanity) { } } +#ifndef BUTIL_USE_ASAN class EchoServiceImpl : public test::EchoService { public: EchoServiceImpl() @@ -1228,14 +1253,14 @@ TEST_F(LoadBalancerTest, revived_from_all_failed_intergrated) { } butil::EndPoint point(butil::IP_ANY, 7777); - brpc::Server server; EchoServiceImpl service; + brpc::Server server; ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); ASSERT_EQ(0, server.Start(point, NULL)); butil::EndPoint point2(butil::IP_ANY, 7778); - brpc::Server server2; EchoServiceImpl service2; + brpc::Server server2; ASSERT_EQ(0, server2.AddService(&service2, brpc::SERVER_DOESNT_OWN_SERVICE)); ASSERT_EQ(0, server2.Start(point2, NULL)); @@ -1264,6 +1289,7 @@ TEST_F(LoadBalancerTest, revived_from_all_failed_intergrated) { bthread_usleep(500000 /* sleep longer than timeout of channel */); ASSERT_EQ(0, num_failed.load(butil::memory_order_relaxed)); } +#endif // BUTIL_USE_ASAN TEST_F(LoadBalancerTest, la_selection_too_long) { brpc::GlobalInitializeOrDie(); diff --git a/test/brpc_prometheus_metrics_unittest.cpp b/test/brpc_prometheus_metrics_unittest.cpp index 064c14f70e..471fd40a3c 100644 --- a/test/brpc_prometheus_metrics_unittest.cpp +++ b/test/brpc_prometheus_metrics_unittest.cpp @@ -23,6 +23,7 @@ #include "brpc/controller.h" #include "butil/strings/string_piece.h" #include "echo.pb.h" +#include "bvar/multi_dimension.h" int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); @@ -45,7 +46,13 @@ enum STATE { HELP = 0, TYPE, GAUGE, - SUMMARY + SUMMARY, + COUNTER, + // When meets a line with a gauge/counter with labels, we have no + // idea the next line is a new HELP or the same gauge/counter just + // with different labels + HELP_OR_GAUGE, + HELP_OR_COUNTER, }; TEST(PrometheusMetrics, sanity) { @@ -54,10 +61,22 @@ TEST(PrometheusMetrics, sanity) { ASSERT_EQ(0, server.AddService(&echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); ASSERT_EQ(0, server.Start("127.0.0.1:8614", NULL)); - brpc::Server server2; - DummyEchoServiceImpl echo_svc2; - ASSERT_EQ(0, server2.AddService(&echo_svc2, brpc::SERVER_DOESNT_OWN_SERVICE)); - ASSERT_EQ(0, server2.Start("127.0.0.1:8615", NULL)); + const std::list labels = {"label1", "label2"}; + bvar::MultiDimension > my_madder("madder", labels); + bvar::Adder* my_adder1 = my_madder.get_stats({"val1", "val2"}); + ASSERT_TRUE(my_adder1); + *my_adder1 << 1 << 2; + bvar::Adder* my_adder2 = my_madder.get_stats({"val2", "val3"}); + ASSERT_TRUE(my_adder1); + *my_adder2 << 3 << 4; + + bvar::MultiDimension my_mlat("mlat", labels); + bvar::LatencyRecorder* my_lat1 = my_mlat.get_stats({"val1", "val2"}); + ASSERT_TRUE(my_lat1); + *my_lat1 << 1 << 2; + bvar::LatencyRecorder* my_lat2 = my_mlat.get_stats({"val2", "val3"}); + ASSERT_TRUE(my_lat2); + *my_lat2 << 3 << 4; brpc::Channel channel; brpc::ChannelOptions channel_opts; @@ -68,19 +87,22 @@ TEST(PrometheusMetrics, sanity) { channel.CallMethod(NULL, &cntl, NULL, NULL, NULL); ASSERT_FALSE(cntl.Failed()); std::string res = cntl.response_attachment().to_string(); - + LOG(INFO) << "output:\n" << res; size_t start_pos = 0; size_t end_pos = 0; + size_t label_start = 0; STATE state = HELP; char name_help[128]; char name_type[128]; char type[16]; int matched = 0; - int gauge_num = 0; + int num = 0; bool summary_sum_gathered = false; bool summary_count_gathered = false; bool has_ever_summary = false; bool has_ever_gauge = false; + bool has_ever_counter = false; // brought in by mvar latency recorder + std::unordered_set metric_name_set; while ((end_pos = res.find('\n', start_pos)) != butil::StringPiece::npos) { res[end_pos] = '\0'; // safe; @@ -98,21 +120,52 @@ TEST(PrometheusMetrics, sanity) { state = GAUGE; } else if (strcmp(type, "summary") == 0) { state = SUMMARY; + } else if (strcmp(type, "counter") == 0) { + state = COUNTER; } else { - ASSERT_TRUE(false); + ASSERT_TRUE(false) << "invalid type: " << type; } + ASSERT_EQ(0, metric_name_set.count(name_type)) << "second TYPE line for metric name " + << name_type; + metric_name_set.insert(name_help); break; + case HELP_OR_GAUGE: + case HELP_OR_COUNTER: + matched = sscanf(res.data() + start_pos, "# HELP %s", name_help); + // Try to figure out current line is a new COMMENT or not + if (matched == 1) { + state = HELP; + } else { + state = state == HELP_OR_GAUGE ? GAUGE : COUNTER; + } + res[end_pos] = '\n'; // revert to original + continue; // do not jump to next line case GAUGE: - matched = sscanf(res.data() + start_pos, "%s %d", name_type, &gauge_num); + case COUNTER: + matched = sscanf(res.data() + start_pos, "%s %d", name_type, &num); ASSERT_EQ(2, matched); - ASSERT_STREQ(name_type, name_help); - state = HELP; - has_ever_gauge = true; + if (state == GAUGE) { + has_ever_gauge = true; + } + if (state == COUNTER) { + has_ever_counter = true; + } + label_start = butil::StringPiece(name_type).find("{"); + if (label_start == strlen(name_help)) { // mvar + ASSERT_EQ(name_type[strlen(name_type) - 1], '}'); + ASSERT_TRUE(strncmp(name_type, name_help, strlen(name_help)) == 0); + state = state == GAUGE ? HELP_OR_GAUGE : HELP_OR_COUNTER; + } else if (label_start == butil::StringPiece::npos) { // var + ASSERT_STREQ(name_type, name_help); + state = HELP; + } else { // invalid + ASSERT_TRUE(false); + } break; case SUMMARY: if (butil::StringPiece(res.data() + start_pos, end_pos - start_pos).find("quantile=") == butil::StringPiece::npos) { - matched = sscanf(res.data() + start_pos, "%s %d", name_type, &gauge_num); + matched = sscanf(res.data() + start_pos, "%s %d", name_type, &num); ASSERT_EQ(2, matched); ASSERT_TRUE(strncmp(name_type, name_help, strlen(name_help)) == 0); if (butil::StringPiece(name_type).ends_with("_sum")) { @@ -138,7 +191,7 @@ TEST(PrometheusMetrics, sanity) { } start_pos = end_pos + 1; } - ASSERT_TRUE(has_ever_gauge && has_ever_summary); + ASSERT_TRUE(has_ever_gauge && has_ever_summary && has_ever_counter); ASSERT_EQ(0, server.Stop(0)); ASSERT_EQ(0, server.Join()); } diff --git a/test/brpc_protobuf_json_unittest.cpp b/test/brpc_protobuf_json_unittest.cpp index 3565772e1d..e8435d2cf3 100644 --- a/test/brpc_protobuf_json_unittest.cpp +++ b/test/brpc_protobuf_json_unittest.cpp @@ -26,7 +26,7 @@ #include "butil/strings/string_util.h" #include "butil/third_party/rapidjson/rapidjson.h" #include "butil/time.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "json2pb/pb_to_json.h" #include "json2pb/json_to_pb.h" #include "json2pb/encode_decode.h" @@ -505,8 +505,8 @@ TEST_F(ProtobufJsonTest, json_to_pb_perf_case) { printf("----------test json to pb performance------------\n\n"); - std::string error; - + std::string error; + ProfilerStart("json_to_pb_perf.prof"); butil::Timer timer; bool res; @@ -542,8 +542,8 @@ TEST_F(ProtobufJsonTest, json_to_pb_encode_decode_perf_case) { \"welcome\", \"enum--type\":1},\"uid*\":\"welcome\"}], \ \"judge\":false, \"spur\":2, \"data:array\":[]}"; printf("----------test json to pb encode/decode performance------------\n\n"); - std::string error; - + std::string error; + ProfilerStart("json_to_pb_encode_decode_perf.prof"); butil::Timer timer; bool res; @@ -593,7 +593,7 @@ TEST_F(ProtobufJsonTest, json_to_pb_complex_perf_case) { json2pb::Json2PbOptions options; options.base64_to_bytes = false; ProfilerStart("json_to_pb_complex_perf.prof"); - for (int i = 0; i < times; i++) { + for (int i = 0; i < times; i++) { gss::message::gss_us_res_t data; butil::IOBufAsZeroCopyInputStream stream(buf); timer.start(); @@ -625,7 +625,7 @@ TEST_F(ProtobufJsonTest, json_to_pb_to_string_complex_perf_case) { json2pb::Json2PbOptions options; options.base64_to_bytes = false; ProfilerStart("json_to_pb_to_string_complex_perf.prof"); - for (int i = 0; i < times; i++) { + for (int i = 0; i < times; i++) { gss::message::gss_us_res_t data; timer.start(); res = json2pb::JsonToProtoMessage(info3, &data, options, &error); @@ -1292,6 +1292,7 @@ TEST_F(ProtobufJsonTest, pb_to_json_perf_case) { printf("text:%s\n", text.data()); printf("----------test pb to json performance------------\n\n"); + ProfilerStart("pb_to_json_perf.prof"); butil::Timer timer; bool res; @@ -1352,6 +1353,7 @@ TEST_F(ProtobufJsonTest, pb_to_json_encode_decode_perf_case) { printf("----------test pb to json encode decode performance------------\n\n"); ProfilerStart("pb_to_json_encode_decode_perf.prof"); + butil::Timer timer; bool res; float avg_time1 = 0; @@ -1401,7 +1403,7 @@ TEST_F(ProtobufJsonTest, pb_to_json_complex_perf_case) { res = JsonToProtoMessage(info3, &data, option, &error); ASSERT_TRUE(res) << error; ProfilerStart("pb_to_json_complex_perf.prof"); - for (int i = 0; i < times; i++) { + for (int i = 0; i < times; i++) { std::string error1; timer.start(); butil::IOBuf buf; @@ -1437,7 +1439,7 @@ TEST_F(ProtobufJsonTest, pb_to_json_to_string_complex_perf_case) { res = JsonToProtoMessage(info3, &data, option, &error); ASSERT_TRUE(res); ProfilerStart("pb_to_json_to_string_complex_perf.prof"); - for (int i = 0; i < times; i++) { + for (int i = 0; i < times; i++) { std::string info4; std::string error1; timer.start(); @@ -1638,4 +1640,107 @@ TEST_F(ProtobufJsonTest, parse_multiple_json_error) { ASSERT_EQ(47ul, offset); } +TEST_F(ProtobufJsonTest, proto_json_to_pb) { + std::string error; + json2pb::ProtoJson2PbOptions options; + + std::string json1 = R"({"addr":"baidu.com",)" + R"("numbers":[{"key":"tel","value":123456},{"key":"cell","value":654321}]})"; + AddressIntMapStd aims; + ASSERT_FALSE(json2pb::ProtoJsonToProtoMessage(json1, &aims, options, &error)); + LOG(INFO) << "Fail to ProtoJsonToProtoMessage: " << error; + + error.clear(); + butil::IOBuf json_buf1; + json_buf1.append(json1); + butil::IOBufAsZeroCopyInputStream input_stream1(json_buf1); + ASSERT_FALSE(json2pb::ProtoJsonToProtoMessage(&input_stream1, &aims, options, &error)); + LOG(INFO) << "Fail to ProtoJsonToProtoMessage: " << error; + error.clear(); + + std::string json2 = R"({"addr":"baidu.com",)" + R"("numbers":{"tel":123456,"cell":654321}})"; + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(json2, &aims, options, &error)) << error; + ASSERT_TRUE(aims.has_addr()); + ASSERT_EQ(aims.addr(), "baidu.com"); + ASSERT_EQ(aims.numbers_size(), 2); + ASSERT_EQ(aims.numbers().at("tel"), 123456); + ASSERT_EQ(aims.numbers().at("cell"), 654321); + + aims.Clear(); + butil::IOBuf json_buf2; + json_buf2.append(json2); + butil::IOBufAsZeroCopyInputStream input_stream2(json_buf2); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream2, &aims, options, &error)) << error; + ASSERT_TRUE(aims.has_addr()); + ASSERT_EQ(aims.addr(), "baidu.com"); + ASSERT_EQ(aims.numbers_size(), 2); + ASSERT_EQ(aims.numbers().at("tel"), 123456); + ASSERT_EQ(aims.numbers().at("cell"), 654321); + + std::string json3 = R"({"addr":"baidu.com",)" + R"("contacts":{"email":"frank@apache.org","office":"Shanghai"}})"; + AddressStringMapStd asms; + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(json3, &asms, options, &error)) << error; + ASSERT_TRUE(asms.has_addr()); + ASSERT_EQ(asms.addr(), "baidu.com"); + ASSERT_EQ(asms.contacts().size(), 2); + ASSERT_EQ(asms.contacts().at("email"), "frank@apache.org"); + ASSERT_EQ(asms.contacts().at("office"), "Shanghai"); + + asms.Clear(); + butil::IOBuf json_buf3; + json_buf3.append(json3); + butil::IOBufAsZeroCopyInputStream input_stream3(json_buf3); + ASSERT_TRUE(json2pb::ProtoJsonToProtoMessage(&input_stream3, &asms, options, &error)) << error; + ASSERT_TRUE(asms.has_addr()); + ASSERT_EQ(asms.addr(), "baidu.com"); + ASSERT_EQ(asms.contacts().size(), 2); + ASSERT_EQ(asms.contacts().at("email"), "frank@apache.org"); + ASSERT_EQ(asms.contacts().at("office"), "Shanghai"); +} + +TEST_F(ProtobufJsonTest, pb_to_proto_json) { + std::string error; + json2pb::Pb2ProtoJsonOptions options; + + AddressIntMapStd aims; + aims.set_addr("baidu.com"); + (*aims.mutable_numbers())["tel"] = 123456; + (*aims.mutable_numbers())["cell"] = 654321; + std::string json1; + ASSERT_TRUE(json2pb::ProtoMessageToJson(aims, &json1)) << error; + ASSERT_NE(json1.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json1.find(R"("cell":654321)"), std::string::npos); + ASSERT_NE(json1.find(R"("tel":123456)"), std::string::npos); + + butil::IOBuf json_buf1; + json_buf1.append(json1); + butil::IOBufAsZeroCopyOutputStream output_stream1(&json_buf1); + ASSERT_TRUE(json2pb::ProtoMessageToJson(aims, &output_stream1, &error)) << error; + json1 = json_buf1.to_string(); + ASSERT_NE(json1.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json1.find(R"("cell":654321)"), std::string::npos); + ASSERT_NE(json1.find(R"("tel":123456)"), std::string::npos); + + AddressStringMapStd asms; + asms.set_addr("baidu.com"); + (*asms.mutable_contacts())["email"] = "frank@apache.org"; + (*asms.mutable_contacts())["office"] = "Shanghai"; + std::string json2; + ASSERT_TRUE(json2pb::ProtoMessageToJson(asms, &json2)) << error; + ASSERT_NE(json2.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json2.find(R"("email":"frank@apache.org")"), std::string::npos); + ASSERT_NE(json2.find(R"("office":"Shanghai")"), std::string::npos); + + butil::IOBuf json_buf2; + json_buf2.append(json2); + butil::IOBufAsZeroCopyOutputStream output_stream2(&json_buf2); + ASSERT_TRUE(json2pb::ProtoMessageToJson(asms, &output_stream2, &error)) << error; + json2 = json_buf2.to_string(); + ASSERT_NE(json2.find(R"("addr":"baidu.com")"), std::string::npos); + ASSERT_NE(json2.find(R"("email":"frank@apache.org")"), std::string::npos); + ASSERT_NE(json2.find(R"("office":"Shanghai")"), std::string::npos); +} + } // namespace diff --git a/test/brpc_redis_unittest.cpp b/test/brpc_redis_unittest.cpp index 573ab2ed5b..7a48d19a85 100644 --- a/test/brpc_redis_unittest.cpp +++ b/test/brpc_redis_unittest.cpp @@ -811,10 +811,12 @@ butil::Mutex s_mutex; std::unordered_map m; std::unordered_map int_map; + class RedisServiceImpl : public brpc::RedisService { public: - RedisServiceImpl() - : _batch_count(0) {} + RedisServiceImpl(std::string password) + : _batch_count(0) + , _password(std::move(password)) {} brpc::RedisCommandHandlerResult OnBatched(const std::vector& args, brpc::RedisReply* output, bool flush_batched) { @@ -864,18 +866,69 @@ class RedisServiceImpl : public brpc::RedisService { std::vector > _batched_command; int _batch_count; + std::string _password; }; +class AuthSession : public brpc::Destroyable { +public: + explicit AuthSession(std::string password) + : _password(std::move(password)) {} + + void Destroy() override { + delete this; + } + + const std::string _password; +}; + +class AuthCommandHandler : public brpc::RedisCommandHandler { +public: + AuthCommandHandler(RedisServiceImpl* rs) + : _rs(rs) {} + + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, + brpc::RedisReply* output, + bool flush_batched) { + if (args.size() < 1) { + output->SetError("ERR wrong number of arguments for 'AUTH' command"); + return brpc::REDIS_CMD_HANDLED; + } + const std::string password(args[1].data(), args[1].size()); + if (_rs->_password != password) { + output->SetError("ERR invalid username/password"); + return brpc::REDIS_CMD_HANDLED; + } + auto auth_session = new AuthSession(password); + ctx->reset_session(auth_session); + output->SetStatus("OK"); + return brpc::REDIS_CMD_HANDLED; + } + +private: + RedisServiceImpl* _rs; +}; + class SetCommandHandler : public brpc::RedisCommandHandler { public: SetCommandHandler(RedisServiceImpl* rs, bool batch_process = false) : _rs(rs) , _batch_process(batch_process) {} - brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, brpc::RedisReply* output, bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (!session || (session->_password != _rs->_password)) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } if (args.size() < 3) { output->SetError("ERR wrong number of arguments for 'set' command"); return brpc::REDIS_CMD_HANDLED; @@ -898,15 +951,26 @@ class SetCommandHandler : public brpc::RedisCommandHandler { bool _batch_process; }; + class GetCommandHandler : public brpc::RedisCommandHandler { public: GetCommandHandler(RedisServiceImpl* rs, bool batch_process = false) : _rs(rs) , _batch_process(batch_process) {} - brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, brpc::RedisReply* output, bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (session->_password != _rs->_password) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } if (args.size() < 2) { output->SetError("ERR wrong number of arguments for 'get' command"); return brpc::REDIS_CMD_HANDLED; @@ -935,11 +999,22 @@ class GetCommandHandler : public brpc::RedisCommandHandler { class IncrCommandHandler : public brpc::RedisCommandHandler { public: - IncrCommandHandler() {} + IncrCommandHandler(RedisServiceImpl* rs) + : _rs(rs) {} - brpc::RedisCommandHandlerResult Run(const std::vector& args, + brpc::RedisCommandHandlerResult Run(brpc::RedisConnContext* ctx, + const std::vector& args, brpc::RedisReply* output, bool flush_batched) { + if (!ctx->session) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } + AuthSession* session = static_cast(ctx->session); + if (session->_password != _rs->_password) { + output->SetError("ERR no auth"); + return brpc::REDIS_CMD_HANDLED; + } if (args.size() < 2) { output->SetError("ERR wrong number of arguments for 'incr' command"); return brpc::REDIS_CMD_HANDLED; @@ -951,24 +1026,31 @@ class IncrCommandHandler : public brpc::RedisCommandHandler { output->SetInteger(value); return brpc::REDIS_CMD_HANDLED; } + +private: + RedisServiceImpl* _rs; }; TEST_F(RedisTest, server_sanity) { + std::string password = GeneratePassword(); brpc::Server server; brpc::ServerOptions server_options; - RedisServiceImpl* rsimpl = new RedisServiceImpl; + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); GetCommandHandler *gh = new GetCommandHandler(rsimpl); SetCommandHandler *sh = new SetCommandHandler(rsimpl); - IncrCommandHandler *ih = new IncrCommandHandler; + AuthCommandHandler *ah = new AuthCommandHandler(rsimpl); + IncrCommandHandler *ih = new IncrCommandHandler(rsimpl); rsimpl->AddCommandHandler("get", gh); rsimpl->AddCommandHandler("set", sh); rsimpl->AddCommandHandler("incr", ih); + rsimpl->AddCommandHandler("auth", ah); server_options.redis_service = rsimpl; brpc::PortRange pr(8081, 8900); ASSERT_EQ(0, server.Start("127.0.0.1", pr, &server_options)); brpc::ChannelOptions options; options.protocol = brpc::PROTOCOL_REDIS; + options.auth = new brpc::policy::RedisAuthenticator(password); brpc::Channel channel; ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); @@ -1029,7 +1111,6 @@ TEST_F(RedisTest, server_sanity) { void* incr_thread(void* arg) { brpc::Channel* c = static_cast(arg); - for (int i = 0; i < 5000; ++i) { brpc::RedisRequest request; brpc::RedisResponse response; @@ -1038,18 +1119,21 @@ void* incr_thread(void* arg) { c->CallMethod(NULL, &cntl, &request, &response, NULL); EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); EXPECT_EQ(1, response.reply_size()); - EXPECT_TRUE(response.reply(0).is_integer()); + EXPECT_TRUE(response.reply(0).is_integer()) << response.reply(0); } return NULL; } TEST_F(RedisTest, server_concurrency) { + std::string password = GeneratePassword(); int N = 10; brpc::Server server; brpc::ServerOptions server_options; - RedisServiceImpl* rsimpl = new RedisServiceImpl; - IncrCommandHandler *ih = new IncrCommandHandler; + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + AuthCommandHandler *ah = new AuthCommandHandler(rsimpl); + IncrCommandHandler *ih = new IncrCommandHandler(rsimpl); rsimpl->AddCommandHandler("incr", ih); + rsimpl->AddCommandHandler("auth", ah); server_options.redis_service = rsimpl; brpc::PortRange pr(8081, 8900); ASSERT_EQ(0, server.Start("0.0.0.0", pr, &server_options)); @@ -1057,6 +1141,7 @@ TEST_F(RedisTest, server_concurrency) { brpc::ChannelOptions options; options.protocol = brpc::PROTOCOL_REDIS; options.connection_type = "pooled"; + options.auth = new brpc::policy::RedisAuthenticator(password); std::vector bths; std::vector channels; for (int i = 0; i < N; ++i) { @@ -1127,12 +1212,14 @@ class MultiCommandHandler : public brpc::RedisCommandHandler { }; TEST_F(RedisTest, server_command_continue) { + std::string password = GeneratePassword(); brpc::Server server; brpc::ServerOptions server_options; - RedisServiceImpl* rsimpl = new RedisServiceImpl; + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); + rsimpl->AddCommandHandler("auth", new AuthCommandHandler(rsimpl)); rsimpl->AddCommandHandler("get", new GetCommandHandler(rsimpl)); rsimpl->AddCommandHandler("set", new SetCommandHandler(rsimpl)); - rsimpl->AddCommandHandler("incr", new IncrCommandHandler); + rsimpl->AddCommandHandler("incr", new IncrCommandHandler(rsimpl)); rsimpl->AddCommandHandler("multi", new MultiCommandHandler); server_options.redis_service = rsimpl; brpc::PortRange pr(8081, 8900); @@ -1140,9 +1227,9 @@ TEST_F(RedisTest, server_command_continue) { brpc::ChannelOptions options; options.protocol = brpc::PROTOCOL_REDIS; + options.auth = new brpc::policy::RedisAuthenticator(password); brpc::Channel channel; ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); - { brpc::RedisRequest request; brpc::RedisResponse response; @@ -1202,11 +1289,14 @@ TEST_F(RedisTest, server_command_continue) { } TEST_F(RedisTest, server_handle_pipeline) { + std::string password = GeneratePassword(); brpc::Server server; brpc::ServerOptions server_options; - RedisServiceImpl* rsimpl = new RedisServiceImpl; + RedisServiceImpl* rsimpl = new RedisServiceImpl(password); GetCommandHandler* getch = new GetCommandHandler(rsimpl, true); SetCommandHandler* setch = new SetCommandHandler(rsimpl, true); + AuthCommandHandler* authch = new AuthCommandHandler(rsimpl); + rsimpl->AddCommandHandler("auth", authch); rsimpl->AddCommandHandler("get", getch); rsimpl->AddCommandHandler("set", setch); rsimpl->AddCommandHandler("multi", new MultiCommandHandler); @@ -1216,6 +1306,7 @@ TEST_F(RedisTest, server_handle_pipeline) { brpc::ChannelOptions options; options.protocol = brpc::PROTOCOL_REDIS; + options.auth = new brpc::policy::RedisAuthenticator(password); brpc::Channel channel; ASSERT_EQ(0, channel.Init("127.0.0.1", server.listen_address().port, &options)); diff --git a/test/brpc_server_unittest.cpp b/test/brpc_server_unittest.cpp index 676f16626d..a51b931781 100644 --- a/test/brpc_server_unittest.cpp +++ b/test/brpc_server_unittest.cpp @@ -70,6 +70,13 @@ DECLARE_bool(enable_dir_service); namespace policy { DECLARE_bool(use_http_error_code); + +extern bool SerializeRpcMessage(const google::protobuf::Message& serializer, Controller& cntl, + ContentType content_type, CompressType compress_type, + butil::IOBuf* buf); +extern bool DeserializeRpcMessage(const butil::IOBuf& deserializer, Controller& cntl, + ContentType content_type, CompressType compress_type, + google::protobuf::Message* message); } } @@ -1693,53 +1700,119 @@ class BaiduMasterServiceImpl : public brpc::BaiduMasterService { cntl->sampled_request()->meta.service_name()); ASSERT_TRUE(cntl->sampled_request()->meta.has_method_name()); ASSERT_EQ("Echo", cntl->sampled_request()->meta.method_name()); + brpc::ContentType content_type = cntl->request_content_type(); + brpc::CompressType compress_type = cntl->request_compress_type(); + test::EchoRequest echo_request; test::EchoResponse echo_response; - brpc::CompressType type = cntl->request_compress_type(); - ASSERT_TRUE(brpc::ParseFromCompressedData( - request->serialized_data(), &echo_request, type)); + ASSERT_TRUE(brpc::policy::DeserializeRpcMessage( + request->serialized_data(), *cntl, content_type, compress_type, &echo_request)); ASSERT_EQ(EXP_REQUEST, echo_request.message()); ASSERT_EQ(EXP_REQUEST, cntl->request_attachment().to_string()); - echo_response.set_message(EXP_RESPONSE); - butil::IOBuf compressed_data; - ASSERT_TRUE(brpc::SerializeAsCompressedData( - echo_response, &response->serialized_data(), type)); - cntl->set_response_compress_type(type); + content_type = (brpc::ContentType)_content_type_index; + compress_type = (brpc::CompressType)_compress_type_index; + ++_compress_type_index; + if (_compress_type_index == brpc::COMPRESS_TYPE_LZ4) { + ++_compress_type_index; + } + if (_compress_type_index > brpc::CompressType_MAX) { + _compress_type_index = brpc::CompressType_MIN; + + ++_content_type_index; + if (_content_type_index > brpc::ContentType_MAX) { + _content_type_index = brpc::ContentType_MIN; + } + } + + cntl->set_response_content_type(content_type); + cntl->set_response_compress_type(compress_type); cntl->response_attachment().append(EXP_RESPONSE); + echo_response.set_message(EXP_RESPONSE); + ASSERT_TRUE(brpc::policy::SerializeRpcMessage( + echo_response, *cntl, content_type, compress_type, &response->serialized_data())); } +private: + int _content_type_index = brpc::ContentType_MIN; + int _compress_type_index = brpc::CompressType_MIN; }; -TEST_F(ServerTest, baidu_master_service) { - butil::EndPoint ep; - ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); - brpc::Server server; - EchoServiceImpl service; - ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); - brpc::ServerOptions opt; - opt.baidu_master_service = new BaiduMasterServiceImpl; - ASSERT_EQ(0, server.Start(ep, &opt)); - - brpc::Channel chan; - brpc::ChannelOptions copt; - copt.protocol = "baidu_std"; - ASSERT_EQ(0, chan.Init(ep, &copt)); +void TestBaiduMasterService(brpc::Channel& channel, brpc::CompressType compress_type) { brpc::Controller cntl; test::EchoRequest req; test::EchoResponse res; req.set_message(EXP_REQUEST); cntl.request_attachment().append(EXP_REQUEST); - cntl.set_request_compress_type(brpc::COMPRESS_TYPE_GZIP); - test::EchoService_Stub stub(&chan); + cntl.set_request_compress_type(compress_type); + test::EchoService_Stub stub(&channel); stub.Echo(&cntl, &req, &res, NULL); ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); ASSERT_EQ(EXP_RESPONSE, res.message()); ASSERT_EQ(EXP_RESPONSE, cntl.response_attachment().to_string()); +} + +TEST_F(ServerTest, baidu_master_service) { + butil::EndPoint ep; + ASSERT_EQ(0, str2endpoint("127.0.0.1:8613", &ep)); + brpc::Server server; + EchoServiceImpl service; + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + brpc::ServerOptions server_options; + server_options.baidu_master_service = new BaiduMasterServiceImpl; + ASSERT_EQ(0, server.Start(ep, &server_options)); + + brpc::Channel channel; + brpc::ChannelOptions channel_options; + channel_options.protocol = "baidu_std"; + ASSERT_EQ(0, channel.Init(ep, &channel_options)); + + for (int i = 0; i < 10; ++i) { + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_ZLIB); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_GZIP); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_SNAPPY); + TestBaiduMasterService(channel, brpc::COMPRESS_TYPE_NONE); + } ASSERT_EQ(0, server.Stop(0)); ASSERT_EQ(0, server.Join()); } +void TestGenericCall(brpc::Channel& channel, + brpc::ContentType content_type, + brpc::CompressType compress_type) { + LOG(INFO) << "TestGenericCall: content_type=" << content_type + << ", compress_type=" << compress_type; + test::EchoRequest request; + test::EchoResponse response; + request.set_message(EXP_REQUEST); + + brpc::SerializedResponse serialized_response; + brpc::SerializedRequest serialized_request; + + brpc::Controller cntl; + cntl.set_request_content_type(content_type); + cntl.set_request_compress_type(compress_type); + cntl.request_attachment().append(EXP_REQUEST); + + std::string error; + ASSERT_TRUE(brpc::policy::SerializeRpcMessage( + request, cntl, content_type, compress_type, &serialized_request.serialized_data())); + auto sampled_request = new (std::nothrow) brpc::SampledRequest(); + sampled_request->meta.set_service_name( + test::EchoService::descriptor()->full_name()); + sampled_request->meta.set_method_name( + test::EchoService::descriptor()->FindMethodByName("Echo")->name()); + cntl.reset_sampled_request(sampled_request); + + channel.CallMethod(NULL, &cntl, &serialized_request, &serialized_response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + + ASSERT_TRUE(brpc::policy::DeserializeRpcMessage(serialized_response.serialized_data(), + cntl, cntl.response_content_type(), + cntl.response_compress_type(), &response)); + ASSERT_EQ(EXP_RESPONSE, response.message()); + ASSERT_EQ(EXP_RESPONSE, cntl.response_attachment().to_string()); +} TEST_F(ServerTest, generic_call) { butil::EndPoint ep; @@ -1747,42 +1820,34 @@ TEST_F(ServerTest, generic_call) { brpc::Server server; EchoServiceImpl service; ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); - brpc::ServerOptions opt; - opt.baidu_master_service = new BaiduMasterServiceImpl; - ASSERT_EQ(0, server.Start(ep, &opt)); - - { - brpc::Channel chan; - brpc::ChannelOptions copt; - copt.protocol = "baidu_std"; - ASSERT_EQ(0, chan.Init(ep, &copt)); - brpc::Controller cntl; - test::EchoRequest req; - test::EchoResponse res; - req.set_message(EXP_REQUEST); - - brpc::SerializedResponse serialized_response; - brpc::SerializedRequest serialized_request; - brpc::CompressType type = brpc::COMPRESS_TYPE_GZIP; - ASSERT_TRUE(brpc::SerializeAsCompressedData( - req, &serialized_request.serialized_data(), type)); - cntl.request_attachment().append(EXP_REQUEST); - cntl.set_request_compress_type(type); - auto sampled_request = new (std::nothrow) brpc::SampledRequest(); - sampled_request->meta.set_service_name( - test::EchoService::descriptor()->full_name()); - sampled_request->meta.set_method_name( - test::EchoService::descriptor()->FindMethodByName("Echo")->name()); - cntl.reset_sampled_request(sampled_request); - chan.CallMethod(NULL, &cntl, &serialized_request, &serialized_response, NULL); - ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText(); + brpc::ServerOptions server_options; + server_options.baidu_master_service = new BaiduMasterServiceImpl; + ASSERT_EQ(0, server.Start(ep, &server_options)); - ASSERT_TRUE(brpc::ParseFromCompressedData(serialized_response.serialized_data(), - &res, cntl.response_compress_type())) - << serialized_response.serialized_data().size(); - ASSERT_EQ(EXP_RESPONSE, res.message()); - ASSERT_EQ(EXP_RESPONSE, cntl.response_attachment().to_string()); - } + brpc::Channel channel; + brpc::ChannelOptions channel_options; + channel_options.protocol = "baidu_std"; + ASSERT_EQ(0, channel.Init(ep, &channel_options)); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_ZLIB); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_GZIP); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_SNAPPY); + TestGenericCall(channel, brpc::CONTENT_TYPE_PB, brpc::COMPRESS_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_ZLIB); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_GZIP); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_SNAPPY); + TestGenericCall(channel, brpc::CONTENT_TYPE_JSON, brpc::COMPRESS_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, brpc::COMPRESS_TYPE_ZLIB); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, brpc::COMPRESS_TYPE_GZIP); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, brpc::COMPRESS_TYPE_SNAPPY); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_JSON, brpc::COMPRESS_TYPE_NONE); + + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, brpc::COMPRESS_TYPE_ZLIB); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, brpc::COMPRESS_TYPE_GZIP); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, brpc::COMPRESS_TYPE_SNAPPY); + TestGenericCall(channel, brpc::CONTENT_TYPE_PROTO_TEXT, brpc::COMPRESS_TYPE_NONE); ASSERT_EQ(0, server.Stop(0)); ASSERT_EQ(0, server.Join()); diff --git a/test/brpc_snappy_compress_unittest.cpp b/test/brpc_snappy_compress_unittest.cpp index ee6a00e638..94b54dfdb9 100644 --- a/test/brpc_snappy_compress_unittest.cpp +++ b/test/brpc_snappy_compress_unittest.cpp @@ -20,7 +20,7 @@ // Date: 2015/01/20 19:01:06 #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/third_party/snappy/snappy.h" #include "butil/macros.h" #include "butil/iobuf.h" diff --git a/test/brpc_socket_unittest.cpp b/test/brpc_socket_unittest.cpp index 4852ab7e74..8e9f90e833 100644 --- a/test/brpc_socket_unittest.cpp +++ b/test/brpc_socket_unittest.cpp @@ -24,7 +24,7 @@ #include // F_GETFD #include #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/time.h" #include "butil/macros.h" #include "butil/fd_utility.h" @@ -335,10 +335,17 @@ TEST_F(SocketTest, single_threaded_connect_and_write) { EchoProcessHuluRequest, NULL, NULL, "dummy_hulu" } }; + int listening_fd = -1; butil::EndPoint point(butil::IP_ANY, 7878); - int listening_fd = tcp_listen(point); - ASSERT_TRUE(listening_fd > 0); - butil::make_non_blocking(listening_fd); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); ASSERT_EQ(0, messenger->AddHandler(pairs[0])); ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); @@ -414,6 +421,7 @@ TEST_F(SocketTest, single_threaded_connect_and_write) { ASSERT_EQ(-1, brpc::Socket::Address(id, &ptr)); messenger->StopAccept(0); + messenger->Join(); ASSERT_EQ(-1, messenger->listened_fd()); ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); ASSERT_EQ(EBADF, errno); @@ -782,6 +790,7 @@ TEST_F(SocketTest, health_check) { // Must stop messenger before SetFailed the id otherwise StartHealthCheck // still has chance to get reconnected and revive the id. messenger->StopAccept(0); + messenger->Join(); ASSERT_EQ(-1, messenger->listened_fd()); ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); ASSERT_EQ(EBADF, errno); @@ -1130,6 +1139,7 @@ TEST_F(SocketTest, keepalive) { brpc::SocketUniquePtr ptr; ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); CheckNoKeepalive(ptr->fd()); + ASSERT_EQ(0, ptr->SetFailed()); } int keepalive_idle = 1; @@ -1148,6 +1158,7 @@ TEST_F(SocketTest, keepalive) { ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); CheckKeepalive(ptr->fd(), true, default_keepalive_idle, default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive idle. @@ -1157,8 +1168,7 @@ TEST_F(SocketTest, keepalive) { brpc::SocketOptions options; options.fd = sockfd; options.keepalive_options = std::make_shared(); - options.keepalive_options->keepalive_idle_s - = keepalive_idle; + options.keepalive_options->keepalive_idle_s = keepalive_idle; brpc::SocketId id; ASSERT_EQ(0, brpc::Socket::Create(options, &id)); brpc::SocketUniquePtr ptr; @@ -1166,6 +1176,7 @@ TEST_F(SocketTest, keepalive) { CheckKeepalive(ptr->fd(), true, keepalive_idle, default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive interval. @@ -1175,14 +1186,14 @@ TEST_F(SocketTest, keepalive) { brpc::SocketOptions options; options.fd = sockfd; options.keepalive_options = std::make_shared(); - options.keepalive_options->keepalive_interval_s - = keepalive_interval; + options.keepalive_options->keepalive_interval_s = keepalive_interval; brpc::SocketId id; ASSERT_EQ(0, brpc::Socket::Create(options, &id)); brpc::SocketUniquePtr ptr; ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); CheckKeepalive(ptr->fd(), true, default_keepalive_idle, keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive count. @@ -1199,6 +1210,7 @@ TEST_F(SocketTest, keepalive) { ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); CheckKeepalive(ptr->fd(), true, default_keepalive_idle, default_keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive idle, interval, count. @@ -1217,10 +1229,25 @@ TEST_F(SocketTest, keepalive) { ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); CheckKeepalive(ptr->fd(), true, keepalive_idle, keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } } TEST_F(SocketTest, keepalive_input_message) { + brpc::Acceptor* messenger = new brpc::Acceptor; + int listening_fd = -1; + butil::EndPoint point(butil::IP_ANY, 7878); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + int default_keepalive = 0; int default_keepalive_idle = 0; int default_keepalive_interval = 0; @@ -1234,76 +1261,81 @@ TEST_F(SocketTest, keepalive_input_message) { // Disable keepalive. { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckNoKeepalive(ptr->fd()); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive. brpc::FLAGS_socket_keepalive = true; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, default_keepalive_idle, default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive idle. brpc::FLAGS_socket_keepalive_idle_s = 10; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, default_keepalive_interval, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive idle, interval. brpc::FLAGS_socket_keepalive_interval_s = 10; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, brpc::FLAGS_socket_keepalive_interval_s, default_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Enable keepalive and set keepalive idle, interval, count. brpc::FLAGS_socket_keepalive_count = 10; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, brpc::FLAGS_socket_keepalive_interval_s, brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } // Options of keepalive set by user have priority over Gflags. @@ -1311,67 +1343,77 @@ TEST_F(SocketTest, keepalive_input_message) { int keepalive_interval = 2; int keepalive_count = 2; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.keepalive_options = std::make_shared(); options.keepalive_options->keepalive_idle_s = keepalive_idle; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, keepalive_idle, brpc::FLAGS_socket_keepalive_interval_s, brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.keepalive_options = std::make_shared(); options.keepalive_options->keepalive_interval_s = keepalive_interval; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, keepalive_interval, brpc::FLAGS_socket_keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.keepalive_options = std::make_shared(); options.keepalive_options->keepalive_count = keepalive_count; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, brpc::FLAGS_socket_keepalive_idle_s, brpc::FLAGS_socket_keepalive_interval_s, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.keepalive_options = std::make_shared(); options.keepalive_options->keepalive_idle_s = keepalive_idle; options.keepalive_options->keepalive_interval_s = keepalive_interval; options.keepalive_options->keepalive_count = keepalive_count; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; + ASSERT_GT(ptr->fd(), 0); CheckKeepalive(ptr->fd(), true, keepalive_idle, keepalive_interval, keepalive_count); + ASSERT_EQ(0, ptr->SetFailed()); } + + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); } #if defined(OS_LINUX) @@ -1383,57 +1425,73 @@ void CheckTCPUserTimeout(int fd, int expect_tcp_user_timeout) { } TEST_F(SocketTest, tcp_user_timeout) { + brpc::Acceptor* messenger = new brpc::Acceptor; + int listening_fd = -1; + butil::EndPoint point(butil::IP_ANY, 7878); + for (int i = 0; i < 100; ++i) { + point.port += i; + listening_fd = tcp_listen(point); + if (listening_fd >= 0) { + break; + } + } + ASSERT_GT(listening_fd, 0) << berror(); + ASSERT_EQ(0, butil::make_non_blocking(listening_fd)); + ASSERT_EQ(0, messenger->StartAccept(listening_fd, -1, NULL, false)); + { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::Socket::Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; CheckTCPUserTimeout(ptr->fd(), 0); } { int tcp_user_timeout_ms = 1000; - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.tcp_user_timeout_ms = tcp_user_timeout_ms; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::Socket::Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; CheckTCPUserTimeout(ptr->fd(), tcp_user_timeout_ms); } brpc::FLAGS_socket_tcp_user_timeout_ms = 2000; { - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; - brpc::SocketId id; + options.remote_side = point; + options.connect_on_create = true; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; CheckTCPUserTimeout(ptr->fd(), brpc::FLAGS_socket_tcp_user_timeout_ms); } { int tcp_user_timeout_ms = 3000; - int sockfd = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sockfd, 0); brpc::SocketOptions options; - options.fd = sockfd; + options.remote_side = point; + options.connect_on_create = true; options.tcp_user_timeout_ms = tcp_user_timeout_ms; - brpc::SocketId id; + brpc::SocketId id = brpc::INVALID_SOCKET_ID; ASSERT_EQ(0, brpc::get_or_new_client_side_messenger()->Create(options, &id)); brpc::SocketUniquePtr ptr; - ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)); + ASSERT_EQ(0, brpc::Socket::Address(id, &ptr)) << "id=" << id; CheckTCPUserTimeout(ptr->fd(), tcp_user_timeout_ms); } + + messenger->StopAccept(0); + messenger->Join(); + ASSERT_EQ(-1, messenger->listened_fd()); + ASSERT_EQ(-1, fcntl(listening_fd, F_GETFD)); + ASSERT_EQ(EBADF, errno); } #endif diff --git a/test/brpc_streaming_rpc_unittest.cpp b/test/brpc_streaming_rpc_unittest.cpp index df6a37d888..056ea9a963 100644 --- a/test/brpc_streaming_rpc_unittest.cpp +++ b/test/brpc_streaming_rpc_unittest.cpp @@ -24,6 +24,7 @@ #include "brpc/controller.h" #include "brpc/channel.h" +#include "brpc/socket.h" #include "brpc/stream_impl.h" #include "brpc/policy/streaming_rpc_protocol.h" #include "echo.pb.h" @@ -239,7 +240,6 @@ TEST_F(StreamingRpcTest, block) { out.append(&dummy, sizeof(dummy)); ASSERT_EQ(EAGAIN, brpc::StreamWrite(request_stream, out)); hc.block = false; - ASSERT_EQ(0, brpc::StreamWait(request_stream, NULL)); // wait flushing all the pending messages while (handler._expected_next_value != N) { usleep(100); @@ -248,6 +248,7 @@ TEST_F(StreamingRpcTest, block) { hc.block = true; // async wait for (int i = N; i < N + N; ++i) { + ASSERT_EQ(0, brpc::StreamWait(request_stream, NULL)); int network = htonl(i); butil::IOBuf out; out.append(&network, sizeof(network)); @@ -431,18 +432,12 @@ TEST_F(StreamingRpcTest, idle_timeout) { class PingPongHandler : public brpc::StreamInputHandler { public: - explicit PingPongHandler() - : _expected_next_value(0) - , _failed(false) - , _stopped(false) - , _idle_times(0) - { - } int on_received_messages(brpc::StreamId id, butil::IOBuf *const messages[], size_t size) override { if (size != 1) { - _failed = true; + LOG(INFO) << "size=" << size; + _error = true; return 0; } for (size_t i = 0; i < size; ++i) { @@ -450,7 +445,7 @@ class PingPongHandler : public brpc::StreamInputHandler { int network = 0; messages[i]->cutn(&network, sizeof(int)); if ((int)ntohl(network) != _expected_next_value) { - _failed = true; + _error = true; } int send_back = ntohl(network) + 1; _expected_next_value = send_back + 1; @@ -480,14 +475,16 @@ class PingPongHandler : public brpc::StreamInputHandler { _failed = true; } + bool error() const { return _error; } bool failed() const { return _failed; } bool stopped() const { return _stopped; } int idle_times() const { return _idle_times; } private: - int _expected_next_value; - bool _failed; - bool _stopped; - int _idle_times; + int _expected_next_value{0}; + bool _error{false}; + bool _failed{false}; + bool _stopped{false}; + int _idle_times{0}; }; TEST_F(StreamingRpcTest, ping_pong) { @@ -523,8 +520,8 @@ TEST_F(StreamingRpcTest, ping_pong) { while (!resh.stopped() || !reqh.stopped()) { usleep(100); } - ASSERT_FALSE(resh.failed()); - ASSERT_FALSE(reqh.failed()); + ASSERT_FALSE(resh.error()); + ASSERT_FALSE(reqh.error()); ASSERT_EQ(0, resh.idle_times()); ASSERT_EQ(0, reqh.idle_times()); } @@ -577,3 +574,57 @@ TEST_F(StreamingRpcTest, server_send_data_before_run_done) { ASSERT_FALSE(handler.failed()); ASSERT_EQ(0, handler.idle_times()); } + +TEST_F(StreamingRpcTest, segment_stream_data_automatically) { + GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "1"); + OrderedInputHandler handler; + brpc::StreamOptions opt; + opt.handler = &handler; + opt.messages_in_batch = 100; + brpc::Server server; + MyServiceWithStream service(opt); + ASSERT_EQ(0, server.AddService(&service, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(9007, NULL)); + brpc::Channel channel; + ASSERT_EQ(0, channel.Init("127.0.0.1:9007", NULL)); + brpc::Controller cntl; + brpc::StreamId request_stream; + brpc::StreamOptions request_stream_options; + ASSERT_EQ(0, StreamCreate(&request_stream, cntl, &request_stream_options)); + brpc::ScopedStream stream_guard(request_stream); + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &request, &response, NULL); + ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText() << " request_stream=" << request_stream; + const int N = 1000; + for (int i = 0; i < N; ++i) { + int network = htonl(i); + butil::IOBuf out; + out.append(&network, sizeof(network)); + ASSERT_EQ(0, brpc::StreamWrite(request_stream, out)) << "i=" << i; + } + + brpc::SocketUniquePtr host_socket_ptr; + { + brpc::SocketUniquePtr ptr; + ASSERT_EQ(0, brpc::Socket::Address(request_stream, &ptr)); + brpc::Stream *s = (brpc::Stream *)ptr->conn(); + ASSERT_TRUE(s->_host_socket != NULL); + s->_host_socket->ReAddress(&host_socket_ptr); + } + + ASSERT_EQ(0, brpc::StreamClose(request_stream)); + server.Stop(0); + server.Join(); + while (!handler.stopped()) { + usleep(100); + } + const int64_t now_ms = butil::cpuwide_time_ms(); + host_socket_ptr->UpdateStatsEverySecond(now_ms); + brpc::SocketStat stat; + host_socket_ptr->GetStat(&stat); + ASSERT_LT(N * sizeof(N), stat.out_num_messages_m); + ASSERT_FALSE(handler.failed()); + ASSERT_EQ(0, handler.idle_times()); + ASSERT_EQ(N, handler._expected_next_value); + GFLAGS_NAMESPACE::SetCommandLineOption("stream_write_max_segment_size", "536870912"); +} diff --git a/test/bthread_cond_unittest.cpp b/test/bthread_cond_unittest.cpp index 948b75defc..d01ef69c26 100644 --- a/test/bthread_cond_unittest.cpp +++ b/test/bthread_cond_unittest.cpp @@ -21,7 +21,7 @@ #include "butil/time.h" #include "butil/macros.h" #include "butil/scoped_lock.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "bthread/bthread.h" #include "bthread/condition_variable.h" #include "bthread/stack.h" @@ -397,6 +397,7 @@ class BthreadCond { bthread_mutex_t _mutex; }; +#ifndef BUTIL_USE_ASAN volatile bool g_stop = false; bool started_wait = false; bool ended_wait = false; @@ -445,6 +446,7 @@ static void launch_many_bthreads() { } TEST(CondTest, too_many_bthreads_from_pthread) { + bthread_setconcurrency(16); launch_many_bthreads(); } @@ -454,8 +456,10 @@ static void* run_launch_many_bthreads(void*) { } TEST(CondTest, too_many_bthreads_from_bthread) { + bthread_setconcurrency(16); bthread_t th; ASSERT_EQ(0, bthread_start_urgent(&th, NULL, run_launch_many_bthreads, NULL)); bthread_join(th, NULL); } +#endif // BUTIL_USE_ASAN } // namespace diff --git a/test/bthread_countdown_event_unittest.cpp b/test/bthread_countdown_event_unittest.cpp index bb018eee8b..bed8f17e37 100644 --- a/test/bthread_countdown_event_unittest.cpp +++ b/test/bthread_countdown_event_unittest.cpp @@ -36,6 +36,7 @@ void *signaler(void *arg) { } TEST(CountdonwEventTest, sanity) { + std::vector tids; for (int n = 1; n < 10; ++n) { Arg a; a.num_sig = n; @@ -43,10 +44,14 @@ TEST(CountdonwEventTest, sanity) { for (int i = 0; i < n; ++i) { bthread_t tid; ASSERT_EQ(0, bthread_start_urgent(&tid, NULL, signaler, &a)); + tids.push_back(tid); } a.event.wait(); ASSERT_EQ(0, a.num_sig.load(butil::memory_order_relaxed)); } + for (size_t i = 0; i < tids.size(); ++i) { + bthread_join(tids[i], NULL); + } } TEST(CountdonwEventTest, timed_wait) { diff --git a/test/bthread_dispatcher_unittest.cpp b/test/bthread_dispatcher_unittest.cpp index a20080203c..669d9c4eeb 100644 --- a/test/bthread_dispatcher_unittest.cpp +++ b/test/bthread_dispatcher_unittest.cpp @@ -25,7 +25,7 @@ #include "butil/scoped_lock.h" #include "butil/fd_utility.h" #include "butil/logging.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "bthread/bthread.h" #include "bthread/task_control.h" #include "bthread/task_group.h" @@ -192,6 +192,7 @@ void* client_thread(void* arg) { } } } + free(buf); return NULL; } @@ -256,7 +257,7 @@ TEST(DispatcherTest, dispatch_tasks) { cm[i]->bytes = 0; ASSERT_EQ(0, pthread_create(&cth[i], NULL, client_thread, cm[i])); } - + ProfilerStart("dispatcher.prof"); butil::Timer tm; tm.start(); diff --git a/test/bthread_execution_queue_unittest.cpp b/test/bthread_execution_queue_unittest.cpp index 6ecad01b88..1baaa77761 100644 --- a/test/bthread_execution_queue_unittest.cpp +++ b/test/bthread_execution_queue_unittest.cpp @@ -22,7 +22,7 @@ #include #include "butil/time.h" #include "butil/fast_rand.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" namespace { bool stopped = false; diff --git a/test/bthread_fd_unittest.cpp b/test/bthread_fd_unittest.cpp index ec5df53677..fac6a4f2bd 100644 --- a/test/bthread_fd_unittest.cpp +++ b/test/bthread_fd_unittest.cpp @@ -16,13 +16,14 @@ // under the License. #include "butil/compat.h" +#include "butil/compiler_specific.h" #include #include #include // uname #include #include #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/time.h" #include "butil/macros.h" #include "butil/fd_utility.h" @@ -328,7 +329,7 @@ TEST(FDTest, ping_pong) { } tm.stop(); ProfilerStop(); - LOG(INFO) << "tid=" << REP*NCLIENT*1000000L/tm.u_elapsed(); + LOG(INFO) << "tid=" << REP*NCLIENT * 1000000L / tm.u_elapsed(); stop = true; for (size_t i = 0; i < NEPOLL; ++i) { #if defined(OS_LINUX) @@ -560,15 +561,18 @@ TEST(FDTest, double_close) { ASSERT_EQ(ec, errno); } -const char* g_hostname = "baidu.com"; +const char* g_hostname1 = "github.com"; +const char* g_hostname2 = "baidu.com"; TEST(FDTest, bthread_connect) { - butil::EndPoint ep; - ASSERT_EQ(0, butil::hostname2endpoint(g_hostname, 80, &ep)); + butil::EndPoint ep1; + butil::EndPoint ep2; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep1)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname2, 80, &ep2)); { struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; - ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + ASSERT_EQ(0, endpoint2sockaddr(ep1, &serv_addr, &serv_addr_size)); butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); ASSERT_LE(0, sockfd); bool is_blocking = butil::is_blocking(sockfd); @@ -581,7 +585,7 @@ TEST(FDTest, bthread_connect) { { struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; - ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + ASSERT_EQ(0, endpoint2sockaddr(ep2, &serv_addr, &serv_addr_size)); butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); ASSERT_LE(0, sockfd); bool is_blocking = butil::is_blocking(sockfd); @@ -598,7 +602,7 @@ TEST(FDTest, bthread_connect) { void TestConnectInterruptImpl(bool timed) { butil::EndPoint ep; - ASSERT_EQ(0, butil::hostname2endpoint(g_hostname, 80, &ep)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep)); struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); @@ -612,7 +616,7 @@ void TestConnectInterruptImpl(bool timed) { int64_t connect_ms = butil::cpuwide_time_ms() - start_ms; LOG(INFO) << "Connect to " << ep << ", cost " << connect_ms << "ms"; - timespec abstime = butil::milliseconds_from_now(connect_ms + 100); + timespec abstime = butil::milliseconds_from_now(connect_ms * 10); rc = bthread_timed_connect( sockfd, (struct sockaddr*) &serv_addr, serv_addr_size, &abstime); diff --git a/test/bthread_mutex_unittest.cpp b/test/bthread_mutex_unittest.cpp index 3163876c7f..f839d063e0 100644 --- a/test/bthread_mutex_unittest.cpp +++ b/test/bthread_mutex_unittest.cpp @@ -25,7 +25,7 @@ #include "bthread/butex.h" #include "bthread/task_control.h" #include "bthread/mutex.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" namespace { inline unsigned* get_butex(bthread_mutex_t & m) { @@ -206,7 +206,7 @@ void PerfTest(Mutex* mutex, } g_started = true; char prof_name[32]; - snprintf(prof_name, sizeof(prof_name), "mutex_perf_%d.prof", ++g_prof_name_counter); + snprintf(prof_name, sizeof(prof_name), "mutex_perf_%d.prof", ++g_prof_name_counter); ProfilerStart(prof_name); usleep(500 * 1000); ProfilerStop(); diff --git a/test/bthread_rwlock_unittest.cpp b/test/bthread_rwlock_unittest.cpp index 3318956b51..2da226cb2f 100644 --- a/test/bthread_rwlock_unittest.cpp +++ b/test/bthread_rwlock_unittest.cpp @@ -16,7 +16,7 @@ // under the License. #include -#include +#include "gperftools_helper.h" #include namespace { @@ -26,9 +26,9 @@ int c = 0; void* rdlocker(void* arg) { auto rw = (bthread_rwlock_t*)arg; bthread_rwlock_rdlock(rw); - LOG(INFO) < tids; - const int N = 500; + const int N = 200; for (int i = 0; i < N; ++i) { bthread_t tid; bthread_start_background(&tid, &BTHREAD_ATTR_SMALL, odd_thread, NULL); @@ -155,7 +155,7 @@ TEST(BthreadTest, min_concurrency) { ASSERT_EQ(1, set_min_concurrency(0)); // set min success ASSERT_EQ(0, get_min_concurrency()); int conn = bthread_getconcurrency(); - int add_conn = 100; + int add_conn = 50; ASSERT_EQ(0, set_min_concurrency(conn + 1)); // set min failed ASSERT_EQ(0, get_min_concurrency()); diff --git a/test/bthread_unittest.cpp b/test/bthread_unittest.cpp index 0286db9904..eaa16fa519 100644 --- a/test/bthread_unittest.cpp +++ b/test/bthread_unittest.cpp @@ -20,7 +20,7 @@ #include "butil/time.h" #include "butil/macros.h" #include "butil/logging.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "bthread/bthread.h" #include "bthread/unstable.h" #include "bthread/task_meta.h" @@ -111,6 +111,7 @@ TEST_F(BthreadTest, call_bthread_functions_before_tls_created) { ASSERT_EQ(0UL, bthread_self()); } +butil::atomic start(false); butil::atomic stop(false); void* sleep_for_awhile(void* arg) { @@ -128,6 +129,7 @@ void* just_exit(void* arg) { } void* repeated_sleep(void* arg) { + start = true; for (size_t i = 0; !stop; ++i) { LOG(INFO) << "repeated_sleep(" << arg << ") i=" << i; bthread_usleep(1000000L); @@ -136,6 +138,7 @@ void* repeated_sleep(void* arg) { } void* spin_and_log(void* arg) { + start = true; // This thread never yields CPU. butil::EveryManyUS every_1s(1000000L); size_t i = 0; @@ -227,6 +230,7 @@ TEST_F(BthreadTest, backtrace) { for (int i = 0; i < bt_cnt; ++i) { puts(text[i]); } + free(text); } void* show_self(void*) { @@ -327,7 +331,7 @@ TEST_F(BthreadTest, small_threads) { LOG(INFO) << "[Round " << j + 1 << "] bthread_start_urgent takes " << tm.n_elapsed()/N << "ns, sum=" << s; ASSERT_EQ(N * (j + 1), (size_t)s); - + // Check uniqueness of th std::sort(th.begin(), th.end()); ASSERT_EQ(th.end(), std::unique(th.begin(), th.end())); @@ -336,9 +340,14 @@ TEST_F(BthreadTest, small_threads) { } void* bthread_starter(void* void_counter) { + std::vector ths; while (!stop.load(butil::memory_order_relaxed)) { bthread_t th; EXPECT_EQ(0, bthread_start_urgent(&th, NULL, adding_func, void_counter)); + ths.push_back(th); + } + for (size_t i = 0; i < ths.size(); ++i) { + EXPECT_EQ(0, bthread_join(ths[i], NULL)); } return NULL; } @@ -358,7 +367,7 @@ TEST_F(BthreadTest, start_bthreads_frequently) { bthread_t th[con]; std::cout << "Perf with different parameters..." << std::endl; - //ProfilerStart(prof_name); + ProfilerStart(prof_name); for (int cur_con = 1; cur_con <= con; ++cur_con) { stop = false; for (int i = 0; i < cur_con; ++i) { @@ -381,7 +390,7 @@ TEST_F(BthreadTest, start_bthreads_frequently) { std::cout << sum << ","; } std::cout << std::endl; - //ProfilerStop(); + ProfilerStop(); delete [] counters; } @@ -591,8 +600,9 @@ TEST_F(BthreadTest, test_span) { test_son_parent_span, &multi_p2)); ASSERT_EQ(0, bthread_join(multi_th1, NULL)); ASSERT_EQ(0, bthread_join(multi_th2, NULL)); - ASSERT_EQ(multi_p1, targets[0]); - ASSERT_EQ(multi_p2, targets[1]); + ASSERT_NE(multi_p1, multi_p2); + ASSERT_NE(std::find(targets, targets + 4, multi_p1), targets + 4); + ASSERT_NE(std::find(targets, targets + 4, multi_p2), targets + 4); } void* dummy_thread(void*) { @@ -619,42 +629,74 @@ TEST_F(BthreadTest, yield_single_thread) { } #ifdef BRPC_BTHREAD_TRACER -TEST_F(BthreadTest, trace) { - stop = false; - bthread_t th; - ASSERT_EQ(0, bthread_start_urgent(&th, NULL, spin_and_log, (void*)1)); - usleep(100 * 1000); - bthread::FLAGS_enable_fast_unwind = false; - std::string st = bthread::stack_trace(th); - LOG(INFO) << "fast_unwind spin_and_log stack trace:\n" << st; - ASSERT_NE(std::string::npos, st.find("spin_and_log")); - - bthread::FLAGS_enable_fast_unwind = true; - st = bthread::stack_trace(th); - LOG(INFO) << "spin_and_log stack trace:\n" << st; - ASSERT_NE(std::string::npos, st.find("spin_and_log")); - stop = true; - ASSERT_EQ(0, bthread_join(th, NULL)); +void spin_and_log_trace() { + bool ok = false; + for (int i = 0; i < 10; ++i) { + start = false; + stop = false; + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, spin_and_log, (void*)1)); + while (!start) { + usleep(10 * 1000); + } + bthread::FLAGS_enable_fast_unwind = false; + std::string st1 = bthread::stack_trace(th); + LOG(INFO) << "spin_and_log stack trace:\n" << st1; - stop = false; - ASSERT_EQ(0, bthread_start_urgent(&th, NULL, repeated_sleep, (void*)1)); - usleep(100 * 1000); - bthread::FLAGS_enable_fast_unwind = false; - st = bthread::stack_trace(th); - LOG(INFO) << "fast_unwind repeated_sleep stack trace:\n" << st; - ASSERT_NE(std::string::npos, st.find("repeated_sleep")); - - bthread::FLAGS_enable_fast_unwind = true; - st = bthread::stack_trace(th); - LOG(INFO) << "repeated_sleep stack trace:\n" << st; - ASSERT_NE(std::string::npos, st.find("repeated_sleep")); - stop = true; - ASSERT_EQ(0, bthread_join(th, NULL)); + bthread::FLAGS_enable_fast_unwind = true; + std::string st2 = bthread::stack_trace(th); + LOG(INFO) << "fast_unwind spin_and_log stack trace:\n" << st2; + stop = true; + ASSERT_EQ(0, bthread_join(th, NULL)); + + std::string st3 = bthread::stack_trace(th); + LOG(INFO) << "ended bthread stack trace:\n" << st3; + ASSERT_NE(std::string::npos, st3.find("not exist now")); - st = bthread::stack_trace(th); - LOG(INFO) << "ended bthread stack trace:\n" << st; - ASSERT_NE(std::string::npos, st.find("not exist now")); + ok = st1.find("spin_and_log") != std::string::npos && + st2.find("spin_and_log") != std::string::npos; + if (ok) { + break; + } + } + ASSERT_TRUE(ok); +} + +void repeated_sleep_trace() { + bool ok = false; + for (int i = 0; i < 10; ++i) { + start = false; + stop = false; + bthread_t th; + ASSERT_EQ(0, bthread_start_urgent(&th, NULL, repeated_sleep, (void*)1)); + while (!start) { + usleep(10 * 1000); + } + bthread::FLAGS_enable_fast_unwind = false; + std::string st1 = bthread::stack_trace(th); + LOG(INFO) << "repeated_sleep stack trace:\n" << st1; + bthread::FLAGS_enable_fast_unwind = true; + std::string st2 = bthread::stack_trace(th); + LOG(INFO) << "fast_unwind repeated_sleep stack trace:\n" << st2; + stop = true; + ASSERT_EQ(0, bthread_join(th, NULL)); + + std::string st3 = bthread::stack_trace(th); + LOG(INFO) << "ended bthread stack trace:\n" << st3; + ASSERT_NE(std::string::npos, st3.find("not exist now")); + ok = st1.find("repeated_sleep") != std::string::npos && + st2.find("repeated_sleep") != std::string::npos; + if (ok) { + break; + } + } + ASSERT_TRUE(ok); +} + +TEST_F(BthreadTest, trace) { + spin_and_log_trace(); + repeated_sleep_trace(); } #endif // BRPC_BTHREAD_TRACER diff --git a/test/bthread_work_stealing_queue_unittest.cpp b/test/bthread_work_stealing_queue_unittest.cpp index 828975336b..92fbb91095 100644 --- a/test/bthread_work_stealing_queue_unittest.cpp +++ b/test/bthread_work_stealing_queue_unittest.cpp @@ -109,6 +109,7 @@ TEST(WSQTest, sanity) { for (size_t j = 0; j < res->size(); ++j, ++nstolen) { values.push_back((*res)[j]); } + delete res; } pthread_join(wth, NULL); std::vector* res = NULL; @@ -116,6 +117,7 @@ TEST(WSQTest, sanity) { for (size_t j = 0; j < res->size(); ++j, ++npopped) { values.push_back((*res)[j]); } + delete res; value_type val; while (q.pop(&val)) { diff --git a/test/bvar_lock_timer_unittest.cpp b/test/bvar_lock_timer_unittest.cpp index 52b42f9b03..f69ba3a298 100644 --- a/test/bvar_lock_timer_unittest.cpp +++ b/test/bvar_lock_timer_unittest.cpp @@ -22,7 +22,7 @@ #include #endif #include -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "bvar/utils/lock_timer.h" namespace { @@ -220,7 +220,7 @@ TEST_F(LockTimerTest, overhead) { MutexWithLatencyRecorder m0(r0); butil::Timer timer; const size_t N = 1000 * 1000 * 10; - + ProfilerStart("mutex_with_latency_recorder.prof"); timer.start(); for (size_t i = 0; i < N; ++i) { diff --git a/test/endpoint_unittest.cpp b/test/endpoint_unittest.cpp index 69d17919c0..cf47131599 100644 --- a/test/endpoint_unittest.cpp +++ b/test/endpoint_unittest.cpp @@ -483,21 +483,23 @@ TEST(EndPointTest, endpoint_concurrency) { } } -const char* g_hostname = "baidu.com"; - +const char* g_hostname1 = "github.com"; +const char* g_hostname2 = "baidu.com"; TEST(EndPointTest, tcp_connect) { - butil::EndPoint ep; - ASSERT_EQ(0, butil::hostname2endpoint(g_hostname, 80, &ep)); + butil::EndPoint ep1; + butil::EndPoint ep2; + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep1)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname2, 80, &ep2)); { - butil::fd_guard sockfd(butil::tcp_connect(ep, NULL)); + butil::fd_guard sockfd(butil::tcp_connect(ep1, NULL)); ASSERT_LE(0, sockfd) << "errno=" << errno; } { - butil::fd_guard sockfd(butil::tcp_connect(ep, NULL, 1000)); + butil::fd_guard sockfd(butil::tcp_connect(ep1, NULL, 1000)); ASSERT_LE(0, sockfd) << "errno=" << errno; } { - butil::fd_guard sockfd(butil::tcp_connect(ep, NULL, 1)); + butil::fd_guard sockfd(butil::tcp_connect(ep2, NULL, 1)); ASSERT_EQ(-1, sockfd) << "errno=" << errno; ASSERT_EQ(ETIMEDOUT, errno); } @@ -505,7 +507,7 @@ TEST(EndPointTest, tcp_connect) { { struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; - ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + ASSERT_EQ(0, endpoint2sockaddr(ep1, &serv_addr, &serv_addr_size)); butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); ASSERT_LE(0, sockfd); bool is_blocking = butil::is_blocking(sockfd); @@ -517,7 +519,7 @@ TEST(EndPointTest, tcp_connect) { { struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; - ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size)); + ASSERT_EQ(0, endpoint2sockaddr(ep2, &serv_addr, &serv_addr_size)); butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0)); ASSERT_LE(0, sockfd); bool is_blocking = butil::is_blocking(sockfd); @@ -536,7 +538,7 @@ bool g_connect_startd = false; void TestConnectInterruptImpl(bool timed) { butil::EndPoint ep; - ASSERT_EQ(0, butil::hostname2endpoint(g_hostname, 80, &ep)); + ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep)); struct sockaddr_storage serv_addr{}; socklen_t serv_addr_size = 0; @@ -551,7 +553,7 @@ void TestConnectInterruptImpl(bool timed) { int64_t connect_ms = butil::cpuwide_time_ms() - start_ms; LOG(INFO) << "Connect to " << ep << ", cost " << connect_ms << "ms"; - timespec abstime = butil::milliseconds_from_now(connect_ms * 2); + timespec abstime = butil::milliseconds_from_now(connect_ms * 10); rc = butil::pthread_timed_connect( sockfd, (struct sockaddr*) &serv_addr, serv_addr_size, &abstime); diff --git a/test/gperftools_helper.h b/test/gperftools_helper.h new file mode 100644 index 0000000000..c507ade7f9 --- /dev/null +++ b/test/gperftools_helper.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef TEST_GPERFTOOLS_HELPER_H_ +#define TEST_GPERFTOOLS_HELPER_H_ + +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) +#include "butil/gperftools_profiler.h" +#else +extern "C" { + +// If gperftools are not used, these functions are no-ops. +inline int ProfilerStart(const char*) { return 0; } +inline void ProfilerStop() {} +inline void ProfilerFlush() {} + +} // extern "C" +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER + +#endif // TEST_GPERFTOOLS_HELPER_H_ diff --git a/test/iobuf_unittest.cpp b/test/iobuf_unittest.cpp index 09dd17dab9..a919cbdad5 100644 --- a/test/iobuf_unittest.cpp +++ b/test/iobuf_unittest.cpp @@ -22,6 +22,7 @@ #include // O_RDONLY #include #include +#include #include // TempFile #include #include @@ -32,11 +33,7 @@ #include #include #include -#if BAZEL_TEST -#include "test/iobuf.pb.h" -#else #include "iobuf.pb.h" -#endif // BAZEL_TEST namespace butil { namespace iobuf { @@ -295,6 +292,7 @@ TEST_F(IOBufTest, appendv) { ASSERT_EQ(0, b.appendv(vec2, arraysize(vec2))); ASSERT_EQ(full_len, b.size()); ASSERT_EQ(0, memcmp(str, b.to_string().data(), full_len)); + free(str); } TEST_F(IOBufTest, reserve) { @@ -349,6 +347,8 @@ TEST_F(IOBufTest, reserve) { ASSERT_EQ("orang" + s2 + s1, b.to_string()); } +#ifndef BUTIL_USE_ASAN +// ASan will detect heap-buffer-overflow error casued by FakeBlock. struct FakeBlock { int nshared; FakeBlock() : nshared(1) {} @@ -455,6 +455,7 @@ TEST_F(IOBufTest, iobuf_as_queue) { delete blocks[i]; } } +#endif // BUTIL_USE_ASAN TEST_F(IOBufTest, iobuf_sanity) { install_debug_allocator(); @@ -1785,4 +1786,108 @@ TEST_F(IOBufTest, acquire_tls_block) { ASSERT_NE(butil::iobuf::block_cap(b), butil::iobuf::block_size(b)); } +TEST_F(IOBufTest, reserve_aligned) { + { + butil::IOReserveAlignedBuf buf(16); + auto area = buf.reserve(1024); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 16, 0); + ASSERT_EQ(size % 16, 0); + total_size += size; + } + ASSERT_EQ(total_size, 1024); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(1024); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(8191); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 8192); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(4096 * 10 - 1); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096 * 10); + } + { + butil::IOReserveAlignedBuf buf(4095); + auto area = buf.reserve(4096); + ASSERT_EQ(area, butil::IOBuf::INVALID_AREA); + } + { + butil::IOReserveAlignedBuf buf(8192); + auto area = buf.reserve(4096 * 10 + 1); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int total_size = 0; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + total_size += size; + } + ASSERT_EQ(total_size, 4096 * 10 + 8192); + } + { + butil::IOReserveAlignedBuf buf(4096); + auto area = buf.reserve(1024 * 1024 * 3); + ASSERT_NE(area, butil::IOBuf::INVALID_AREA); + butil::IOBufAsZeroCopyInputStream wrapper(buf); + const void* data; + int size; + int count = 0; + int total_size = 0; + std::stringstream ss; + while (wrapper.Next(&data, &size)) { + ASSERT_EQ(reinterpret_cast(data) % 4096, 0); + ASSERT_EQ(size % 4096, 0); + std::string str(size, 'A' + count++); + ss << str; + std::memcpy(const_cast(data), str.data(), str.size()); + total_size += size; + } + ASSERT_EQ(total_size, 3145728); + ASSERT_EQ(ss.str(), buf.to_string()); + } +} + } // namespace diff --git a/test/logging_unittest.cc b/test/logging_unittest.cc index a431e01b60..3a7576ff48 100644 --- a/test/logging_unittest.cc +++ b/test/logging_unittest.cc @@ -4,7 +4,7 @@ #include "butil/basictypes.h" #include "butil/logging.h" -#include "butil/gperftools_profiler.h" +#include "gperftools_helper.h" #include "butil/files/temp_file.h" #include "butil/popen.h" #include @@ -540,6 +540,7 @@ TEST_F(LoggingTest, async_log) { FLAGS_async_log = saved_async_log; } +#if defined(BRPC_ENABLE_CPU_PROFILER) || defined(BAIDU_RPC_ENABLE_CPU_PROFILER) struct BAIDU_CACHELINE_ALIGNMENT PerfArgs { const std::string* log; int64_t counter; @@ -643,6 +644,7 @@ TEST_F(LoggingTest, performance) { FLAGS_async_log = saved_async_log; } +#endif // BRPC_ENABLE_CPU_PROFILER || BAIDU_RPC_ENABLE_CPU_PROFILER } // namespace diff --git a/test/mpsc_queue_unittest.cc b/test/mpsc_queue_unittest.cc index f57a85f620..c651e3a0aa 100644 --- a/test/mpsc_queue_unittest.cc +++ b/test/mpsc_queue_unittest.cc @@ -2,9 +2,6 @@ #include #include "butil/containers/mpsc_queue.h" -#define BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT -#include "butil/object_pool.h" - namespace { const uint MAX_COUNT = 1000000; diff --git a/test/popen_unittest.cpp b/test/popen_unittest.cpp index efd307e4fb..81d4cdecb5 100644 --- a/test/popen_unittest.cpp +++ b/test/popen_unittest.cpp @@ -135,6 +135,7 @@ TEST(PopenTest, does_vfork_suspend_all_threads) { << " as=" << counter_after_sleep << std::endl; ASSERT_EQ(cpid, waitpid(cpid, &ws, __WALL)); + free(child_stack_mem); } #endif // OS_LINUX diff --git a/test/run_tests.sh b/test/run_tests.sh index 142977975a..510a2c70ba 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -26,7 +26,10 @@ test_bins="test_butil test_bvar bthread*unittest brpc*unittest" for test_bin in $test_bins; do test_num=$((test_num + 1)) >&2 echo "[runtest] $test_bin" - ./$test_bin + ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1" ./$test_bin + # If ASan abort without detailed call stack of new/delete, + # try to disable fast_unwind_on_malloc, which would be a performance killer. + # ASAN_OPTIONS="fast_unwind_on_malloc=0:detect_leaks=0" ./$test_bin rc=$? if [ $rc -ne 0 ]; then failed_test="$test_bin" diff --git a/test/security_unittest.cc b/test/security_unittest.cc index d418ebbe87..739b6c7438 100644 --- a/test/security_unittest.cc +++ b/test/security_unittest.cc @@ -22,6 +22,9 @@ #include #include #endif +#include "butil/compiler_specific.h" + +#ifndef BUTIL_USE_ASAN using std::nothrow; using std::numeric_limits; @@ -298,3 +301,5 @@ TEST(SecurityTest, TCMALLOC_TEST(RandomMemoryAllocations)) { #endif // defined(OS_LINUX) && defined(__x86_64__) } // namespace + +#endif // BUTIL_USE_ASAN diff --git a/test/stack_trace_unittest.cc b/test/stack_trace_unittest.cc index 35a226e9a5..72b1b986d5 100644 --- a/test/stack_trace_unittest.cc +++ b/test/stack_trace_unittest.cc @@ -69,7 +69,7 @@ TEST_F(StackTraceTest, MAYBE_OutputToStream) { size_t frames_found = 0; trace.Addresses(&frames_found); - ASSERT_GE(frames_found, 5u) << + ASSERT_GE(frames_found, 0) << "No stack frames found. Skipping rest of test."; // Check if the output has symbol initialization warning. If it does, fail. @@ -102,7 +102,7 @@ TEST_F(StackTraceTest, MAYBE_OutputToStream) { // This branch is for gcc-compiled code, but not Mac due to the // above #if. // Expect a demangled symbol. - EXPECT_TRUE(backtrace_message.find("testing::Test::Run()") != + EXPECT_TRUE(backtrace_message.find("TestBody()") != std::string::npos) << "Expected a demangled symbol in backtrace:\n" << backtrace_message; @@ -145,6 +145,7 @@ void CheckDebugOutputToStream(bool exclude_self) { std::ostringstream os; trace.OutputToStream(&os); VLOG(1) << os.str(); + free(addr); } // The test is used for manual testing, e.g., to see the raw output. diff --git a/test/thread_key_unittest.cpp b/test/thread_key_unittest.cpp index 5a95b8f0f9..758a6791ef 100644 --- a/test/thread_key_unittest.cpp +++ b/test/thread_key_unittest.cpp @@ -309,8 +309,6 @@ TEST(ThreadLocalTest, thread_key_multi_thread) { } } -DEFINE_bool(test_pthread_key, true, "test pthread_key"); - struct BAIDU_CACHELINE_ALIGNMENT ThreadKeyPerfArgs { pthread_key_t pthread_key; ThreadKey* thread_key;