From 760aefe6056c8bab1432aa105f78295f418e2fad Mon Sep 17 00:00:00 2001 From: Jie Yao Date: Fri, 25 Sep 2026 00:45:02 +0800 Subject: [PATCH 1/2] upgrade to homobject v5: remove folly dependency --- .github/workflows/build_commit.yml | 38 +- .github/workflows/build_dependencies.yml | 124 +++- .github/workflows/conan_build.yml | 72 +- .github/workflows/version_change_check.yml | 2 +- CMakeLists.txt | 6 +- README.md | 31 +- cmake/sisl_cast_compat.hpp | 51 ++ conanfile.py | 12 +- docs/adr/rebuild-homeobject-without-folly.md | 455 ++++++++++++ src/CMakeLists.txt | 5 +- src/include/homeobject/blob_manager.hpp | 2 +- src/include/homeobject/common.hpp | 22 +- src/include/homeobject/homeobject.hpp | 2 +- src/lib/CMakeLists.txt | 6 +- src/lib/blob_manager.cpp | 28 +- src/lib/blob_route.hpp | 52 +- src/lib/homeobject_impl.cpp | 19 +- src/lib/homeobject_impl.hpp | 5 +- src/lib/homestore_backend/CMakeLists.txt | 29 +- .../homestore_backend/MPMCPriorityQueue.hpp | 192 ----- src/lib/homestore_backend/gc_manager.cpp | 415 +++++------ src/lib/homestore_backend/gc_manager.hpp | 98 +-- .../homestore_backend/heap_chunk_selector.h | 6 +- src/lib/homestore_backend/hs_blob_manager.cpp | 295 ++++---- src/lib/homestore_backend/hs_cp_callbacks.cpp | 5 +- src/lib/homestore_backend/hs_homeobject.cpp | 76 +- src/lib/homestore_backend/hs_homeobject.hpp | 71 +- src/lib/homestore_backend/hs_http_manager.cpp | 656 +++++++++-------- src/lib/homestore_backend/hs_http_manager.hpp | 74 +- src/lib/homestore_backend/hs_pg_manager.cpp | 238 +++---- .../homestore_backend/hs_shard_manager.cpp | 126 ++-- src/lib/homestore_backend/index_kv.cpp | 8 +- src/lib/homestore_backend/index_kv.hpp | 18 +- .../homestore_backend/pg_blob_iterator.cpp | 142 ++-- .../homestore_backend/replication_message.hpp | 10 +- .../replication_state_machine.cpp | 282 ++++---- .../replication_state_machine.hpp | 112 +-- src/lib/homestore_backend/scrub_manager.cpp | 673 ++++++++---------- src/lib/homestore_backend/scrub_manager.hpp | 53 +- .../snapshot_receive_handler.cpp | 243 ++++--- .../homestore_backend/tests/CMakeLists.txt | 4 - .../tests/homeobj_fixture.hpp | 77 +- .../tests/homeobj_fixture_http.hpp | 70 +- .../tests/homeobj_misc_tests.cpp | 58 +- .../homestore_backend/tests/hs_blob_tests.cpp | 34 +- .../homestore_backend/tests/hs_gc_tests.cpp | 106 ++- .../homestore_backend/tests/hs_pg_tests.cpp | 29 +- .../tests/hs_repl_test_helper.hpp | 50 +- .../tests/hs_scrubber_tests.cpp | 114 ++- .../tests/hs_shard_tests.cpp | 32 +- .../tests/test_heap_chunk_selector.cpp | 3 - .../tests/test_homestore_backend.cpp | 2 +- .../tests/test_homestore_backend_dynamic.cpp | 80 +-- .../tests/test_mpmc_priority_queue.cpp | 413 ----------- src/lib/memory_backend/CMakeLists.txt | 3 +- src/lib/memory_backend/mem_blob_manager.cpp | 66 +- src/lib/memory_backend/mem_homeobject.cpp | 4 +- src/lib/memory_backend/mem_homeobject.hpp | 12 +- src/lib/memory_backend/mem_pg_manager.cpp | 21 +- src/lib/memory_backend/mem_shard_manager.cpp | 9 +- src/lib/pg_manager.cpp | 14 +- src/lib/shard_manager.cpp | 62 +- src/lib/tests/BlobManagerTest.cpp | 151 ++-- src/lib/tests/PGManagerTest.cpp | 20 +- src/lib/tests/ShardManagerTest.cpp | 56 +- src/lib/tests/fixture_app.cpp | 34 +- test_package/test_package.cpp | 8 +- v4upgrade.md | 41 -- v5upgrade.md | 61 ++ 69 files changed, 3161 insertions(+), 3227 deletions(-) create mode 100644 cmake/sisl_cast_compat.hpp create mode 100644 docs/adr/rebuild-homeobject-without-folly.md delete mode 100644 src/lib/homestore_backend/MPMCPriorityQueue.hpp delete mode 100644 src/lib/homestore_backend/tests/test_mpmc_priority_queue.cpp delete mode 100644 v4upgrade.md create mode 100644 v5upgrade.md diff --git a/.github/workflows/build_commit.yml b/.github/workflows/build_commit.yml index 7d0e4c2aa..c04eb636e 100644 --- a/.github/workflows/build_commit.yml +++ b/.github/workflows/build_commit.yml @@ -18,50 +18,64 @@ on: tooling: required: true type: string + compiler: + required: false + type: string + default: 'gcc' + conan-channel: + required: false + type: string + default: 'dev' jobs: SislDeps: - uses: ebay/sisl/.github/workflows/build_dependencies.yml@stable/v13.x + uses: ebay/sisl/.github/workflows/build_dependencies.yml@dev/v14.x with: - branch: stable/v13.x + branch: dev/v14.x platform: ${{ inputs.platform }} build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: None if: ${{ github.event_name != 'pull_request' }} NuraftMesgDeps: needs: SislDeps - uses: eBay/nuraft_mesg/.github/workflows/build_dependencies.yml@stable/v4.x + uses: eBay/nuraft_mesg/.github/workflows/build_dependencies.yml@dev/v5.x with: - branch: stable/v4.x + branch: dev/v5.x platform: ${{ inputs.platform }} build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: None if: ${{ github.event_name != 'pull_request' }} IOMgrDeps: needs: [SislDeps] - uses: eBay/iomanager/.github/workflows/build_dependencies.yml@stable/v12.x + uses: eBay/iomanager/.github/workflows/build_dependencies.yml@dev/v13.x with: - branch: stable/v12.x + branch: dev/v13.x platform: ${{ inputs.platform }} build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} - prerelease: ${{ inputs.prerelease }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: None if: ${{ github.event_name != 'pull_request' }} HomeStoreDeps: needs: [IOMgrDeps, NuraftMesgDeps] - uses: eBay/homestore/.github/workflows/build_dependencies.yml@stable/v7.x + uses: eBay/homestore/.github/workflows/build_dependencies.yml@dev/v8.x with: - branch: stable/v7.x + branch: dev/v8.x platform: ${{ inputs.platform }} build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} - prerelease: ${{ inputs.prerelease }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: None if: ${{ github.event_name != 'pull_request' }} @@ -74,6 +88,8 @@ jobs: build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} prerelease: ${{ inputs.prerelease }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: ${{ inputs.tooling }} testing: 'True' if: ${{ github.event_name != 'pull_request' }} @@ -86,6 +102,8 @@ jobs: build-type: ${{ inputs.build-type }} malloc-impl: ${{ inputs.malloc-impl }} prerelease: ${{ inputs.prerelease }} + compiler: ${{ inputs.compiler }} + conan-channel: ${{ inputs.conan-channel }} tooling: ${{ inputs.tooling }} testing: 'True' if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/build_dependencies.yml b/.github/workflows/build_dependencies.yml index 961c669d1..61772304b 100644 --- a/.github/workflows/build_dependencies.yml +++ b/.github/workflows/build_dependencies.yml @@ -27,6 +27,14 @@ on: required: false type: string default: 'False' + compiler: + required: false + type: string + default: 'gcc' + conan-channel: + required: false + type: string + default: 'dev' workflow_dispatch: inputs: platform: @@ -64,7 +72,9 @@ on: tooling: required: false type: choice - - 'Sanitize' + options: + - 'AddressSanitize' + - 'ThreadSanitize' - 'Coverage' - 'None' default: 'None' @@ -76,6 +86,16 @@ on: - 'True' - 'False' default: 'True' + compiler: + required: false + type: choice + options: + - gcc + default: gcc + conan-channel: + required: false + type: string + default: 'dev' jobs: BuildHomeObjectDeps: @@ -95,30 +115,51 @@ jobs: if: ${{ inputs.testing == 'False' }} - name: Setup Conan - uses: ebay/sisl/.github/actions/setup_conan2@stable/v13.x + uses: ebay/sisl/.github/actions/setup_conan2@dev/v14.x with: platform: ${{ inputs.platform }} + compiler: ${{ inputs.compiler }} if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Load HomeObject Cache id: restore-cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: testing: ${{ inputs.testing }} key_prefix: HomeObjectDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} + - name: Retrieve Dependencies Sisl + uses: actions/checkout@v4 + with: + repository: eBay/sisl + path: import/sisl + ref: dev/v14.x + if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} + - name: Load Sisl Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x + with: + testing: 'False' + path: import/sisl + key_prefix: SislDeps14-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.compiler }} + fail_on_cache_miss: true + if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} + + - name: Retrieve Dependencies IOMgr + uses: actions/checkout@v4 with: - load_any: 'True' - key_prefix: SislDeps13-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }} + repository: eBay/iomanager + path: import/iomgr + ref: dev/v13.x if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Load IOMgr Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: - load_any: 'True' - key_prefix: IOMgrDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} + testing: 'False' + path: import/iomgr + key_prefix: IOMgrDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.compiler }} + fail_on_cache_miss: true if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Retrieve Dependencies NuRaftMesg @@ -126,11 +167,11 @@ jobs: with: repository: eBay/nuraft_mesg path: import/nuraft_mesg - ref: stable/v4.x + ref: dev/v5.x if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Load NuRaftMesg Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: testing: 'False' path: import/nuraft_mesg @@ -143,25 +184,41 @@ jobs: with: repository: eBay/HomeStore path: import/homestore - ref: stable/v7.x + ref: dev/v8.x if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Load HomeStore Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: testing: 'False' path: import/homestore - key_prefix: HomestoreDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} + key_prefix: HomestoreDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }} fail_on_cache_miss: true if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - - name: Export Recipes run: | sudo apt-get install -y python3-pyelftools libaio-dev python -m pip install pyelftools - conan export import/homestore - conan export import/nuraft_mesg + sudo rm -rf $ANDROID_HOME + + pushd import/sisl + ./prepare_v2.sh + conan export --user oss --channel ${{ inputs.conan-channel }} . + popd + + pushd import/iomgr + conan export --user oss --channel ${{ inputs.conan-channel }} . + popd + + pushd import/nuraft_mesg + ./prepare_v2.sh + conan export --user oss --channel ${{ inputs.conan-channel }} . + popd + + pushd import/homestore + conan export --user oss --channel ${{ inputs.conan-channel }} . + popd if: ${{ inputs.testing == 'True' || steps.restore-cache.outputs.cache-hit != 'true' }} - name: Build Cache @@ -170,11 +227,9 @@ jobs: echo "Disk space usage" sudo df -h sudo ls -l /tmp - sudo rm -rf $ANDROID_HOME echo "Disk space usage after cleanup" sudo df -h conan install \ - -c tools.build:skip_test=True \ ${pre} \ -o sisl/*:malloc_impl=${{ inputs.malloc-impl }} \ -o iomgr/*:testing=off \ @@ -184,31 +239,35 @@ jobs: --format=json \ --build missing \ . > ~/build.json - conan list --graph ~/build.json --graph-binaries=build --format=json > ~/pkglist.json + conan list --graph ~/build.json --graph-binaries=build,cache --format=json > ~/pkglist.json if: ${{ steps.restore-cache.outputs.cache-hit != 'true' }} - name: Save Conan Cache - uses: eBay/sisl/.github/actions/store_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/store_conan2@dev/v14.x with: key_prefix: HomeObjectDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} if: ${{ github.event_name != 'pull_request' && steps.restore-cache.outputs.cache-hit != 'true' }} - name: Reload Sisl Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: - load_any: 'True' - key_prefix: SislDeps13-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }} + testing: 'False' + path: import/sisl + key_prefix: SislDeps14-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.compiler }} + fail_on_cache_miss: true if: ${{ inputs.testing == 'True' && github.event_name != 'pull_request' && steps.restore-cache.outputs.cache-hit != 'true' }} - name: Reload IOMgr Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: - load_any: 'True' - key_prefix: IOMgrDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} + testing: 'False' + path: import/iomgr + key_prefix: IOMgrDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.compiler }} + fail_on_cache_miss: true if: ${{ inputs.testing == 'True' && github.event_name != 'pull_request' && steps.restore-cache.outputs.cache-hit != 'true' }} - name: Reload NuRaftMesg Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: testing: 'False' path: import/nuraft_mesg @@ -217,17 +276,19 @@ jobs: if: ${{ inputs.testing == 'True' && github.event_name != 'pull_request' && steps.restore-cache.outputs.cache-hit != 'true' }} - name: Reload HomeStore Cache - uses: eBay/sisl/.github/actions/load_conan2@stable/v13.x + uses: eBay/sisl/.github/actions/load_conan2@dev/v14.x with: testing: 'False' path: import/homestore - key_prefix: HomestoreDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }}-${{ inputs.prerelease }} + key_prefix: HomestoreDeps-${{ inputs.platform }}-${{ inputs.build-type }}-${{ inputs.malloc-impl }} fail_on_cache_miss: true if: ${{ inputs.testing == 'True' && github.event_name != 'pull_request' && steps.restore-cache.outputs.cache-hit != 'true' }} - name: Create and Test Package run: | - sanitize=$([[ "${{ inputs.tooling }}" == "Sanitize" ]] && echo "True" || echo "False") + if [[ "${{ inputs.tooling }}" == "AddressSanitize" ]]; then sanitize="address" + elif [[ "${{ inputs.tooling }}" == "ThreadSanitize" ]]; then sanitize="thread" + else sanitize="False"; fi pre=$([[ "${{ inputs.build-type }}" != "Debug" ]] && echo "-o iomgr/*:prerelease=${{ inputs.prerelease }}" || echo "") conan create \ ${pre} \ @@ -236,6 +297,7 @@ jobs: -o homestore/*:testing=off \ -o homeobject/*:sanitize=${sanitize} \ -s:h build_type=${{ inputs.build-type }} \ + ${{ inputs.conan-channel != '' && format('--user oss --channel {0}', inputs.conan-channel) || '' }} \ --build missing \ . if: ${{ inputs.testing == 'True' && inputs.tooling != 'Coverage' }} diff --git a/.github/workflows/conan_build.yml b/.github/workflows/conan_build.yml index dd3da6e76..db7511b49 100644 --- a/.github/workflows/conan_build.yml +++ b/.github/workflows/conan_build.yml @@ -4,38 +4,52 @@ on: workflow_dispatch: push: branches: - - stable/v4.x + - dev/v5.x pull_request: branches: - - stable/v4.x + - dev/v5.x jobs: - Build: - strategy: - fail-fast: false - matrix: - platform: ["ubuntu-24.04"] - build-type: ["Debug", "Release"] - malloc-impl: ["libc", "tcmalloc"] - prerelease: ["True", "False"] - tooling: ["Sanitize", "Coverage", "None"] - exclude: - - build-type: Debug - prerelease: "False" - - build-type: Debug - tooling: None - - build-type: Debug - malloc-impl: tcmalloc - - build-type: Release - malloc-impl: libc - - build-type: Release - tooling: Sanitize - - build-type: Release - tooling: Coverage + GccAddressSanitize: uses: ./.github/workflows/build_commit.yml with: - platform: ${{ matrix.platform }} - build-type: ${{ matrix.build-type }} - malloc-impl: ${{ matrix.malloc-impl }} - prerelease: ${{ matrix.prerelease }} - tooling: ${{ matrix.tooling }} + platform: "ubuntu-24.04" + build-type: "Debug" + malloc-impl: "libc" + prerelease: "True" + compiler: "gcc" + conan-channel: "dev" + tooling: "AddressSanitize" + + GccThreadSanitize: + uses: ./.github/workflows/build_commit.yml + with: + platform: "ubuntu-24.04" + build-type: "Debug" + malloc-impl: "libc" + prerelease: "True" + compiler: "gcc" + conan-channel: "dev" + tooling: "ThreadSanitize" + + GccCoverage: + uses: ./.github/workflows/build_commit.yml + with: + platform: "ubuntu-24.04" + build-type: "Debug" + malloc-impl: "libc" + prerelease: "True" + compiler: "gcc" + conan-channel: "dev" + tooling: "Coverage" + + GccRelease: + uses: ./.github/workflows/build_commit.yml + with: + platform: "ubuntu-24.04" + build-type: "Release" + malloc-impl: "tcmalloc" + prerelease: "False" + compiler: "gcc" + conan-channel: "dev" + tooling: "None" diff --git a/.github/workflows/version_change_check.yml b/.github/workflows/version_change_check.yml index 95e553967..284c38dc9 100644 --- a/.github/workflows/version_change_check.yml +++ b/.github/workflows/version_change_check.yml @@ -4,7 +4,7 @@ on: pull_request: types: [opened, edited, synchronize] branches: - - stable/v4.x + - dev/v5.x jobs: check-file: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c8d65fc3..2c3ba205e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,11 @@ cmake_minimum_required (VERSION 3.11) project (homeobject LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# sisl v14 removed cast-shorthand macros; restore until call sites are expanded. +add_compile_options(-include ${CMAKE_CURRENT_SOURCE_DIR}/cmake/sisl_cast_compat.hpp) if (NOT DEFINED CMAKE_BUILD_TYPE) set (CMAKE_BUILD_TYPE "Debug") diff --git a/README.md b/README.md index 23e86a1cb..80f423ae3 100644 --- a/README.md +++ b/README.md @@ -44,30 +44,17 @@ management and scheduling of Pgs is the concern of the Application. ## Build +HomeObject 5.x requires **C++23**, **Conan 2**, **sisl ^14.9**, and **homestore ^8.3**. Public async APIs use `sisl::async::task` (coroutines) and `std::expected`; Folly and Pistache are not used. + ### System Pre-requisites * CMake 3.13 or later -* conan 1.x (`pipx install conan~=1`) +* conan 2.x (`pipx install conan`) +* A C++23 toolchain (GCC/Clang) * libaio-dev (assuming Ubuntu) * uuid-dev (assuming Ubuntu) ### Dependencies -* SISL -``` -$ git clone https://github.com/eBay/sisl -$ cd sisl & ./prepare.sh && conan export . oss/master -``` - -* IOManager -``` -$ git clone https://github.com/eBay/iomanager -$ cd iomanager & ./prepare.sh && conan export . oss/master -``` - -* HomeStore -``` -$ git clone https://github.com/eBay/homestore -$ cd homestore && conan export . oss/master -``` +Build upstream packages in order (sisl → iomgr → nuraft_mesg → homestore), using `@oss/dev` when aligning with this tree. From sisl, run `./prepare_v2.sh` then `conan create` / `conan export` as upstream docs describe. ### Compilation ``` @@ -75,12 +62,18 @@ $ mkdir build $ cd build # Install all dependencies -$ conan install --build missing +$ conan install --build=missing # Build and Test $ conan build ``` +For memory-backend-only development: `-o homeobject/*:with_homestore=False` (sets `HOMEOBJECT_MEMORY_ONLY`). + +### Downstream API migration (4.x → 5.x) +* `AsyncResult` is a coroutine (`sisl::async::task`); drive it with `co_await`, `sisl::async::sync_get` (non-reactor threads only), or `detach` / `detach_then`. +* `Result` is `std::expected`: use `if (!r)` instead of `hasError()`; errors via `std::unexpected(...)`. +* `NullResult` success value is `std::monostate`, not `folly::Unit`. ## Contributing to This Project We welcome contributions. If you find any bugs, potential flaws and edge cases, improvements, new feature suggestions or discussions, please submit issues or pull requests. diff --git a/cmake/sisl_cast_compat.hpp b/cmake/sisl_cast_compat.hpp new file mode 100644 index 000000000..e27f27797 --- /dev/null +++ b/cmake/sisl_cast_compat.hpp @@ -0,0 +1,51 @@ +/********************************************************************************* + * Modifications Copyright 2017-2019 eBay Inc. + * + * Licensed 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 + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + *********************************************************************************/ +#pragma once + +// sisl v14 COMPATIBILITY SHIM (force-included for all homeobject TUs; see top-level CMakeLists.txt). +// +// sisl removed its 15 cast-shorthand macros from ("expand all call sites to the +// real C++ cast keyword"). homeobject still uses them across ~600 sites, so this restores them verbatim +// to keep homeobject compiling against sisl v14 while it is being modernized. Delete this header (and the +// force-include) once homeobject's call sites are expanded to the real cast keywords like sisl did. + +#include +#include +#include + +#ifndef r_cast +#define r_cast reinterpret_cast +#define s_cast static_cast +#define d_cast dynamic_cast +#define dp_cast std::dynamic_pointer_cast +#define sp_cast std::static_pointer_cast +#define uintptr_cast reinterpret_cast< uint8_t* > +#define voidptr_cast reinterpret_cast< void* > +#define c_voidptr_cast reinterpret_cast< const void* > +#define charptr_cast reinterpret_cast< char* > +#define c_charptr_cast reinterpret_cast< const char* > +#define int_cast static_cast< int > +#define uint32_cast static_cast< uint32_t > +#define int64_cast static_cast< int64_t > +#define uint64_cast static_cast< uint64_t > +#define size_cast static_cast< size_t > +#endif + +// sisl v14 keeps Clock and the get_elapsed_time_* helpers in namespace sisl, but homeobject uses them +// bare across ~100 sites (they used to arrive into scope transitively). Pull them to global scope here +// (this header is force-included after the std/cast section, so the symbols resolve). Same interim +// nature as the cast macros -- drop once homeobject qualifies these as sisl::. +#include +using sisl::Clock; // Clock is in namespace sisl; get_elapsed_time_* are global (no using needed). diff --git a/conanfile.py b/conanfile.py index f032c15c4..923e29c56 100644 --- a/conanfile.py +++ b/conanfile.py @@ -5,12 +5,12 @@ from conan.tools.files import copy from os.path import join -required_conan_version = ">=1.60.0" +required_conan_version = ">=2.0" class HomeObjectConan(ConanFile): name = "homeobject" - version = "4.3.5" + version = "5.0.0" homepage = "https://github.com/eBay/HomeObject" description = "Blob Store built on HomeStore" @@ -24,7 +24,7 @@ class HomeObjectConan(ConanFile): "shared": ['True', 'False'], "fPIC": ['True', 'False'], "coverage": ['True', 'False'], - "sanitize": ['True', 'False'], + "sanitize": ['address', 'thread', 'False'], } default_options = { @@ -50,12 +50,12 @@ def build_requirements(self): self.test_requires("gtest/[^1.17]") def requirements(self): - self.requires("sisl/[^13.2]", transitive_headers=True) - self.requires("homestore/[^7.5.12]") + self.requires("sisl/[^14.9]@oss/dev", transitive_headers=True) + self.requires("homestore/[^8.3]@oss/dev", transitive_headers=True) def validate(self): if self.info.settings.compiler.cppstd: - check_min_cppstd(self, 20) + check_min_cppstd(self, 23) def layout(self): self.folders.source = "." diff --git a/docs/adr/rebuild-homeobject-without-folly.md b/docs/adr/rebuild-homeobject-without-folly.md new file mode 100644 index 000000000..5ff113af6 --- /dev/null +++ b/docs/adr/rebuild-homeobject-without-folly.md @@ -0,0 +1,455 @@ +# Rebuild HomeObject on current HomeStore/sisl and remove Folly + +**Status**: Accepted +**Date**: 2026-09-16 +**Updated**: 2026-09-17 +**Scope**: HomeObject (`HomeObject/`), consuming local `HomeStore/` and `sisl/` in this workspace + +--- + +## Context + +sisl 和 HomeStore 已经完成 Folly 剥离,并把异步模型从 `folly::Future` / `folly::SemiFuture` 换成 C++23 协程 + `sisl::async::task`(底层是 stdexec `exec::task`)。HomeObject 4.3.4 仍停在旧栈: + +| 组件 | HomeObject 当前依赖 | 本仓库实际代码 | +|---|---|---| +| sisl | `sisl/[^13.2]`,C++20 | `14.9.0`,C++23,无 Folly;HTTP 为 cpp-httplib,无 Pistache | +| HomeStore | `homestore/[^7.5.12]` | `8.3.0`,C++23,无 Folly;传递依赖 `iomgr/[^13.0]`、`nuraft_mesg/[^5.0]` | +| Conan | `>=1.60.0` | sisl/HomeStore 要求 `>=2.0`;上游包走 `@oss/dev` | +| 异步 API | `folly::SemiFuture` / `Expected` | `sisl::async::task` + `std::expected` | + +HomeObject 不能只“换包版本”。公开头文件、manager 实现、HomeStore listener、GC/scrub、HTTP、测试夹具都直接依赖 Folly 类型和旧 HomeStore / Pistache API。继续链接 Folly 会把 Folly 重新引入已经干净的依赖图,也会和当前头文件对不上。 + +本 ADR 决定:以本目录下的 sisl 与 HomeStore 为唯一上游,把 HomeObject 重建到同一异步与错误模型,并清掉全部 Folly(以及 Pistache)依赖。采用 **C++23 + `std::expected` + `sisl::async::task` + Boost concurrent_flat_map + sisl 队列 + httplib HTTP**,公开 API 破坏性变更,版本到 5.x。 + +### 当前 Folly / 旧 HTTP 使用面(必须清零) + +仓库内约 35 个 `.cpp/.hpp` 引用 Folly,外加 Pistache handler。按用途分组: + +1. **公开 API 词汇表**(破坏性,下游必跟) + `src/include/homeobject/common.hpp`:`folly::Expected`、`folly::Unit`、`folly::SemiFuture`。 + `BlobManager` / `PGManager` / `ShardManager` 的 `Result` / `AsyncResult` / `NullAsyncResult` 全部建立在这组 typedef 上。 + +2. **Future 编排与延迟执行** + `thenValue` / `via` / `collectAll` / `collectAllUnsafe` / `Promise` / `makeSemiFuture` / `makeFuture`: + core managers、memory backend、homestore backend(blob/pg/shard、replication SM、snapshot、GC、scrub、HTTP GC)、以及几乎所有测试。 + `HomeObjectImpl::executor_` + `_defer()`:`folly::makeSemiFuture().via(executor_)`,由 `--executor immediate|cpu|io` 选择 `QueuedImmediateExecutor` / `getGlobalCPUExecutor` / `getGlobalIOExecutor`。 + +3. **并发容器与小对象** + - `folly::ConcurrentHashMap`:memory backend `btree_` / `index_`;`gc_manager` 的 `copied_blobs`、`m_pdev_gc_actors`;`scrub_manager` 的 `m_pg_scrub_ctx_map`、`m_pg_scrub_sb_map` + - `folly::small_vector`:`ho_repl_ctx::data_bufs_` + - `folly::MPMCQueue`:**仍在用** — `gc_manager` `pdev_gc_actor::m_reserved_chunk_queue`(容量 `reserved_chunk_num_per_pdev`,调用 `blockingWrite` / `blockingRead` / `read`)。替换后改 GC 逻辑,不再模拟 blocking。 + - `folly::EvictingCacheMap`:`hs_http_manager.hpp` 的 `gc_jobs_map_` / `scrub_jobs_map_`(容量 100) + - 本地 `homestore_backend/MPMCPriorityQueue.hpp`:scrub 任务队列,与 `sisl::MPMCPriorityQueue` 同源旧拷贝 + +4. **线程池** + - scrub:`m_scrub_executor`、`m_scrub_req_executor`(`IOThreadPoolExecutor`) + - GC:`m_gc_executor`、`m_egc_executor`(`IOThreadPoolExecutor`;正常 GC 与 emergent GC 两套池,带 rate limiter) + - `.via(getGlobalIOExecutor())` / `InlineExecutor`:scrub RPC、replication fetch、blob prefetch + +5. **初始化与杂项** + - `folly::Init`:`fixture_app.cpp`、`hs_repl_test_helper.hpp`、`test_heap_chunk_selector.cpp`、`test_package.cpp` + - `folly::Uri`:`hs_homeobject.cpp` `lookup_peer`(先拼 `http://` 再拆 host/port) + - `folly::makeUnexpected` / `v.hasError()`:遍布实现与测试 + +6. **Pistache HTTP**(sisl 14 已删除) + `hs_http_manager.hpp` 全部 handler 仍是 `Pistache::Rest::Request` + `Pistache::Http::ResponseWriter`。sisl 现接口是 `sisl::http_handler` = `httplib::Server::Handler`(`httplib::Request const&`, `httplib::Response&`),经 `ioenvironment.get_http_server()->setup_route(...)` 注册。 + 测试侧同样未迁:`homeobj_fixture_http.hpp` 是 Pistache **client**;`hs_repl_test_helper.hpp` 的 `start_http_server` / `/metrics` 仍绑 Pistache method 与 `ResponseWriter`。`test_homestore_backend_dynamic.cpp` 无条件 include 该 client。 + +### HomeStore 接口已经变了,HomeObject 还没跟上 + +| HomeObject 现状 | 当前 HomeStore | +|---|---| +| `#include ` 等 `.h` | 公开头全部是 `.hpp`(`blk.hpp`、`repl_dev.hpp`、`chunk_selector.hpp`、`vchunk.hpp`、`crc.hpp`) | +| `homestore::ReplDevListener` / `ReplDev` | `repl_dev_listener` / `repl_dev` | +| `homestore::ReplResult` / `AsyncReplResult<>` | `homestore::result` / `async_result` / `async_status` | +| `folly::Future on_fetch_data(...)` | `sisl::async::task on_fetch_data(...)` | +| `folly::Future CPCallbacks::cp_flush` | `sisl::async::task cp_flush` | +| `get_blk_alloc_hints` 返回 `ReplResult` | `result`(错误是 `std::error_condition`,可与 `ReplServiceError` 比较) | +| `create_snapshot` 返回 `AsyncReplResult<>` | `async_status`(协程,`co_return homestore::ok()`) | +| `MultiBlkId` | `multi_blk_id` | +| `async_alloc_write(..., false /* part_of_batch */, tid)` | `async_alloc_write(..., io_batch* batch = nullptr, tid)` | +| `create_repl_dev(...).thenValue(...)` | `co_await hs()->repl_service().create_repl_dev(...)` 得到 `result>` | +| `data_service().async_read(...).thenValue(...)` | `co_await data_service().async_read(...)` → `iomgr::io_result` | + +HomeStore 测试用 `sisl::async::sync_get` 阻塞协程;生产路径用 `co_await`,非协程回调用 `sisl::async::detach` / `detach_then`。HomeObject 必须同一套。 + +sisl v14 删掉了 `r_cast` / `s_cast` / `uint32_cast` 等 15 个宏。HomeObject 仍在 `replication_state_machine.hpp`、`hs_blob_manager.cpp`、`index_kv.hpp` 等处使用。HomeStore 用 force-include `sisl_cast_compat.hpp` 过渡;HomeObject 同样 **先 force-include,随后在改过的文件里顺手展开**。 + +--- + +## Decision + +### 1. 上游锁定:本地 sisl + 本地 HomeStore,不再走 Folly + +- HomeObject 只消费本仓库的 `sisl` 14.9.x 和 `homestore` 8.3.x,以及它们已经无 Folly 的传递依赖:`iomgr/[^13.0]`、`nuraft_mesg/[^5.0]`。 +- `conanfile.py` 禁止再直接或传递依赖 `folly` 或 `pistache`。验收:`conan graph info` / 链接命令行中无二者;源码 `rg folly` / `rg Pistache` 为零(注释允许 “formerly folly”)。 +- C++ 标准升到 **23**。`check_min_cppstd(self, 23)`;`required_conan_version = ">=2.0"`。 +- HomeObject 主版本 **5.0.0**。不在 4.x 做 ABI / Folly typedef shim。 +- 开发阶段:Conan editable / `path=` 指向本仓库的 sisl 与 HomeStore;channel 与上游一致时用 `@oss/dev`。 + +### 2. 公开 API:`std::expected` + `sisl::async::task` + +`src/include/homeobject/common.hpp` 只保留: + +```cpp +#include +#include +#include // 调用方需要 C++23。stdexec 已由 sisl 14.9 requires("stdexec/25.09") 传到 sisl::sisl,memory-only 也会带上,不需要再经 homestore/iomgr。 + +namespace homeobject { + +template < class E > +class Manager { +public: + template < typename T > + using Result = std::expected< T, E >; + template < typename T > + using AsyncResult = sisl::async::task< Result< T > >; + + using NullResult = Result< std::monostate >; + using NullAsyncResult = AsyncResult< std::monostate >; +}; + +} // namespace homeobject +``` + +不要 `#include `。那个头是 `std::expected`,HomeObject 业务错误仍是 `BlobError` / `PGError` / `ShardError`(带 `current_leader`),不能塞进 `error_condition`。 + +| 旧 | 新 | +|---|---| +| `folly::Expected` | `std::expected`:`hasError()` → `!r`;成功值 `*r` / `r.value()` | +| `folly::Unit` | `std::monostate` | +| `folly::makeUnexpected(e)` | `std::unexpected(e)` | +| `folly::SemiFuture>` | `sisl::async::task>` | +| `future.get()`(测试 / 下游同步调用) | `sisl::async::sync_get(std::move(task))`,**仅非 reactor 线程** | +| `thenValue` 链 | 函数写成 coroutine,`co_await` 下游。未 `co_await` / `detach` / `sync_get` 的 `task` **不会跑** | + +HomeStore 的 `ReplServiceError` 仍经 `toPgError` / `toBlobError` / `toShardError` 翻译。增加 `std::error_condition` 重载:用 `r.error() == ReplServiceError::NOT_LEADER` 分支,或 `static_cast(r.error().value())`(仅当 `r.error().category()` 是 replication category)。 + +### 3. `_defer` / `--executor`:删除,不保留 Folly 式 hop + +`_defer()` 的唯一作用是把后续 `thenValue` 扔到 Folly executor。公开 API 改为 coroutine 之后: + +- 删除 `HomeObjectImpl::executor_` 和 `_defer()`。 +- 删除 `--executor immediate|cpu|io`(`SISL_OPTION_GROUP(homeobject, ...)`)。`MemoryTestCPU` / `MemoryTestIO` 合并为一条 `memory_test`,不保留被忽略的 flag。 +- manager 正面(`get_shard` / `list_shards` 等)直接 `co_return` 同步 `Result`,或 `co_await` 真正的 backend `task`。不需要为“切线程”再包一层。 +- 若某条路径必须离开调用线程,只在那一条上 `co_await` iomgr hop,不要恢复全局 executor 选择。 + +### 4. 内部异步:coroutine-first;Promise 用 `value_awaitable` + +| 场景 | 替换 | +|---|---| +| 公开/内部 async 方法 | `sisl::async::task` + `co_return` | +| Raft 提交后交回调用方(今日 `repl_result_ctx::promise_`) | `sisl::async::value_awaitable< Result >`(单 waiter)。多 waiter 才用 `shared_awaitable` | +| 非协程回调里启动协程 | `sisl::async::detach` / `detach_then` | +| N 路并发 | `sisl::async::when_all(std::vector>)` → `task>`。不 short-circuit。`T` **必须能默认构造**(子 task 抛异常时该槽 default-init)。元素类型只用 `expected<...>` / `iomgr::io_result` / `monostate`,不要用 Folly `Try` 替身 | +| 立刻成功的 CP flush | `co_return true;` | +| memory backend | 同样返回 `task`,函数写成 coroutine | + +`repl_result_ctx`: + +```cpp +template < typename T > // T = Manager::Result,例如 ShardManager::Result +struct repl_result_ctx : public ho_repl_ctx { + sisl::async::value_awaitable< T > done_; + + sisl::async::task< T > result() { co_return co_await sisl::async::await_value_ref(done_); } + + void set_ok() { done_.complete(T{std::monostate{}}); } // NullResult:今日 setValue(folly::Unit()) + void set_ok(typename T::value_type v) { done_.complete(T{std::move(v)}); } + void set_err(typename T::error_type e) { done_.complete(T{std::unexpected(std::move(e))}); } +}; +``` + +规则: + +- `complete` 收的是 `T`(即 `expected`),**不能** `complete(std::unexpected(...))`。无参 `set_ok()` 只对 `NullResult`(`expected`)编译通过。 +- `value_awaitable` 不可移动、恰好 complete 一次。漏 complete → 调用方永远挂起;两次 complete → UB。 +- `on_commit` / `on_error` / rollback 路径都必须 complete。若调用方尚未 `co_await result()`,对象仍须活着(继续用 `intrusive_ptr`)。 +- 默认单 waiter。不要无故上 `shared_awaitable`。 + +### 5. 执行器:去掉 Folly 线程池,落到 iomgr(GC 额外约束) + +| 旧 | 新 | +|---|---| +| scrub 两个 `IOThreadPoolExecutor` | 在 iomgr worker 上 `detach` 的 `task`;reactor 上取任务用 `try_pop`,阻塞 `pop` 只留给非 reactor worker | +| GC / eGC 两个 `IOThreadPoolExecutor` | **默认:每个 pdev 两条 `std::jthread`(normal / emergent)跑 actor 循环**,循环里可以 `RateLimiter`。取 reserved chunk 用非阻塞 `read`:失败则本任务失败返回,不要阻塞等队列。循环内的磁盘 IO 仍 `co_await` HomeStore `async_read` / `async_alloc_write`(iomgr 负责 hop)。不要再引入 Folly 或第三套通用线程池库。验收以 `homestore_test_gc` 吞吐不显著差于现状为准。 | +| `.via(getGlobalIOExecutor())` | 删除;`co_await` HomeStore/iomgr IO 时由 iomgr hop | +| `.via(InlineExecutor)` | 删除 | +| `folly::Init` | 删除;测试只留 `SISL_OPTIONS_LOAD` + `SISL_LOGGING_INIT` | + +### 6. 容器与杂项 + +| 旧 | 新 | 注意 | +|---|---|---| +| `folly::small_vector` | `boost::container::small_vector` | 与 HomeStore `blkid_list_t` 一致 | +| `folly::ConcurrentHashMap` | `boost::concurrent_flat_map`(``) | Boost 1.85,无新依赖。见下节 API。不要用 sisl `SimpleHashMap` | +| GC `folly::MPMCQueue` | `sisl::BoundedMPMCQueue`,并改 GC 逻辑 | **不要** mutex/cv blocking 包装。sisl 只有非阻塞 `write`/`read`/`sizeGuess`。`chunk_id_t` 是 trivially copyable。调用约定见下。 | + +`m_reserved_chunk_queue` 调用约定(改 GC,不模拟 Folly blocking): + +| 旧 | 新 | +|---|---| +| `blockingWrite(id)`(`add_reserved_chunk`、回收路径、`on_gc_task_completed`) | `while (!q.write(id)) {}` — 满则自旋重试直到成功。队列容量是 reserved chunk 数,正常不应长期满;不要丢 chunk、不要 cv 等待 | +| `blockingRead(out)`(`process_gc_task` 取 `move_to_chunk`) | `if (!q.read(out)) { 本任务失败并返回 false; }` — 空则直接失败,不要阻塞。今日注释已写明线程数≈reserved 数时 `blockingRead` 实际立刻返回;没有 reserved chunk 就失败比挂起更清楚 | +| `read(out)`(`handle_recovered_gc_task` 扫队列找 `move_to`/`move_from`) | 保持非阻塞循环:`for (; q.read(chunk_id);)` | + +| HomeObject `MPMCPriorityQueue` | `sisl::MPMCPriorityQueue`(``) | 删除本地头、CMake 条目、`MPMCPriorityQueueTest` | +| `folly::EvictingCacheMap(100)` | `sisl::LruMap`(``,容量 100) | 就是 list + hashmap 的有界缓存,无新依赖。`set` / `get` / 迭代与现用法一致;`get` miss 返回 `Value{}`(`shared_ptr` 为 `nullptr`)。**`get` 不刷新 LRU**(只有 `set` 会提到队首),和「只留最近 100 个 job」按写入计一致。容器本身不是线程安全的,继续用现有 `gc_job_mutex_` / `scrub_job_mutex_`。不要用 `sisl::cache` 的 `LruEvictor` / `SimpleHashMap`。 | +| `folly::Uri` | 手拆 `host:port` | `HomeObjectApplication::lookup_peer` 只保证 `std::string`。现有 fixture(`hs_repl_test_helper`)返回 `127.0.0.1:`。解析规则:去掉可选的 `http://` / `https://` 前缀,按最后一个 `:` 拆 host 与端口;`[ipv6]:port` 若出现再补。不要加 `boost::urls`。 | + +`boost::concurrent_flat_map` 对照(HomeObject 现网全是 iterator 风格): + +| Folly `ConcurrentHashMap` | `boost::concurrent_flat_map` | +|---|---| +| `auto it = m.find(k); if (it != m.end()) use(it->second);` | `bool hit = m.cvisit(k, [&](auto const& p) { use(p.second); });` | +| `auto [it, ok] = m.try_emplace(k, v);` | `bool ok = m.try_emplace(k, v);`(无 iterator) | +| `m.insert(k, v)` / `m.emplace(...)` | `m.emplace(k, v)` / `m.insert({k, v})` | +| `m.erase(k)` / `m.erase(it)` | `m.erase(k)` | +| `for (auto& e : m)` | 若回调里还要改同一 map:先快照。范例:`HomeStore/src/lib/replication/repl_dev/raft_repl_dev.cpp` 的 `RaftReplDev::gc_repl_reqs`、`clear_chunk_req`;`raft_state_machine.cpp` 的 `RaftStateMachine::iterate_repl_reqs` | +| `assign_if_equal(k, expected, desired)` | `m.visit(k, [&](auto& p) { if (p.second == expected) p.second = desired; });` | +| 持有 `it->second` 跨过下一次 map 操作 | 禁止。`shared_ptr` / `unique_ptr` 在回调里拷/移出去再用 | + +单 key、不遍历时不必快照,用 `cvisit` / `visit` / `erase_if` / `try_emplace`(同文件 `link_lsn_to_req` / `unlink_lsn_to_req`)。 + +### 7. HTTP:Pistache → sisl / httplib + +- handler 签名改为 `void(httplib::Request const&, httplib::Response&)`,经 `ioenvironment.get_http_server()->setup_route(sisl::http_method::Get, "/...", handler)` 注册(与 `sisl/http/http_server.hpp`、iomgr `with_http_server()` 一致)。 +- 读 query / body、写 JSON / status 改用 httplib API,不要再 include Pistache。 +- `trigger_gc_for_pg` 返回 `sisl::async::task`;HTTP 线程用 `detach_then` 填 `Response`,**禁止**在 HTTP/iomgr 线程上 `sync_get`。 + +### 8. 对齐 HomeStore 8.x 头文件与调用 + +1. include:`repl_dev.h` → `repl_dev.hpp`,`repl_decls.h` → `repl_decls.hpp`,`blk.h` → `blk.hpp`,`chunk_selector.h` → `chunk_selector.hpp`,`vchunk.h` → `vchunk.hpp`,`crc.h` → `crc.hpp`。 +2. 类型名:`ReplDevListener` → `repl_dev_listener`,`ReplDev` → `repl_dev`,`MultiBlkId` → `multi_blk_id`。 +3. 虚函数按 `repl_dev.hpp` / `cp_mgr.hpp`:`on_fetch_data`、`create_snapshot`、`get_blk_alloc_hints`、`cp_flush`。 +4. `create_repl_dev` / `replace_member` / `remove_member` / `flip_learner_flag`:`co_await`,失败 `co_return std::unexpected(toPgError(r.error()))`。 +5. `async_read` / `async_write` / `async_free_blks`:`co_await`,类型为 `iomgr::io_result`。 +6. **`async_alloc_write` 最后一个 batch 参数从 `bool part_of_batch` 改为 `io_batch* batch = nullptr`。** 现有 `false /* part_of_batch */` 全部改成省略或显式 `nullptr`。 +7. sisl cast 宏:复制 HomeStore `sisl_cast_compat.hpp` 并 `-include`。 + +### 9. 构建与发布 + +`HomeObject/conanfile.py`: + +- `self.requires("sisl/[^14.9]", transitive_headers=True)`(与上游 channel 对齐时加 `@oss/dev`) +- `self.requires("homestore/[^8.3]")` +- 不直接 requires folly / pistache(homestore 已拉 iomgr、nuraft_mesg) +- `check_min_cppstd(self, 23)`;`required_conan_version = ">=2.0"` +- 根 `CMakeLists.txt`:`CMAKE_CXX_STANDARD 23` + +本地顺序:sisl → iomgr → nuraft_mesg → homestore → homeobject(Conan create / editable)。本 `no-folly` 超仓可 `add_subdirectory` 做开发,不替代 Conan 发布。 + +CI 在上游包未发布前,workflow 先 `conan create` 三个上游。 + +### 10. 不做的范围 + +- 不把 HomeObject 业务错误改成 `std::error_condition`。 +- 不重写 GC 优先级调度、scrub 协议、on-disk superblk。 +- 不合并 memory / homestore backend。 +- 不引入 Folly / Abseil / 自研并发 hashmap;只用 `boost::concurrent_flat_map`。 +- 不保留本地 `MPMCPriorityQueue` 分叉。 +- 不为 4.x 提供 Folly typedef 兼容层。 +- 不把 `sisl/result.hpp` 引进公开头。 +- 不新增 `boost::urls` 或通用线程池库。 + +--- + +## 分阶段步骤 + +原则:Phase 0 只改构建,允许尚未编译通过。**Phase 1 起每个 phase 结束必须能编译并跑通该 phase 列出的测试**(未迁模块可暂时不加入 `add_subdirectory` / `add_test`,但禁止继续链接 Folly)。 + +### Phase 0 — 构建脚手架 + +- `CMakeLists.txt` / `conanfile.py`:C++23、sisl 14.9、homestore 8.3、Conan 2。 +- 增加 sisl cast compat force-include。 +- **不做** 全库 `.h` → `.hpp` 重命名(并进 Phase 2,与 listener / `async_*` 一起改,避免声明与函数体一半新一半旧)。 + +**完成标准**:`conan graph info` 无 folly、无 pistache;工具链按 C++23 配置成功。 + +### Phase 1 — 公开头 + core + memory(一次闭环) + +公开 typedef 一变,core / memory 必须一起改,否则没有可编译的翻译单元。 + +**文件**:`common.hpp`;`homeobject_impl.hpp/.cpp`(删 `_defer` / executor);`blob_manager.cpp` `pg_manager.cpp` `shard_manager.cpp`;memory backend 全部;`tests/fixture_app.cpp` `*ManagerTest.cpp` `BlobManagerTest.cpp`。 + +**怎么改** + +1. Decision 2 的 typedef。 +2. 删除 `_defer` / `--executor`;正面 API 写成 coroutine。 +3. `makeUnexpected` → `std::unexpected`。 +4. memory:`concurrent_flat_map`;拆掉 `WITH_SHARD` / `IF_BLOB_ALIVE` iterator 宏。 +5. 测试:`.get()` → `sync_get`;`collectAll` → `sync_get(when_all(...))`;去掉 `folly::Init`。 +6. 本阶段 **不编译** `homestore_backend`。在 `src/lib/CMakeLists.txt` 加 `option(HOMEOBJECT_MEMORY_ONLY "Build memory backend only" OFF)`,Phase 1 CI/本地打开它;Phase 2 起关掉。不要靠手改注释,合并时容易忘恢复。 + +**完成标准**:`homeobject_memory` 链接;`memory_test` 通过;一个只 include `homeobject/blob_manager.hpp` 的 TU **没有 Folly include path** 也能编译。 + +### Phase 2 — HomeStore listener 与复制路径 + +**文件**:`replication_state_machine.hpp/.cpp`;`hs_homeobject.hpp/.cpp`(含 `folly::Uri` → 手拆 host/port);`hs_blob/pg/shard_manager.cpp`;`hs_cp_callbacks.cpp`;`index_kv.hpp/.cpp`;`heap_chunk_selector.h` include;`replication_message.hpp`。 + +**关键改写** + +1. `repl_dev_listener`;`on_fetch_data` coroutine + `co_return`,删除 “bypass later thenValue” 的异常技巧。 +2. `create_snapshot`:`co_return homestore::ok();` +3. `cp_flush`:`task`,`co_return true;` +4. `create_pg`:`co_await create_repl_dev` +5. blob get:`co_await async_read` +6. `async_alloc_write(..., nullptr, tid)`(不再传 `bool`) +7. `toPgError(std::error_condition)` 等重载 +8. `repl_result_ctx` 按 Decision 4(含无参 `set_ok()`) + +关掉 `HOMEOBJECT_MEMORY_ONLY`。`hs_homeobject.hpp` **直接 include** `gc_manager.hpp` / `scrub_manager.hpp`,并持有 `GCManager` / `ScrubManager` / `HttpManager`。只把三个 `.cpp` 移出 `target_sources` **不能** 让 `hs_homeobject.cpp` 编过。本阶段必须: + +- 把 `gc_manager.hpp` / `scrub_manager.hpp` / `hs_http_manager.hpp` 里的 Folly 类型改成前向声明或已迁类型(`concurrent_flat_map`、`task`、`sisl::MPMCPriorityQueue`),使 include 不再拉 Folly;**或** +- 把这三个模块的头+实现一并做到能编译(HTTP 实现可仍是空 stub,头不能再出现 Pistache/Folly)。 + +`pg_blob_iterator.cpp` 若本阶段未改,也要从 `homeobject_homestore` 的 sources 拿掉,否则 `thenValue` / `async_read` 会编不过。 + +**完成标准**:`homestore_test_pg` / `_shard` / `_blob` / `_misc` 通过。dynamic / GC / scrub / `enable_http` 测试本阶段不跑。 + +### Phase 3 — snapshot、GC、scrub(工作量最大) + +**snapshot_receive_handler.cpp**:`cp_fut` 改为 `co_await cp_mgr().trigger_cp_flush(...)` 或 `shared_awaitable`;`collect_all_futures` → `when_all`。空 fan-out 必须覆盖:`when_all({})` 会立刻 `co_return` 空 vector。 + +**gc_manager**(头+实现;含 Folly Promise,不只是容器) + +- CHM → `concurrent_flat_map`;再改同一 map 时按 `gc_repl_reqs` 先快照。 +- `m_reserved_chunk_queue`:`sisl::BoundedMPMCQueue`。`write` 失败自旋重试直到成功;`read` 失败则 GC 任务返回 `false`。不要 cv blocking 包装。 +- 两个 `IOThreadPoolExecutor` → 每 pdev 两条 `std::jthread`(Decision 5)。 +- `add_gc_task` / `process_gc_task` / `submit_gc_task` / `gc_task_guard` 上的 `folly::Promise` / `SemiFuture` → `value_awaitable` 或 `task`,完成规则与 `repl_result_ctx` 相同。 +- `async_read` / `async_alloc_write` then 链 → `task` / `when_all`。 +- `hs_gc_tests.cpp`:`collectAllUnsafe` → `sync_get(when_all(...))`。 + +**scrub_manager** + +- 队列改 `sisl::MPMCPriorityQueue`;删除本地头、重复单测,以及头文件里残留的 `#include `。 +- 去掉两个 Folly 线程池。 +- Promise → `value_awaitable` + `task`;取消用错误值 complete。 +- `when_all` 聚合 `expected`。 + +**完成标准**:`homestore_test_gc`、shard-race、`FetchDataWithOriginatorGC`、`homestore_test_scrubber` 通过。dynamic 测试 **仅** `enable_http=false` 的 ReplaceMember / BaselineResync / restart;CMake 上把 `homeobj_fixture_http.hpp` 从 dynamic 目标拿掉(或 `#if`),直到 Phase 4。 + +### Phase 4 — HTTP(服务端 + 测试客户端 + fixture 路由) + +- `hs_http_manager.hpp/.cpp`:Pistache → httplib;`EvictingCacheMap` → `sisl::LruMap`(容量 100,外锁保留)。`hs_http_manager.cpp` 里不止 `trigger_gc_for_pg`(`InlineExecutor`、`thenError`、`collectAllUnsafe` 聚多 PG),全部改成 `task` + `when_all` + `detach_then`。 +- `homeobj_fixture_http.hpp`:Pistache client → `httplib::Client`(`get`/`post`/`del` 同步即可)。 +- `hs_repl_test_helper.hpp`:`start_http_server` / `/metrics` 改为 `sisl::http_method` + httplib handler,去掉 `Pistache::Rest::Request`。 +- CMake 把 HTTP helper 加回 dynamic 目标。 + +**完成标准**:`enable_http=true` 的 dynamic 路径通过;`rg -i pistache HomeObject --glob '!docs/**'` 为空。 + +### Phase 5 — 测试夹具扫尾与发布 + +- `hs_repl_test_helper.hpp`、`test_package.cpp`、`homeobj_fixture.hpp`、各 `hs_*_tests.cpp`:无 `.get()` on SemiFuture,无 `hasError()`。 +- `rg folly` / `rg -i pistache` 仅注释与 docs。 +- CI Debug+Sanitize 与 Release 跑完整 `add_test`(已 `RUN_SERIAL`)。 +- README / changelog:C++23、无 Folly、`AsyncResult` 是 coroutine。 +- 下游 10 行迁移:`sync_get` / `co_await`;`if (!r)`。 +- sisl 14.9、homestore 8.3 发布后再发 HomeObject 5.x。 + +--- + +## 文件级修改清单 + +### 构建 + +| 文件 | 修改 | +|---|---| +| `conanfile.py` | 5.0.0、cppstd 23、sisl 14.9、homestore 8.3、Conan 2、无 folly/pistache | +| `CMakeLists.txt`、`src/CMakeLists.txt`、`src/lib/CMakeLists.txt` | C++23;cast compat;`HOMEOBJECT_MEMORY_ONLY` | +| `.github/workflows/*` | 先编上游或指向 `@oss/dev` | + +### 公开 API / core / memory + +| 文件 | 修改 | +|---|---| +| `src/include/homeobject/common.hpp` | 去掉 Folly;`Result` / `AsyncResult` | +| `homeobject_impl.hpp/.cpp` | 删除 `_defer`、executor、`--executor` | +| `blob_manager.cpp` `pg_manager.cpp` `shard_manager.cpp` | coroutine;`std::unexpected` | +| `mem_homeobject.hpp/.cpp` `mem_*_manager.cpp` | `concurrent_flat_map` + coroutine | + +### HomeStore backend + +| 文件 | 修改 | +|---|---| +| `replication_state_machine.hpp/.cpp` | listener、on_fetch_data、create_snapshot、`repl_result_ctx`、small_vector | +| `hs_homeobject.hpp/.cpp` | 类型/include、`lookup_peer` 手拆 URI | +| `hs_blob_manager.cpp` `hs_pg_manager.cpp` `hs_shard_manager.cpp` | Future + `async_*` + `io_batch*` | +| `hs_cp_callbacks.cpp` | `task` | +| `index_kv.hpp/.cpp` `pg_blob_iterator.cpp` | expected + `async_read` | +| `snapshot_receive_handler.cpp` | `when_all` + CP | +| `gc_manager.hpp/.cpp` | flat_map、BoundedMPMCQueue(write 重试 / read 失败即失败)、`jthread`、`Promise` / `add_gc_task` | +| `scrub_manager.hpp/.cpp` | flat_map、sisl 优先队列、线程池、Promise;去掉残留 `MPMCQueue.h` | +| 删除 `MPMCPriorityQueue.hpp` | 改用 sisl | +| `hs_http_manager.hpp/.cpp` | httplib、`sisl::LruMap`、`when_all` / `detach_then`(不止 `trigger_gc_for_pg`) | +| `heap_chunk_selector.h` | include `.hpp` | +| `replication_message.hpp` | `crc.hpp` | + +### 测试 + +| 文件 | 修改 | +|---|---| +| `tests/fixture_app.cpp` `*ManagerTest.cpp` `BlobManagerTest.cpp` | Init、`sync_get`、`when_all` | +| `homestore_backend/tests/hs_repl_test_helper.hpp` `homeobj_fixture.hpp` | Init、`hasError`、`.get()`;`/metrics` 去 Pistache | +| `homeobj_fixture_http.hpp` | Pistache client → `httplib::Client` | +| `hs_gc_tests.cpp` `hs_pg/shard/blob_tests.cpp` `test_homestore_backend_dynamic.cpp` `hs_scrubber_tests.cpp` | `collectAllUnsafe`、`hasError` | +| `test_heap_chunk_selector.cpp` `test_package.cpp` | Init | +| 删除 `test_mpmc_priority_queue.cpp` 与 `MPMCPriorityQueueTest` | 单测归 sisl | + +--- + +## 语义陷阱 + +1. **Lazy vs eager**:未 `co_await` / `sync_get` / `detach` 的 `task` 不跑。 +2. **不要在 iomgr / HTTP 线程上 `sync_get`**。 +3. **`when_all` 吞异常**:失败必须在 `expected` / `io_result` 里;`T` 要能默认构造。`when_all({})` 立刻成功并返回空 vector,零 peer / 零任务路径必须测。 +4. **`value_awaitable::complete` 是 noexcept**:错误放进 `expected` 再 complete;每条 commit/error/rollback 恰好一次。 +5. **`std::expected` 没有 `hasError()` / `hasValue()`**。 +6. **`toPgError` 必须吃 `std::error_condition`**,不能只留 `ReplServiceError` 重载。 +7. **`sisl::MPMCPriorityQueue::pop()` 阻塞**;reactor 上用 `try_pop()`。不要和 `BoundedMPMCQueue` 搞混。 +8. **`BoundedMPMCQueue` 没有 blocking API**。GC **不要** cv 包装:`write` 失败自旋重试到成功(禁止忽略返回值的单次 `write`);`read` 失败直接让任务失败返回 `false`(禁止当永远成功)。 +9. **`concurrent_flat_map` 无 iterator**;`visit_all` 持分片锁,回调内禁止再进同一 map。范例见 `raft_repl_dev.cpp` `gc_repl_reqs` / `clear_chunk_req`,`raft_state_machine.cpp` `iterate_repl_reqs`。 +10. **`async_alloc_write` 的 `bool` 已不存在**,传 `false` 会编不过。 + +--- + +## Consequences + +**正面** + +- 与 sisl 14 / HomeStore 8 / iomgr 13 同一异步、错误、HTTP 模型。 +- 产品构建不再带 Folly / Pistache。 +- `on_fetch_data` / blob get 等控制流比 `thenValue` 链清楚。 + +**负面 / 风险** + +- 公开 API 破坏,下游必须改 `sync_get` / `co_await`。 +- GC actor 改为 `jthread` 后,rate limiter 与「reserved 队列空则任务失败」和今日 Folly 池 + `blockingRead` 不同;以 `homestore_test_gc` 为准。 +- scrub 并发模型同样会变,用 `homestore_test_scrubber` 对比。 +- `visit` 重入会死锁;memory backend 宏必须拆。 +- 漏 `detach` / 漏 `complete` 是静默挂起,review 时专项查。 + +**回滚** + +- 改动只进 5.x。失败则产品继续 4.3.x + homestore 7.x + sisl 13 + Folly。不提供双栈。 + +--- + +## 验收清单 + +- [ ] `rg '#include { return ctx.begin(); } template < typename FormatContext > - auto format(homeobject::BlobError const& err, FormatContext& ctx) { + auto format(homeobject::BlobError const& err, FormatContext& ctx) const { if (err.current_leader.has_value()) { return fmt::format_to(ctx.out(), "Code={}, Leader={}", err.code, err.current_leader.value()); } else { diff --git a/src/include/homeobject/common.hpp b/src/include/homeobject/common.hpp index 63eff1305..fa2dd39c7 100644 --- a/src/include/homeobject/common.hpp +++ b/src/include/homeobject/common.hpp @@ -1,16 +1,12 @@ #pragma once -#include -#include -#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuninitialized" -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#pragma GCC diagnostic pop +#include +#include +#include +#include +#include #include -#include SISL_LOGGING_DECL(homeobject); @@ -49,12 +45,12 @@ template < class E > class Manager { public: template < typename T > - using Result = folly::Expected< T, E >; + using Result = std::expected< T, E >; template < typename T > - using AsyncResult = folly::SemiFuture< Result< T > >; + using AsyncResult = sisl::async::task< Result< T > >; - using NullResult = Result< folly::Unit >; - using NullAsyncResult = AsyncResult< folly::Unit >; + using NullResult = Result< std::monostate >; + using NullAsyncResult = AsyncResult< std::monostate >; virtual ~Manager() = default; }; diff --git a/src/include/homeobject/homeobject.hpp b/src/include/homeobject/homeobject.hpp index c83671266..bfccb2012 100644 --- a/src/include/homeobject/homeobject.hpp +++ b/src/include/homeobject/homeobject.hpp @@ -97,7 +97,7 @@ struct formatter< homeobject::device_info_t > { } template < typename FormatContext > - auto format(homeobject::device_info_t const& device, FormatContext& ctx) { + auto format(homeobject::device_info_t const& device, FormatContext& ctx) const { std::string type; switch (device.type) { case homeobject::DevType::HDD: diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 147f5a380..f23285a6b 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -16,6 +16,10 @@ target_link_libraries(${PROJECT_NAME}_core list(GET sisl_INCLUDE_DIRS 0 SISL_TOP_INCLUDE) include ("${SISL_TOP_INCLUDE}/../cmake/settings_gen.cmake") -add_subdirectory(homestore_backend) +option(HOMEOBJECT_MEMORY_ONLY "Build memory backend only (skip homestore_backend)" OFF) + add_subdirectory(memory_backend) +if (NOT HOMEOBJECT_MEMORY_ONLY) + add_subdirectory(homestore_backend) +endif() add_subdirectory(tests) diff --git a/src/lib/blob_manager.cpp b/src/lib/blob_manager.cpp index d7b54a882..5c3148eac 100644 --- a/src/lib/blob_manager.cpp +++ b/src/lib/blob_manager.cpp @@ -6,29 +6,23 @@ std::shared_ptr< BlobManager > HomeObjectImpl::blob_manager() { return shared_fr BlobManager::AsyncResult< Blob > HomeObjectImpl::get(shard_id_t shard, blob_id_t const& blob_id, uint64_t off, uint64_t len, bool allow_skip_verify, trace_id_t tid) const { - return _get_shard(shard, tid) - .thenValue([this, blob_id, off, len, allow_skip_verify, tid](auto const e) -> BlobManager::AsyncResult< Blob > { - if (!e) return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); - return _get_blob(e.value(), blob_id, off, len, allow_skip_verify, tid); - }); + auto e = _get_shard(shard, tid); + if (!e) co_return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); + co_return co_await _get_blob(e.value(), blob_id, off, len, allow_skip_verify, tid); } BlobManager::AsyncResult< blob_id_t > HomeObjectImpl::put(shard_id_t shard, Blob&& blob, trace_id_t tid) { - return _get_shard(shard, tid) - .thenValue([this, blob = std::move(blob), tid](auto const e) mutable -> BlobManager::AsyncResult< blob_id_t > { - if (!e) return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); - if (ShardInfo::State::SEALED == e.value().state) - return folly::makeUnexpected(BlobError(BlobErrorCode::SEALED_SHARD)); - if (blob.body.size() == 0) return folly::makeUnexpected(BlobError(BlobErrorCode::INVALID_ARG)); - return _put_blob(e.value(), std::move(blob), tid); - }); + auto e = _get_shard(shard, tid); + if (!e) co_return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); + if (ShardInfo::State::SEALED == e.value().state) co_return std::unexpected(BlobError(BlobErrorCode::SEALED_SHARD)); + if (blob.body.size() == 0) co_return std::unexpected(BlobError(BlobErrorCode::INVALID_ARG)); + co_return co_await _put_blob(e.value(), std::move(blob), tid); } BlobManager::NullAsyncResult HomeObjectImpl::del(shard_id_t shard, blob_id_t const& blob, trace_id_t tid) { - return _get_shard(shard, tid).thenValue([this, blob, tid](auto const e) mutable -> BlobManager::NullAsyncResult { - if (!e) return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); - return _del_blob(e.value(), blob, tid); - }); + auto e = _get_shard(shard, tid); + if (!e) co_return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD)); + co_return co_await _del_blob(e.value(), blob, tid); } Blob Blob::clone() const { diff --git a/src/lib/blob_route.hpp b/src/lib/blob_route.hpp index 46239c754..70a0a7c41 100644 --- a/src/lib/blob_route.hpp +++ b/src/lib/blob_route.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "homeobject_impl.hpp" @@ -33,6 +34,19 @@ struct BlobRouteByChunk { }; #pragma pack() +// ADL hash_value for boost::hash / concurrent_flat_map (must live in the type's namespace). +inline std::size_t hash_value(BlobRoute const& r) noexcept { + return boost::hash_value(std::make_pair(r.shard, r.blob)); +} + +inline std::size_t hash_value(BlobRouteByChunk const& r) noexcept { + std::size_t seed = 0; + boost::hash_combine(seed, r.chunk); + boost::hash_combine(seed, r.shard); + boost::hash_combine(seed, r.blob); + return seed; +} + } // namespace homeobject namespace fmt { @@ -44,9 +58,9 @@ struct formatter< homeobject::BlobRouteByChunk > { } template < typename FormatContext > - auto format(homeobject::BlobRouteByChunk const& r, FormatContext& ctx) { - return fmt::v10::format_to(ctx.out(), "{:04x}:{:04x}:{:012x}:{:016x}", r.chunk, - (r.shard >> homeobject::shard_width), (r.shard & homeobject::shard_mask), r.blob); + auto format(homeobject::BlobRouteByChunk const& r, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{:04x}:{:04x}:{:012x}:{:016x}", r.chunk, (r.shard >> homeobject::shard_width), + (r.shard & homeobject::shard_mask), r.blob); } }; @@ -58,9 +72,9 @@ struct formatter< homeobject::BlobRoute > { } template < typename FormatContext > - auto format(homeobject::BlobRoute const& r, FormatContext& ctx) { - return fmt::v10::format_to(ctx.out(), "{:04x}:{:012x}:{:016x}", (r.shard >> homeobject::shard_width), - (r.shard & homeobject::shard_mask), r.blob); + auto format(homeobject::BlobRoute const& r, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{:04x}:{:012x}:{:016x}", (r.shard >> homeobject::shard_width), + (r.shard & homeobject::shard_mask), r.blob); } }; @@ -68,18 +82,24 @@ struct formatter< homeobject::BlobRoute > { template <> struct std::hash< homeobject::BlobRoute > { - std::size_t operator()(homeobject::BlobRoute const& r) const noexcept { - return boost::hash_value< homeobject::blob_id_t >(std::make_pair(r.shard, r.blob)); - } + std::size_t operator()(homeobject::BlobRoute const& r) const noexcept { return homeobject::hash_value(r); } }; template <> struct std::hash< homeobject::BlobRouteByChunk > { - std::size_t operator()(homeobject::BlobRouteByChunk const& r) const noexcept { - std::size_t seed = 0; - boost::hash_combine(seed, r.chunk); - boost::hash_combine(seed, r.shard); - boost::hash_combine(seed, r.blob); - return seed; - } + std::size_t operator()(homeobject::BlobRouteByChunk const& r) const noexcept { return homeobject::hash_value(r); } +}; + +// Explicit boost::hash specializations — required by boost::concurrent_flat_map's default Hash +// (ADL hash_value is not reliably found with this Boost/container_hash version). +namespace boost { +template <> +struct hash< homeobject::BlobRoute > { + std::size_t operator()(homeobject::BlobRoute const& r) const noexcept { return homeobject::hash_value(r); } +}; + +template <> +struct hash< homeobject::BlobRouteByChunk > { + std::size_t operator()(homeobject::BlobRouteByChunk const& r) const noexcept { return homeobject::hash_value(r); } }; +} // namespace boost diff --git a/src/lib/homeobject_impl.cpp b/src/lib/homeobject_impl.cpp index e2d5f1b36..4fc95c013 100644 --- a/src/lib/homeobject_impl.cpp +++ b/src/lib/homeobject_impl.cpp @@ -1,28 +1,11 @@ #include "homeobject_impl.hpp" -#include - -SISL_OPTION_GROUP(homeobject, - (executor_type, "", "executor", "Executor to use for Future deferal", - ::cxxopts::value< std::string >()->default_value("immediate"), "immediate|cpu|io")); - SISL_LOGGING_DEF(HOMEOBJECT_LOG_MODS) namespace homeobject { HomeObjectImpl::HomeObjectImpl(std::weak_ptr< HomeObjectApplication >&& application) : _application(std::move(application)) { - auto exe_type = SISL_OPTIONS["executor"].as< std::string >(); - std::transform(exe_type.begin(), exe_type.end(), exe_type.begin(), ::tolower); - - if ("immediate" == exe_type) [[likely]] - executor_ = &folly::QueuedImmediateExecutor::instance(); - else if ("io" == exe_type) - executor_ = folly::getGlobalIOExecutor(); - else if ("cpu" == exe_type) - executor_ = folly::getGlobalCPUExecutor(); - else - RELEASE_ASSERT(false, "Unknown Folly Executor type: [{}]", exe_type); - LOGI("initialized with [executor={}]", exe_type); + LOGI("HomeObjectImpl initialized"); } } // namespace homeobject diff --git a/src/lib/homeobject_impl.hpp b/src/lib/homeobject_impl.hpp index b905cbc96..6f57ac93c 100644 --- a/src/lib/homeobject_impl.hpp +++ b/src/lib/homeobject_impl.hpp @@ -135,8 +135,6 @@ class HomeObjectImpl : public HomeObject, /// Our SvcId retrieval and SvcId->IP mapping std::weak_ptr< HomeObjectApplication > _application; - folly::Executor::KeepAlive<> executor_; - /// mutable std::shared_mutex _pg_lock; std::map< pg_id_t, unique< PG > > _pg_map; @@ -145,8 +143,7 @@ class HomeObjectImpl : public HomeObject, std::map< shard_id_t, ShardIterator > _shard_map; /// - auto _defer() const { return folly::makeSemiFuture().via(executor_); } - folly::Future< ShardManager::Result< ShardInfo > > _get_shard(shard_id_t id, trace_id_t tid) const; + ShardManager::Result< ShardInfo > _get_shard(shard_id_t id, trace_id_t tid) const; public: explicit HomeObjectImpl(std::weak_ptr< HomeObjectApplication >&& application); diff --git a/src/lib/homestore_backend/CMakeLists.txt b/src/lib/homestore_backend/CMakeLists.txt index 39f4c3f23..c426330db 100644 --- a/src/lib/homestore_backend/CMakeLists.txt +++ b/src/lib/homestore_backend/CMakeLists.txt @@ -31,7 +31,6 @@ target_sources("${PROJECT_NAME}_homestore" PRIVATE hs_http_manager.cpp gc_manager.cpp scrub_manager.cpp - MPMCPriorityQueue.hpp $ ) target_link_libraries("${PROJECT_NAME}_homestore" PUBLIC @@ -61,28 +60,28 @@ add_subdirectory(tests) add_executable(homestore_test_pg) target_sources(homestore_test_pg PRIVATE $) target_link_libraries(homestore_test_pg PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestPg COMMAND homestore_test_pg --executor immediate --config_path ./ +add_test(NAME HomestoreTestPg COMMAND homestore_test_pg --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance:0 --override_config homestore_config.consensus.max_grpc_message_size:138412032) add_executable(homestore_test_shard) target_sources(homestore_test_shard PRIVATE $) target_link_libraries(homestore_test_shard PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestShard COMMAND homestore_test_shard --executor immediate --config_path ./ +add_test(NAME HomestoreTestShard COMMAND homestore_test_shard --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance:0 --override_config homestore_config.consensus.max_grpc_message_size:138412032) add_executable(homestore_test_blob) target_sources(homestore_test_blob PRIVATE $) target_link_libraries(homestore_test_blob PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestBlob COMMAND homestore_test_blob --executor immediate --config_path ./ +add_test(NAME HomestoreTestBlob COMMAND homestore_test_blob --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance:0 --override_config homestore_config.consensus.max_grpc_message_size:138412032) add_executable(homestore_test_misc) target_sources(homestore_test_misc PRIVATE $) target_link_libraries(homestore_test_misc PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestMisc COMMAND homestore_test_misc --executor immediate --config_path ./ +add_test(NAME HomestoreTestMisc COMMAND homestore_test_misc --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=0 --override_config homestore_config.consensus.max_grpc_message_size=138412032) @@ -91,7 +90,7 @@ add_executable(homestore_test_dynamic) target_sources(homestore_test_dynamic PRIVATE $) target_link_libraries(homestore_test_dynamic PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) add_test(NAME HomestoreTestReplaceMember - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=0 --override_config homestore_config.generic.repl_dev_cleanup_interval_sec=5 --override_config homestore_config.consensus.max_grpc_message_size=138412032 @@ -100,7 +99,7 @@ add_test(NAME HomestoreTestReplaceMember --gtest_filter=HomeObjectFixture.ReplaceMember) # To test both baseline & incremental resync functionality, we use 13 to minimize the likelihood of it being a divisor of the total LSN (currently 30) add_test(NAME HomestoreTestReplaceMemberWithBaselineResync - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=13 --override_config homestore_config.consensus.num_reserved_log_items=13 --override_config homestore_config.resource_limits.raft_logstore_reserve_threshold=13 @@ -111,7 +110,7 @@ add_test(NAME HomestoreTestReplaceMemberWithBaselineResync --override_config homestore_config.consensus.laggy_threshold=2000 --gtest_filter=HomeObjectFixture.ReplaceMember) add_test(NAME HomestoreTestBaselineResync - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=13 --override_config homestore_config.consensus.num_reserved_log_items=13 --override_config homestore_config.resource_limits.raft_logstore_reserve_threshold=13 @@ -122,7 +121,7 @@ add_test(NAME HomestoreTestBaselineResync --override_config homestore_config.consensus.laggy_threshold=2000 --gtest_filter=HomeObjectFixture.BaselineResync*) add_test(NAME HomestoreResyncTestWithFollowerRestart - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=13 --override_config homestore_config.consensus.num_reserved_log_items=13 --override_config homestore_config.resource_limits.raft_logstore_reserve_threshold=13 @@ -133,7 +132,7 @@ add_test(NAME HomestoreResyncTestWithFollowerRestart --override_config homestore_config.consensus.laggy_threshold=2000 --gtest_filter=HomeObjectFixture.RestartFollower*) add_test(NAME HomestoreResyncTestWithLeaderRestart - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config homestore_config.consensus.snapshot_freq_distance=13 --override_config homestore_config.consensus.num_reserved_log_items=13 --override_config homestore_config.resource_limits.raft_logstore_reserve_threshold=13 @@ -144,7 +143,7 @@ add_test(NAME HomestoreResyncTestWithLeaderRestart --override_config homestore_config.consensus.laggy_threshold=2000 --gtest_filter=HomeObjectFixture.RestartLeader*) #add_test(NAME HomestoreReplaceMemberRollbackTest -# COMMAND homestore_test_dynamic --executor immediate --config_path ./ +# COMMAND homestore_test_dynamic --config_path ./ # --override_config homestore_config.consensus.snapshot_freq_distance=13 # --override_config homestore_config.consensus.num_reserved_log_items=13 # --override_config homestore_config.resource_limits.raft_logstore_reserve_threshold=13 @@ -157,7 +156,7 @@ add_test(NAME HomestoreResyncTestWithLeaderRestart # GC tests add_test(NAME FetchDataWithOriginatorGC - COMMAND homestore_test_dynamic --executor immediate --config_path ./ + COMMAND homestore_test_dynamic --config_path ./ --override_config hs_backend_config.gc_garbage_rate_threshold=50 --override_config hs_backend_config.gc_garbage_rate_threshold_low=30 --gtest_filter=HomeObjectFixture.FetchDataWithOriginatorGC) @@ -165,7 +164,7 @@ add_test(NAME FetchDataWithOriginatorGC add_executable(homestore_test_gc) target_sources(homestore_test_gc PRIVATE $) target_link_libraries(homestore_test_gc PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestGC COMMAND homestore_test_gc --executor immediate --config_path ./ +add_test(NAME HomestoreTestGC COMMAND homestore_test_gc --config_path ./ --override_config hs_backend_config.gc_garbage_rate_threshold=0 --override_config hs_backend_config.gc_garbage_rate_threshold_low=0 --override_config hs_backend_config.gc_scan_interval_sec=5 @@ -174,7 +173,7 @@ add_test(NAME HomestoreTestGC COMMAND homestore_test_gc --executor immediate --c # Shard-race repro tests: require chunks_per_pg=1 (forces vchunk reuse after seal) and gc_garbage_rate_threshold=0. # Run together since they share the same config requirements. add_test(NAME HomestoreTestGC_ShardRaceTests - COMMAND homestore_test_gc --executor immediate --config_path ./ + COMMAND homestore_test_gc --config_path ./ --chunks_per_pg 1 --override_config hs_backend_config.gc_garbage_rate_threshold=0 --override_config hs_backend_config.gc_garbage_rate_threshold_low=0 @@ -184,7 +183,7 @@ add_test(NAME HomestoreTestGC_ShardRaceTests add_executable(homestore_test_scrubber) target_sources(homestore_test_scrubber PRIVATE $) target_link_libraries(homestore_test_scrubber PUBLIC homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME HomestoreTestScrubber COMMAND homestore_test_scrubber -csv error --executor immediate --config_path ./ +add_test(NAME HomestoreTestScrubber COMMAND homestore_test_scrubber -csv error --config_path ./ --override_config hs_backend_config.enable_scrubber=true --override_config nuraft_mesg_config.mesg_factory_config.data_request_deadline_secs:10) diff --git a/src/lib/homestore_backend/MPMCPriorityQueue.hpp b/src/lib/homestore_backend/MPMCPriorityQueue.hpp deleted file mode 100644 index 9b0ba02d5..000000000 --- a/src/lib/homestore_backend/MPMCPriorityQueue.hpp +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace homeobject { - -/** - * @brief Multi-Producer Multi-Consumer Priority Queue (C++20) - * - * Thread-safe priority queue that supports: - * - Concurrent push operations from multiple producers - * - Concurrent pop operations from multiple consumers - * - Blocking pop when queue is empty - * - Graceful shutdown via close() method - * - * @tparam T Element type (must be comparable) - * @tparam Compare Comparison function (default: std::less for max-heap) - */ -template < typename T, typename Compare = std::less< T > > - requires std::movable< T > && std::predicate< Compare, T, T > -class MPMCPriorityQueue { -public: - using value_type = T; - using size_type = std::size_t; - using comparator_type = Compare; - - /** - * @brief Status codes returned by pop operations - */ - enum class Status : uint8_t { - Ok, ///< Successfully popped an element - Closed ///< Queue is closed, no more elements available - }; - - /** - * @brief Result of a pop operation - */ - struct PopResult { - Status status; - std::optional< T > value; ///< Has value only if status == Ok - - // Convenience methods - [[nodiscard]] constexpr bool is_ok() const noexcept { return status == Status::Ok; } - [[nodiscard]] constexpr bool is_closed() const noexcept { return status == Status::Closed; } - }; - - /** - * @brief Construct an empty priority queue - */ - constexpr MPMCPriorityQueue() noexcept(std::is_nothrow_default_constructible_v< Compare >) = default; - - /** - * @brief Destructor - automatically closes the queue - */ - ~MPMCPriorityQueue() { close(); } - - // Disable copy and move to prevent issues with condition variables - MPMCPriorityQueue(const MPMCPriorityQueue&) = delete; - MPMCPriorityQueue& operator=(const MPMCPriorityQueue&) = delete; - MPMCPriorityQueue(MPMCPriorityQueue&&) = delete; - MPMCPriorityQueue& operator=(MPMCPriorityQueue&&) = delete; - - /** - * @brief Thread-safe push operation (copy) - * - * @param value Element to insert - * @return true if pushed successfully, false if queue is closed - */ - bool push(const T& value) - requires std::copy_constructible< T > - { - { - std::scoped_lock lock(mutex_); - if (closed_) [[unlikely]] { - return false; // Queue is closed, cannot push - } - pq_.push(value); - } - cv_.notify_one(); // Wake one waiting consumer - return true; - } - - /** - * @brief Thread-safe push operation (move) - * - * @param value Element to insert (will be moved) - * @return true if pushed successfully, false if queue is closed - */ - bool push(T&& value) { - { - std::scoped_lock lock(mutex_); - if (closed_) [[unlikely]] { return false; } - pq_.push(std::move(value)); - } - cv_.notify_one(); - return true; - } - - /** - * @brief Thread-safe pop operation - * - * Blocks if queue is empty and not closed. - * Returns immediately if queue is closed. - * - * @return PopResult containing status and optional value - * @note Thread-safe for multiple concurrent consumers - */ - [[nodiscard]] PopResult pop() { - std::unique_lock lock(mutex_); - - // Wait until queue has elements or is closed - cv_.wait(lock, [this] { return closed_ || !pq_.empty(); }); - - // Try to pop an element - if (!pq_.empty()) { - T top = std::move(const_cast< T& >(pq_.top())); - pq_.pop(); - return PopResult{.status = Status::Ok, .value = std::move(top)}; - } - - // Queue is empty and closed - return PopResult{.status = Status::Closed, .value = std::nullopt}; - } - - /** - * @brief Close the queue - * - * After calling close(): - * - All blocked pop() calls will wake up - * - Existing elements can still be popped - * - New push() calls will be ignored - * - pop() returns Status::Closed when queue becomes empty - * - * @note Thread-safe and idempotent - */ - void close() noexcept { - { - std::scoped_lock lock(mutex_); - closed_ = true; - } - cv_.notify_all(); // Wake all waiting consumers - } - - /** - * @brief Get current number of elements - * - * @return Number of elements in the queue - * @note Thread-safe - */ - [[nodiscard]] size_type size() const { - std::scoped_lock lock(mutex_); - return pq_.size(); - } - - /** - * @brief Check if queue is empty - * - * @return true if queue has no elements - * @note Thread-safe - */ - [[nodiscard]] bool empty() const { - std::scoped_lock lock(mutex_); - return pq_.empty(); - } - - /** - * @brief Check if queue is closed - * - * @return true if close() has been called - * @note Thread-safe - */ - [[nodiscard]] bool is_closed() const { - std::scoped_lock lock(mutex_); - return closed_; - } - -private: - mutable std::mutex mutex_; - std::condition_variable cv_; - bool closed_{false}; - std::priority_queue< T, std::vector< T >, Compare > pq_; -}; - -} // namespace homeobject diff --git a/src/lib/homestore_backend/gc_manager.cpp b/src/lib/homestore_backend/gc_manager.cpp index 82f5e4791..edfa33824 100644 --- a/src/lib/homestore_backend/gc_manager.cpp +++ b/src/lib/homestore_backend/gc_manager.cpp @@ -13,8 +13,8 @@ SISL_LOGGING_DECL(gcmgr) #define RECOVERD_GC_TASK_ID 0 #define GCLOG(level, gc_task_id, pg_id, shard_id, msg, ...) \ - LOG##level##MOD(gcmgr, "[gc_task_id={}, pg_id={}, shard_id=0x{:x}] " msg, gc_task_id, pg_id, shard_id, \ - ##__VA_ARGS__) + LOG##level##MOD(gcmgr, "[gc_task_id={}, pg_id={}, shard_id=0x{:x}] " msg, gc_task_id, pg_id, \ + shard_id, ##__VA_ARGS__) #define GCLOGT(gc_task_id, pg_id, shard_id, msg, ...) GCLOG(TRACE, gc_task_id, pg_id, shard_id, msg, ##__VA_ARGS__) #define GCLOGD(gc_task_id, pg_id, shard_id, msg, ...) GCLOG(DEBUG, gc_task_id, pg_id, shard_id, msg, ##__VA_ARGS__) @@ -68,7 +68,7 @@ void GCManager::on_gc_task_meta_blk_found(sisl::byte_view const& buf, void* meta // metablk and then destroy the gc_task_sb, which will also try to acquire the lock of metaservice, as a result, a // dead lock will happen. so here we will handle all the gc tasks after read all the metablks m_recovered_gc_tasks.emplace_back(gc_task_meta_name); - m_recovered_gc_tasks.back().load(buf, meta_cookie); + m_recovered_gc_tasks.back().load(buf, r_cast< homestore::meta_blk* >(meta_cookie)); } void GCManager::handle_all_recovered_gc_tasks() { @@ -94,7 +94,7 @@ void GCManager::handle_all_recovered_gc_tasks() { void GCManager::on_gc_actor_meta_blk_found(sisl::byte_view const& buf, void* meta_cookie) { m_gc_actor_sbs.emplace_back(gc_actor_meta_name); auto& gc_actor_sb = m_gc_actor_sbs.back(); - gc_actor_sb.load(buf, meta_cookie); + gc_actor_sb.load(buf, r_cast< homestore::meta_blk* >(meta_cookie)); auto pdev_id = gc_actor_sb->pdev_id; // create a gc actor for this pdev if not exists @@ -104,7 +104,7 @@ void GCManager::on_gc_actor_meta_blk_found(sisl::byte_view const& buf, void* met void GCManager::on_reserved_chunk_meta_blk_found(sisl::byte_view const& buf, void* meta_cookie) { homestore::superblk< gc_reserved_chunk_superblk > reserved_chunk_sb(gc_reserved_chunk_meta_name); - auto chunk_id = reserved_chunk_sb.load(buf, meta_cookie)->chunk_id; + auto chunk_id = reserved_chunk_sb.load(buf, r_cast< homestore::meta_blk* >(meta_cookie))->chunk_id; auto EXVchunk = m_chunk_selector->get_extend_vchunk(chunk_id); if (EXVchunk == nullptr) { LOGWARNMOD(gcmgr, @@ -121,30 +121,18 @@ void GCManager::on_reserved_chunk_meta_blk_found(sisl::byte_view const& buf, voi GCManager::~GCManager() { stop(); } void GCManager::start() { - for (const auto& [pdev_id, gc_actor] : m_pdev_gc_actors) { - gc_actor->start(); - LOGINFOMOD(gcmgr, "start gc actor for pdev={}", pdev_id); - } + m_pdev_gc_actors.visit_all([](auto const& actor_entry) { + actor_entry.second->start(); + LOGINFOMOD(gcmgr, "start gc actor for pdev={}", actor_entry.first); + }); start_gc_scan_timer(); } void GCManager::start_gc_scan_timer() { const auto gc_scan_interval_sec = HS_BACKEND_DYNAMIC_CONFIG(gc_scan_interval_sec); - - // the initial idea here is that we want gc timer to run in a reactor that not shared with other fibers that - // probably hold a lock before fiber switching(for example , cp io reactor) to prevent some potential dead lock - // issue. here, we make gc timer run in a random worker reactor, which is created by iomanager itself(not created - // by user). worker reactor is mainly used for handling disk io(async_write/async_read, sync_write/sync_read), and - // they are all handled in the main_fiber. moreover, the timer assigned to the worker reactor is also handled in the - // main_fiber. as a result, only main fiber works for all the io and timer and no fiber swithing happens, unless - // user explicitly selectes a sync_io_fiber of a worker reactor to do some io operation, which does not happens in - // homestore backend and homeobject code base. - - // theoretically, there is no way to make a user created reactor totally pure, because even if we create a separate - // user reactor for gc timer, if some one user all_user to do io operation or set timer, then this reactor will be - // polluted by other fibers or timer - iomanager.run_on_wait(iomgr::reactor_regex::random_worker, [&]() { - m_gc_timer_fiber = iomanager.iofiber_self(); + auto* reactor = m_hs_home_object->bg_timer_reactor(); + RELEASE_ASSERT(reactor, "bg timer reactor is not started"); + iomanager.run_on_wait(reactor, [&]() { m_gc_timer_hdl = iomanager.schedule_thread_timer(gc_scan_interval_sec * 1000 * 1000 * 1000, true, nullptr /*cookie*/, [this](void*) { scan_chunks_for_gc(); }); }); @@ -157,70 +145,69 @@ void GCManager::stop_gc_scan_timer() { LOGWARNMOD(gcmgr, "gc scheduler timer is not running, no need to stop it"); return; } - RELEASE_ASSERT(m_gc_timer_fiber, - "m_gc_timer_hdl is not null_timer_handle, but m_gc_timer_fiber is null, fatal error!"); LOGINFOMOD(gcmgr, "stop gc scheduler timer"); - iomanager.run_on_wait(m_gc_timer_fiber, [&]() { + auto* reactor = m_hs_home_object->bg_timer_reactor(); + RELEASE_ASSERT(reactor, "bg timer reactor is not started"); + iomanager.run_on_wait(reactor, [&]() { iomanager.cancel_timer(m_gc_timer_hdl, true); m_gc_timer_hdl = iomgr::null_timer_handle; }); - m_gc_timer_fiber = nullptr; } void GCManager::stop() { stop_gc_scan_timer(); - for (const auto& [pdev_id, gc_actor] : m_pdev_gc_actors) { - gc_actor->stop(); - LOGINFOMOD(gcmgr, "stop gc actor for pdev={}", pdev_id); - } + m_pdev_gc_actors.visit_all([](auto const& actor_entry) { + actor_entry.second->stop(); + LOGINFOMOD(gcmgr, "stop gc actor for pdev={}", actor_entry.first); + }); } -folly::SemiFuture< bool > GCManager::submit_gc_task(task_priority priority, chunk_id_t chunk_id) { +sisl::async::task< bool > GCManager::submit_gc_task(task_priority priority, chunk_id_t chunk_id) { auto ex_vchunk = m_chunk_selector->get_extend_vchunk(chunk_id); if (ex_vchunk == nullptr) { LOGERRORMOD(gcmgr, "chunk {} not found when submit gc task!", chunk_id); - return folly::makeFuture< bool >(false); + co_return false; } // if the chunk has no garbage to be reclaimed, we don`t need to gc it , return true directly const auto defrag_blk_num = ex_vchunk->get_defrag_nblks(); if (!defrag_blk_num && task_priority::normal == priority) { LOGERRORMOD(gcmgr, "chunk {} has no garbage to be reclaimed, skip gc for this chunk!", chunk_id); - return folly::makeFuture< bool >(true); + co_return true; } auto pdev_id = ex_vchunk->get_pdev_id(); - auto it = m_pdev_gc_actors.find(pdev_id); - if (it == m_pdev_gc_actors.end()) { + auto actor = get_pdev_gc_actor(pdev_id); + if (!actor) { LOGINFOMOD(gcmgr, "pdev gc actor not found for pdev_id={}, chunk={}", pdev_id, chunk_id); - return folly::makeFuture< bool >(false); + co_return false; } - auto& actor = it->second; - return actor->add_gc_task(static_cast< uint8_t >(priority), chunk_id); + co_return co_await sisl::async::await_value(actor->add_gc_task(static_cast< uint8_t >(priority), chunk_id)); } std::shared_ptr< GCManager::pdev_gc_actor > GCManager::try_create_pdev_gc_actor(uint32_t pdev_id, const homestore::superblk< gc_actor_superblk >& gc_actor_sb) { - auto const [it, happened] = m_pdev_gc_actors.try_emplace( - pdev_id, std::make_shared< pdev_gc_actor >(gc_actor_sb, m_chunk_selector, m_hs_home_object)); - RELEASE_ASSERT((it != m_pdev_gc_actors.end()), "Unexpected error in m_pdev_gc_actors!!!"); - if (happened) { - LOGINFOMOD(gcmgr, "create new gc actor for pdev_id: {}", pdev_id); - } else { + auto actor = std::make_shared< pdev_gc_actor >(gc_actor_sb, m_chunk_selector, m_hs_home_object); + const bool happened = m_pdev_gc_actors.try_emplace(pdev_id, actor); + + if (!happened) { LOGINFOMOD(gcmgr, "pdev gc actor already exists for pdev_id: {}", pdev_id); + } else { + LOGINFOMOD(gcmgr, "created pdev gc actor for pdev_id={}", pdev_id); } - return it->second; + return actor; } std::shared_ptr< GCManager::pdev_gc_actor > GCManager::get_pdev_gc_actor(uint32_t pdev_id) { - auto it = m_pdev_gc_actors.find(pdev_id); - if (it == m_pdev_gc_actors.end()) { + std::shared_ptr< pdev_gc_actor > actor; + bool hit = m_pdev_gc_actors.cvisit(pdev_id, [&](auto const& e) { actor = e.second; }); + if (!hit) { LOGERRORMOD(gcmgr, "pdev gc actor not found for pdev_id: {}", pdev_id); return nullptr; } - return it->second; + return actor; } GCManager::ChunkGCSnapshot GCManager::get_chunk_gc_snapshot(chunk_id_t chunk_id, uint8_t gc_thresh_low) { @@ -238,7 +225,7 @@ GCManager::ChunkGCSnapshot GCManager::get_chunk_gc_snapshot(chunk_id_t chunk_id, snap.ratio_pct = (100.0f * static_cast< float >(snap.defrag_blks)) / static_cast< float >(snap.total_blks); } - // is_gc_candidate mirrors the original get_chunk_gc_ratio non-zero condition: + // is_gc_candidate condition: // - Only AVAILABLE chunks are candidates: INUSE means an open shard owns them, GC means // already being processed. // - Chunks with no pg assignment are unowned and do not need GC. @@ -262,14 +249,6 @@ GCManager::ChunkGCSnapshot GCManager::get_chunk_gc_snapshot(chunk_id_t chunk_id, return snap; } -float GCManager::get_chunk_gc_ratio(chunk_id_t chunk_id) { - // Preserve the original contract: return the actual ratio for chunks passing every gate - // except the low-watermark threshold; return 0 otherwise. Callers that need finer control - // (scanner, metrics accumulation) should call get_chunk_gc_snapshot directly. - const auto snap = get_chunk_gc_snapshot(chunk_id, 0 /* gc_thresh_low */); - return snap.is_gc_candidate ? snap.ratio_pct : 0.0f; -} - void GCManager::scan_chunks_for_gc() { const auto reserved_chunk_num_per_pdev = HS_BACKEND_DYNAMIC_CONFIG(reserved_chunk_num_per_pdev); const auto reserved_chunk_num_per_pdev_for_egc = HS_BACKEND_DYNAMIC_CONFIG(reserved_chunk_num_per_pdev_for_egc); @@ -287,10 +266,9 @@ void GCManager::scan_chunks_for_gc() { for (const auto& [pdev_id, chunks] : m_chunk_selector->get_pdev_chunks()) { const uint32_t max_task_num = 2 * (reserved_chunk_num_per_pdev - reserved_chunk_num_per_pdev_for_egc); - auto it = m_pdev_gc_actors.find(pdev_id); - RELEASE_ASSERT(it != m_pdev_gc_actors.end(), "can not find gc actor for pdev_id {} when scanning chunks for gc", - pdev_id); - auto& actor = it->second; + std::shared_ptr< pdev_gc_actor > actor; + RELEASE_ASSERT(m_pdev_gc_actors.cvisit(pdev_id, [&](auto const& actor_entry) { actor = actor_entry.second; }), + "can not find gc actor for pdev_id {} when scanning chunks for gc", pdev_id); // Collect at most max_task_num chunks with the highest garbage ratios via a bounded // min-heap. K = max_task_num (fixed during the scan) rather than remaining_capacity @@ -398,8 +376,8 @@ void GCManager::scan_chunks_for_gc() { if (!is_high_tier && low_tier_submitted >= low_tier_cap) { continue; } auto future = actor->add_gc_task(static_cast< uint8_t >(task_priority::normal), info.chunk_id); - if (future.isReady()) { - if (future.value()) { + if (future->await_ready()) { + if (future->await_resume()) { LOGINFOMOD(gcmgr, "gc task for chunk_id={} on pdev_id={} has been submitted and successfully completed " "shortly", @@ -479,8 +457,8 @@ void GCManager::pdev_gc_actor::start() { normal_gc_thread_num = std::min< uint8_t >(normal_gc_thread_num, reserved_chunk_num_per_pdev - reserved_chunk_num_per_pdev_for_egc); - m_gc_executor = std::make_shared< folly::IOThreadPoolExecutor >(normal_gc_thread_num); - m_egc_executor = std::make_shared< folly::IOThreadPoolExecutor >(reserved_chunk_num_per_pdev_for_egc); + m_gc_executor = std::make_shared< boost::asio::thread_pool >(normal_gc_thread_num); + m_egc_executor = std::make_shared< boost::asio::thread_pool >(reserved_chunk_num_per_pdev_for_egc); LOGINFOMOD(gcmgr, "pdev gc actor for pdev_id={} has started, {} threads for normal gc and {} threads for egc", m_pdev_id, normal_gc_thread_num, reserved_chunk_num_per_pdev_for_egc); @@ -492,10 +470,7 @@ void GCManager::pdev_gc_actor::stop() { LOGWARNMOD(gcmgr, "pdev gc actor for pdev_id={} is already stopped, no need to stop again!", m_pdev_id); return; } - m_gc_executor->stop(); m_gc_executor.reset(); - - m_egc_executor->stop(); m_egc_executor.reset(); LOGINFOMOD(gcmgr, "pdev gc actor for pdev_id={} has stopped", m_pdev_id); } @@ -506,21 +481,25 @@ void GCManager::pdev_gc_actor::add_reserved_chunk( // mark a reserved chunk as gc state, so that it will not be selected as a gc candidate m_chunk_selector->try_mark_chunk_to_gc_state(chunk_id, true /* force */); m_reserved_chunks.emplace_back(std::move(reserved_chunk_sb)); - m_reserved_chunk_queue.blockingWrite(chunk_id); + while (!m_reserved_chunk_queue.write(chunk_id)) {} LOGDEBUGMOD(gcmgr, "chunk_id={} is added to reserved chunk queue", chunk_id); } -folly::SemiFuture< bool > GCManager::pdev_gc_actor::add_gc_task(uint8_t priority, chunk_id_t move_from_chunk) { +std::shared_ptr< sisl::async::value_awaitable< bool > > +GCManager::pdev_gc_actor::add_gc_task(uint8_t priority, chunk_id_t move_from_chunk) { + auto ret = std::make_shared< sisl::async::value_awaitable< bool > >(); if (m_is_stopped.load()) { LOGWARNMOD(gcmgr, "pdev gc actor for pdev_id={} is not started yet or already stopped, cannot add gc task!", m_pdev_id); - return folly::makeSemiFuture< bool >(false); + ret->complete(false); + return ret; } auto EXvchunk = m_chunk_selector->get_extend_vchunk(move_from_chunk); // it does not belong to any pg, so we don't need to gc it. if (!EXvchunk->m_pg_id.has_value()) { LOGTRACEMOD(gcmgr, "chunk_id={} belongs to no pg, not eligible for gc", move_from_chunk) - return folly::makeSemiFuture< bool >(false); + ret->complete(false); + return ret; } const auto pg_id = EXvchunk->m_pg_id.value(); @@ -530,36 +509,36 @@ folly::SemiFuture< bool > GCManager::pdev_gc_actor::add_gc_task(uint8_t priority LOGTRACEMOD(gcmgr, "chunk_id={} belongs to pg {}, which is not eligible for gc at this moment!", move_from_chunk, pg_id) m_hs_home_object->gc_manager()->decr_pg_pending_gc_task(pg_id); - return folly::makeSemiFuture< bool >(false); + ret->complete(false); + return ret; } if (m_chunk_selector->try_mark_chunk_to_gc_state(move_from_chunk, priority == static_cast< uint8_t >(task_priority::emergent))) { - auto [promise, future] = folly::makePromiseContract< bool >(); const auto gc_task_id = GCManager::_gc_task_id.fetch_add(1); if (sisl_unlikely(priority == static_cast< uint8_t >(task_priority::emergent))) { - m_egc_executor->add([this, gc_task_id, priority, move_from_chunk, promise = std::move(promise)]() mutable { + boost::asio::post(*m_egc_executor, [this, gc_task_id, priority, move_from_chunk, ret]() mutable { LOGDEBUGMOD(gcmgr, "start emergent gc task : move_from_chunk_id={}, priority={}", move_from_chunk, priority); - process_gc_task(move_from_chunk, priority, std::move(promise), gc_task_id); + process_gc_task(move_from_chunk, priority, ret, gc_task_id); }); } else { - // Increment BEFORE handing the task to the executor so that an immediately-completing - // task (which decrements via ~gc_task_guard) cannot underflow the counter. + // Increment BEFORE handing the task to the executor so that an immediately-completingtask (which decrements + // via ~gc_task_guard) cannot underflow the counter. m_pending_normal_gc_task_count.fetch_add(1, std::memory_order_relaxed); - m_gc_executor->add([this, gc_task_id, priority, move_from_chunk, promise = std::move(promise)]() mutable { + boost::asio::post(*m_gc_executor, [this, gc_task_id, priority, move_from_chunk, ret]() mutable { LOGDEBUGMOD(gcmgr, "start gc task : move_from_chunk_id={}, priority={}", move_from_chunk, priority); - process_gc_task(move_from_chunk, priority, std::move(promise), gc_task_id); + process_gc_task(move_from_chunk, priority, ret, gc_task_id); }); } - - return std::move(future); + } else { + LOGWARNMOD(gcmgr, "fail to submit gc task for chunk_id={}, priority={}", move_from_chunk, priority); + m_hs_home_object->gc_manager()->decr_pg_pending_gc_task(pg_id); + ret->complete(false); } - LOGWARNMOD(gcmgr, "fail to submit gc task for chunk_id={}, priority={}", move_from_chunk, priority); - m_hs_home_object->gc_manager()->decr_pg_pending_gc_task(pg_id); - return folly::makeSemiFuture< bool >(false); + return ret; } void GCManager::drain_pg_pending_gc_task(const pg_id_t pg_id) { @@ -635,7 +614,7 @@ void GCManager::pdev_gc_actor::handle_recovered_gc_task( // now we need to put the reserved chunks back to the reserved chunk queue for (const auto& reserved_chunk : reserved_chunks) { - m_reserved_chunk_queue.blockingWrite(reserved_chunk); + while (!m_reserved_chunk_queue.write(reserved_chunk)) {} } // 2 make the two chunks to the state as that before gc task starts @@ -741,7 +720,7 @@ bool GCManager::pdev_gc_actor::replace_blob_index( "remove tombstone when updating pg index after data copy blob_id={}, move_from_chunk={}, " "move_to_chunk={}", blob, move_from_chunk, move_to_chunk); - homestore::data_service().async_free_blk(new_pba_value.pbas()); + sisl::async::detach(homestore::data_service().async_free_blk(new_pba_value.pbas())); return homestore::put_filter_decision::remove; } @@ -794,7 +773,7 @@ bool GCManager::pdev_gc_actor::replace_blob_index( "Failed to update blob in pg index table, move_from_chunk={}, error_status={}, move_to_chunk={}", move_from_chunk, ret, move_to_chunk); // pg index table might be partial updated, we can not put move_to_chunk back to the queue - // m_reserved_chunk_queue.blockingWrite(move_to_chunk); + // while (!m_reserved_chunk_queue.write(move_to_chunk)) {} return false; } @@ -820,7 +799,7 @@ bool GCManager::pdev_gc_actor::replace_blob_index( // note that, when we copy data, there is not create shard or put blob in this chunk, only delete blob might happen. bool GCManager::pdev_gc_actor::copy_valid_data( chunk_id_t move_from_chunk, chunk_id_t move_to_chunk, - folly::ConcurrentHashMap< BlobRouteByChunk, BlobRouteValue >& copied_blobs, const uint8_t priority, + boost::concurrent_flat_map< BlobRouteByChunk, BlobRouteValue >& copied_blobs, const uint8_t priority, const uint64_t task_id) { auto move_to_vchunk = m_chunk_selector->get_extend_vchunk(move_to_chunk); @@ -952,8 +931,11 @@ bool GCManager::pdev_gc_actor::copy_valid_data( } // copy all the valid blobs in the shard from move_from_chunk to move_to_chunk - std::vector< folly::Future< bool > > futs; - for (const auto& [k, v] : valid_blob_indexes) { + std::vector< sisl::async::task< bool > > futs; + futs.reserve(valid_blob_indexes.size()); + + auto copy_blob = [this, &hints, &move_from_chunk, &move_to_chunk, &data_service, task_id, pg_id, &copied_blobs, + blk_size](BlobRouteKey k, BlobRouteValue v) -> sisl::async::task< bool > { // k is shard_id + blob_id, v is multiblk id auto pba = v.pbas(); auto total_size = pba.blk_count() * blk_size; @@ -964,109 +946,99 @@ bool GCManager::pdev_gc_actor::copy_valid_data( data_sgs.iovs.emplace_back( iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); - futs.emplace_back(std::move( - // read blob from move_from_chunk - data_service.async_read(pba, data_sgs, total_size) - .thenValue([this, k, &hints, &move_from_chunk, &move_to_chunk, &data_service, task_id, pg_id, - data_sgs = std::move(data_sgs), pba, &copied_blobs](auto&& err) { - COUNTER_INCREMENT(metrics_, gc_read_blk_count, pba.blk_count()); - RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); - - const auto shard_id = k.key().shard; - const auto blob_id = k.key().blob; - - if (err) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to read blob from move_from_chunk={}, blob_id={}, err={}, " - "err_category={}, err_message={}", - move_from_chunk, blob_id, err.value(), err.category().name(), err.message()); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - return folly::makeFuture< bool >(false); - } - - GCLOGT(task_id, pg_id, shard_id, - "successfully read blob from move_from_chunk={}, blob_id={}, pba={}", move_from_chunk, - blob_id, pba.to_string()); - - if (m_enable_read_verify) { - // after a blob is deleted at originator, if it receives a fetch_data request of - // this blob, a fake delete_marker blob will be returned to the requester. This - // case happens in incremental resync scenario. when verifying blob, if it is a - // delete_marker, we let it pass the verification in gc scenario so that it will - // not block any gc task. - if (!m_hs_home_object->verify_blob(data_sgs.iovs[0].iov_base, shard_id, blob_id, true)) { - GCLOGE(task_id, pg_id, shard_id, - "blob verification fails for move_from_chunk={}, blob_id={}, pba={}", - move_from_chunk, blob_id, pba.to_string()); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - return folly::makeFuture< bool >(false); - } - } - - // write the blob to the move_to_chunk. we do not care about the blob order in a - // shard since we can not guarantee a certain order - homestore::MultiBlkId new_pba; - return data_service.async_alloc_write(data_sgs, hints, new_pba) - .thenValue([this, shard_id, blob_id, new_pba, &move_to_chunk, task_id, pg_id, &copied_blobs, - data_sgs = std::move(data_sgs)](auto&& err) { - COUNTER_INCREMENT(metrics_, gc_write_blk_count, new_pba.blk_count()); - RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - if (err) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to write blob to move_to_chunk={}, blob_id={}, err={}, " - "err_category={}, err_message={}", - move_to_chunk, blob_id, err.value(), err.category().name(), err.message()); - return false; - } - - // insert a new entry to gc index table for this blob. [move_to_chunk_id, - // shard_id, blob_id] -> [new pba] - BlobRouteByChunkKey key{BlobRouteByChunk{move_to_chunk, shard_id, blob_id}}; - BlobRouteValue value{new_pba}, existing_value; - - homestore::BtreeSinglePutRequest put_req{ - &key, &value, homestore::btree_put_type::INSERT, &existing_value}; - auto status = m_index_table->put(put_req); - if (status != homestore::btree_status_t::success) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to insert new key to gc index table for " - "move_to_chunk={}, blob_id={}, err={}", - move_to_chunk, blob_id, status); - return false; - } - - GCLOGT(task_id, pg_id, shard_id, - "successfully insert new key to gc index table for " - "move_to_chunk={}, blob_id={}, new_pba={}", - move_to_chunk, blob_id, new_pba.to_string()); - - BlobRouteByChunk route_key{move_to_chunk, shard_id, blob_id}; - auto ret = copied_blobs.insert(route_key, value); - RELEASE_ASSERT(ret.second, - "we should not copy the same blob twice in gc task, " - "move_to_chunk={}, shard_id=0x{:x}, pg_id={}, blob_id={}", - move_to_chunk, shard_id, pg_id, blob_id); - - return true; - }); - }))); + // read blob from move_from_chunk + auto err = co_await data_service.async_read(pba, data_sgs, total_size); + COUNTER_INCREMENT(metrics_, gc_read_blk_count, pba.blk_count()); + RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); + + const auto shard_id = k.key().shard; + const auto blob_id = k.key().blob; + + if (!err) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to read blob from move_from_chunk={}, blob_id={}, err={}, " + "err_category={}, err_message={}", + move_from_chunk, blob_id, err.error().value(), err.error().category().name(), + err.error().message()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + co_return false; + } + + GCLOGT(task_id, pg_id, shard_id, "successfully read blob from move_from_chunk={}, blob_id={}, pba={}", + move_from_chunk, blob_id, pba.to_string()); + + if (m_enable_read_verify) { + // after a blob is deleted at originator, if it receives a fetch_data request of + // this blob, a fake delete_marker blob will be returned to the requester. This + // case happens in incremental resync scenario. when verifying blob, if it is a + // delete_marker, we let it pass the verification in gc scenario so that it will + // not block any gc task. + if (!m_hs_home_object->verify_blob(data_sgs.iovs[0].iov_base, shard_id, blob_id, true)) { + GCLOGE(task_id, pg_id, shard_id, + "blob verification fails for move_from_chunk={}, blob_id={}, pba={}", move_from_chunk, + blob_id, pba.to_string()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + co_return false; + } + } + + // write the blob to the move_to_chunk. we do not care about the blob order in a + // shard since we can not guarantee a certain order + homestore::multi_blk_id new_pba; + err = co_await data_service.async_alloc_write(data_sgs, hints, new_pba); + COUNTER_INCREMENT(metrics_, gc_write_blk_count, new_pba.blk_count()); + RELEASE_ASSERT(data_sgs.iovs.size() == 1, "data_sgs.iovs.size() should be 1, but not!"); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + if (!err) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to write blob to move_to_chunk={}, blob_id={}, err={}, " + "err_category={}, err_message={}", + move_to_chunk, blob_id, err.error().value(), err.error().category().name(), + err.error().message()); + co_return false; + } + + // insert a new entry to gc index table for this blob. [move_to_chunk_id, + // shard_id, blob_id] -> [new pba] + BlobRouteByChunkKey key{BlobRouteByChunk{move_to_chunk, shard_id, blob_id}}; + BlobRouteValue value{new_pba}, existing_value; + + homestore::BtreeSinglePutRequest put_req{&key, &value, homestore::btree_put_type::INSERT, &existing_value}; + auto status = m_index_table->put(put_req); + if (status != homestore::btree_status_t::success) { + GCLOGE(task_id, pg_id, shard_id, + "Failed to insert new key to gc index table for move_to_chunk={}, blob_id={}, err={}", + move_to_chunk, blob_id, status); + co_return false; + } + + GCLOGT(task_id, pg_id, shard_id, + "successfully insert new key to gc index table for move_to_chunk={}, blob_id={}, new_pba={}", + move_to_chunk, blob_id, new_pba.to_string()); + + BlobRouteByChunk route_key{move_to_chunk, shard_id, blob_id}; + const bool inserted = copied_blobs.insert(std::pair{route_key, value}); + RELEASE_ASSERT(inserted, + "we should not copy the same blob twice in gc task, " + "move_to_chunk={}, shard_id=0x{:x}, pg_id={}, blob_id={}", + move_to_chunk, shard_id, pg_id, blob_id); + + co_return true; + }; + + for (const auto& [k, v] : valid_blob_indexes) { + futs.emplace_back(copy_blob(k, v)); } - const auto succeed_copying_shard = - folly::collectAllUnsafe(futs) - .thenValue([this, &shard_id, &move_to_chunk, task_id, pg_id](auto&& results) { - for (auto const& ok : results) { - RELEASE_ASSERT(ok.hasValue(), "we never throw any exception when copying data"); - if (!ok.value()) { - GCLOGE(task_id, pg_id, shard_id, - "Failed to copy blob for move_to_chunk={}, will cancel this task", move_to_chunk); - return false; - } - } - return true; - }) - .get(); + bool succeed_copying_shard = true; + for (auto const& ok : sisl::async::sync_get(sisl::async::when_all(std::move(futs)))) { + if (!ok) { + GCLOGE(task_id, pg_id, shard_id, "Failed to copy blob for move_to_chunk={}, will cancel this task", + move_to_chunk); + succeed_copying_shard = false; + break; + } + } if (!succeed_copying_shard) { GCLOGE(task_id, pg_id, shard_id, "Failed to copy all blobs from move_from_chunk={} to move_to_chunk={}", @@ -1086,8 +1058,8 @@ bool GCManager::pdev_gc_actor::copy_valid_data( // to the exact last offset. const auto used_blks = move_to_vchunk->get_used_blks(); if (used_blks) { - homestore::MultiBlkId commit_blk_id(used_blks - 1, 1, move_to_chunk); - if (data_service.commit_blk(commit_blk_id) != homestore::BlkAllocStatus::SUCCESS) { + homestore::multi_blk_id commit_blk_id(used_blks - 1, 1, move_to_chunk); + if (!data_service.commit_blk(commit_blk_id)) { GCLOGE(task_id, pg_id, NO_SHARD_ID, "fail to commit_blk for move_to_chunk={}, commit_blk_id={}", move_to_chunk, commit_blk_id.to_string()); return false; @@ -1196,7 +1168,7 @@ bool GCManager::pdev_gc_actor::purge_reserved_chunk(chunk_id_t chunk, const uint } bool GCManager::pdev_gc_actor::check_blob_consistency( - folly::ConcurrentHashMap< BlobRouteByChunk, BlobRouteValue > const& copied_blobs, + boost::concurrent_flat_map< BlobRouteByChunk, BlobRouteValue > const& copied_blobs, std::vector< std::pair< BlobRouteByChunkKey, BlobRouteValue > > const& valid_blob_indexes, const uint64_t task_id, const pg_id_t pg_id) { @@ -1217,34 +1189,34 @@ bool GCManager::pdev_gc_actor::check_blob_consistency( for (const auto& [k, v] : valid_blob_indexes) { const auto shard_id = k.key().shard; BlobRouteByChunk route_key{k.key().chunk, shard_id, k.key().blob}; - const auto it = copied_blobs.find(route_key); - if (it == copied_blobs.end()) { + const bool found = copied_blobs.cvisit(route_key, [&](auto const& entry) { + if (v.pbas() != entry.second.pbas()) { + GCLOGW(task_id, pg_id, shard_id, + "pba of copied blob is not the same as that in gc index table for move_to_chunk={}, " + "blob_id={}, copied_pba={}, gc_index_table_pba={}", + k.key().chunk, k.key().blob, entry.second.pbas().to_string(), v.pbas().to_string()); + ret = false; + } + }); + if (!found) { GCLOGW(task_id, pg_id, shard_id, "can not find copied blob in copied_blobs for move_to_chunk={}, blob_id={}", k.key().chunk, k.key().blob); ret = false; - break; - } - - if (v.pbas() != it->second.pbas()) { - GCLOGW(task_id, pg_id, shard_id, - "pba of copied blob is not the same as that in gc index table for move_to_chunk={}, blob_id={}, " - "copied_pba={}, gc_index_table_pba={}", - k.key().chunk, k.key().blob, it->second.pbas().to_string(), v.pbas().to_string()); - ret = false; - break; } + if (!ret) { break; } } } if (!ret) { GCLOGW(task_id, pg_id, NO_SHARD_ID, "copied blobs do not match those in gc index table, start printing copied blobs:"); - for (const auto& [k, v] : copied_blobs) { + copied_blobs.cvisit_all([&](auto const& entry) { + const auto& [k, v] = entry; const auto shard_id = k.shard; GCLOGW(task_id, pg_id, shard_id, "copied blob: move_to_chunk={}, blob_id={}, pba={}", k.chunk, k.blob, v.pbas().to_string()); - } + }); GCLOGW(task_id, pg_id, NO_SHARD_ID, "start printing valid blobs from gc index table:"); for (const auto& [k, v] : valid_blob_indexes) { const auto shard_id = k.key().shard; @@ -1267,7 +1239,7 @@ void GCManager::pdev_gc_actor::on_gc_task_completed(uint8_t priority, pg_id_t pg if (success) { m_chunk_selector->update_vchunk_info_after_gc(move_from_chunk, move_to_chunk, final_state, pg_id, vchunk_id, task_id); - m_reserved_chunk_queue.blockingWrite(move_from_chunk); + while (!m_reserved_chunk_queue.write(move_from_chunk)) {} durable_entities_update([this, priority](auto& de) { priority == static_cast< uint8_t >(task_priority::normal) ? de.success_gc_task_count.fetch_add(1) : de.success_egc_task_count.fetch_add(1); @@ -1278,7 +1250,7 @@ void GCManager::pdev_gc_actor::on_gc_task_completed(uint8_t priority, pg_id_t pg vchunk_id, move_from_chunk, move_to_chunk, final_state, priority); } else { m_chunk_selector->mark_chunk_out_of_gc_state(move_from_chunk, final_state, task_id); - m_reserved_chunk_queue.blockingWrite(move_to_chunk); + while (!m_reserved_chunk_queue.write(move_to_chunk)) {} durable_entities_update([this, priority](auto& de) { priority == static_cast< uint8_t >(task_priority::normal) ? de.failed_gc_task_count.fetch_add(1) : de.failed_egc_task_count.fetch_add(1); @@ -1289,7 +1261,8 @@ void GCManager::pdev_gc_actor::on_gc_task_completed(uint8_t priority, pg_id_t pg } void GCManager::pdev_gc_actor::process_gc_task(chunk_id_t move_from_chunk, uint8_t priority, - folly::Promise< bool > task, const uint64_t task_id) { + std::shared_ptr< sisl::async::value_awaitable< bool > > task, + const uint64_t task_id) { auto start_time = std::chrono::steady_clock::now(); auto vchunk = m_chunk_selector->get_extend_vchunk(move_from_chunk); RELEASE_ASSERT(vchunk->m_pg_id.has_value(), "chunk_id={} is expected to belong to a pg, but not!", move_from_chunk); @@ -1300,7 +1273,7 @@ void GCManager::pdev_gc_actor::process_gc_task(chunk_id_t move_from_chunk, uint8 if (vchunk->m_state != ChunkState::GC) { GCLOGW(task_id, pg_id, NO_SHARD_ID, "move_from_chunk={} is expected to in GC state but not!", move_from_chunk); - task.setValue(false); + task->complete(false); // We return before constructing gc_task_guard, so we must mirror its bookkeeping here: // decrement the per-pdev pending normal-priority counter that add_gc_task incremented. if (priority == static_cast< uint8_t >(task_priority::normal)) { @@ -1318,7 +1291,7 @@ void GCManager::pdev_gc_actor::process_gc_task(chunk_id_t move_from_chunk, uint8 // wait for a reserved chunk to be available. now, the amount of threads in the folly executor(thread pool) is equal // to the amount of reserved number, so we can make sure that a gc task handle thread can always get a reserved // chunk, so actually the blockingRead here will not block in any case and return immediately. - m_reserved_chunk_queue.blockingRead(move_to_chunk); + while (!m_reserved_chunk_queue.read(move_to_chunk)) {} GCLOGI(task_id, pg_id, NO_SHARD_ID, "task for move_from_chunk={} to move_to_chunk={} with priority={} start copying data", move_from_chunk, move_to_chunk, priority); @@ -1330,7 +1303,7 @@ void GCManager::pdev_gc_actor::process_gc_task(chunk_id_t move_from_chunk, uint8 return; } - folly::ConcurrentHashMap< BlobRouteByChunk, BlobRouteValue > copied_blobs; + boost::concurrent_flat_map< BlobRouteByChunk, BlobRouteValue > copied_blobs; if (!copy_valid_data(move_from_chunk, move_to_chunk, copied_blobs, priority, task_id)) { GCLOGW(task_id, pg_id, NO_SHARD_ID, "failed to copy data from move_from_chunk={} to move_to_chunk={} with priority={}", move_from_chunk, @@ -1354,7 +1327,7 @@ void GCManager::pdev_gc_actor::process_gc_task(chunk_id_t move_from_chunk, uint8 // trigger cp to make sure the offset the the append blk allocator and the wbcache of gc index table are both // flushed. - if (!homestore::hs()->cp_mgr().trigger_cp_flush(true /* force */).get()) { + if (!sisl::async::sync_get(homestore::hs()->cp_mgr().trigger_cp_flush(true /* force */))) { GCLOGW(task_id, pg_id, NO_SHARD_ID, "expect gc index table and blk allocator to be flushed but failed!"); return; } @@ -1420,8 +1393,8 @@ bool GCManager::pdev_gc_actor::process_after_gc_metablk_persisted( #endif // trigger again to make sure the pg index wbcache is persisted. - auto fut = homestore::hs()->cp_mgr().trigger_cp_flush(true /* force */); - RELEASE_ASSERT(std::move(fut).get(), "expect pg index table to be flushed but failed!"); + RELEASE_ASSERT(sisl::async::sync_get(homestore::hs()->cp_mgr().trigger_cp_flush(true /* force */)), + "expect pg index table to be flushed but failed!"); // update the reserved chunk meta blk. we need to do this before gc_task_sb is destroyed because if we do this after // gc_task_sb is destroyed and crash happens before we update reserved_chunk metablk, then we have no chance to @@ -1476,7 +1449,7 @@ GCManager::pdev_gc_actor::~pdev_gc_actor() { GCManager::pdev_gc_actor::gc_task_guard::~gc_task_guard() { m_gc_actor->on_gc_task_completed(priority, pg_id, move_from_chunk, move_to_chunk, vchunk_id, success, task_id); - task.setValue(success); + task->complete(success); if (priority == static_cast< uint8_t >(task_priority::normal)) { m_gc_actor->m_pending_normal_gc_task_count.fetch_sub(1, std::memory_order_relaxed); } diff --git a/src/lib/homestore_backend/gc_manager.hpp b/src/lib/homestore_backend/gc_manager.hpp index 7e03153bd..d121e4d2e 100644 --- a/src/lib/homestore_backend/gc_manager.hpp +++ b/src/lib/homestore_backend/gc_manager.hpp @@ -4,24 +4,21 @@ #include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#pragma GCC diagnostic pop -#include -#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuninitialized" -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#pragma GCC diagnostic pop +#include +#include #include #include +#include +#include +#include +#include +#include + #include #include -#include +#include #include #include "heap_chunk_selector.h" @@ -155,14 +152,10 @@ class GCManager { // Backlog / pressure snapshot gauges. Values refreshed once per scan cycle by // GCManager::scan_chunks_for_gc; worst-case staleness = gc_scan_interval_sec. - REGISTER_GAUGE(pending_gc_bytes, - "Total reclaimable garbage bytes in PG-owned chunks on this pdev"); - REGISTER_GAUGE(eligible_gc_bytes, - "Reclaimable bytes currently eligible for normal GC on this pdev"); - REGISTER_GAUGE(eligible_gc_chunk_count, - "Chunks currently eligible for normal GC on this pdev"); - REGISTER_GAUGE(pending_normal_gc_task_count, - "Normal-priority GC tasks queued or running on this pdev"); + REGISTER_GAUGE(pending_gc_bytes, "Total reclaimable garbage bytes in PG-owned chunks on this pdev"); + REGISTER_GAUGE(eligible_gc_bytes, "Reclaimable bytes currently eligible for normal GC on this pdev"); + REGISTER_GAUGE(eligible_gc_chunk_count, "Chunks currently eligible for normal GC on this pdev"); + REGISTER_GAUGE(pending_normal_gc_task_count, "Normal-priority GC tasks queued or running on this pdev"); // Distribution of the pending backlog by garbage-ratio bucket. We register 10 // gauges under a single Prometheus metric name (`pending_gc_chunks_ratio`), @@ -179,18 +172,16 @@ class GCManager { // shape. Prometheus text output is unaffected — it uses HELP (unchanged across // registrations) and disambiguates series by labels. static constexpr std::array< const char*, 10 > kRatioBucketLabels = { - "00-10", "10-20", "20-30", "30-40", "40-50", - "50-60", "60-70", "70-80", "80-90", "90-100"}; + "00-10", "10-20", "20-30", "30-40", "40-50", "50-60", "60-70", "70-80", "80-90", "90-100"}; for (size_t i = 0; i < kRatioBucketLabels.size(); ++i) { const auto lo = i * 10; const auto hi = (i + 1) * 10; - const auto desc = fmt::format( - "Snapshot count of pending chunks with garbage ratio in ({}, {}]% " - "(bucket={})", - lo, hi, kRatioBucketLabels[i]); - ratio_bucket_indices_[i] = m_impl_ptr->register_gauge( - "pending_gc_chunks_ratio", desc, "" /* report_name */, - sisl::metric_label{"bucket", kRatioBucketLabels[i]}); + const auto desc = fmt::format("Snapshot count of pending chunks with garbage ratio in ({}, {}]% " + "(bucket={})", + lo, hi, kRatioBucketLabels[i]); + ratio_bucket_indices_[i] = + m_impl_ptr->register_gauge("pending_gc_chunks_ratio", desc, "" /* report_name */, + sisl::metric_label{"bucket", kRatioBucketLabels[i]}); } register_me_to_farm(); @@ -230,9 +221,8 @@ class GCManager { // Bypass GAUGE_UPDATE for the same reason as bucket registration: we need to // address 10 distinct gauge indices that share one metric name. for (size_t i = 0; i < ratio_bucket_indices_.size(); ++i) { - m_impl_ptr->gauge_update( - ratio_bucket_indices_[i], - static_cast< int64_t >(gc_actor_.get_pending_ratio_bucket(i))); + m_impl_ptr->gauge_update(ratio_bucket_indices_[i], + static_cast< int64_t >(gc_actor_.get_pending_ratio_bucket(i))); } } @@ -268,8 +258,8 @@ class GCManager { struct gc_task_guard { public: gc_task_guard(uint8_t priority, pg_id_t pg_id, chunk_id_t move_from_chunk, chunk_id_t move_to_chunk, - chunk_id_t vchunk_id, uint64_t task_id, folly::Promise< bool >& task, - pdev_gc_actor* gc_actor) : + chunk_id_t vchunk_id, uint64_t task_id, + std::shared_ptr< sisl::async::value_awaitable< bool > > task, pdev_gc_actor* gc_actor) : priority(priority), pg_id(pg_id), move_from_chunk(move_from_chunk), @@ -295,13 +285,14 @@ class GCManager { chunk_id_t move_to_chunk; chunk_id_t vchunk_id; uint64_t task_id; - folly::Promise< bool >& task; + std::shared_ptr< sisl::async::value_awaitable< bool > > task; pdev_gc_actor* m_gc_actor; }; public: void add_reserved_chunk(homestore::superblk< GCManager::gc_reserved_chunk_superblk > reserved_chunk_sb); - folly::SemiFuture< bool > add_gc_task(uint8_t priority, chunk_id_t move_from_chunk); + std::shared_ptr< sisl::async::value_awaitable< bool > > add_gc_task(uint8_t priority, + chunk_id_t move_from_chunk); void handle_recovered_gc_task(homestore::superblk< GCManager::gc_task_superblk >& gc_task_sb); void start(); void stop(); @@ -315,12 +306,8 @@ class GCManager { // Snapshot readers used by pdev_gc_metrics::on_gather. Return the last value published // by GCManager::scan_chunks_for_gc for this pdev; 0 before the first scan completes. - uint64_t get_pending_gc_bytes() const { - return m_pending_gc_bytes.load(std::memory_order_relaxed); - } - uint64_t get_eligible_gc_bytes() const { - return m_eligible_gc_bytes.load(std::memory_order_relaxed); - } + uint64_t get_pending_gc_bytes() const { return m_pending_gc_bytes.load(std::memory_order_relaxed); } + uint64_t get_eligible_gc_bytes() const { return m_eligible_gc_bytes.load(std::memory_order_relaxed); } uint32_t get_eligible_gc_chunk_count() const { return m_eligible_gc_chunk_count.load(std::memory_order_relaxed); } @@ -332,8 +319,7 @@ class GCManager { // the totals locally over all chunks on this pdev, then hand them in via one call so the // metrics stay internally consistent within a scan cycle. Between-gauge drift is bounded // by one scan interval; individual scalars are aligned and therefore torn-read safe. - void publish_scan_snapshot(uint64_t pending_bytes, uint64_t eligible_bytes, - uint32_t eligible_chunks, + void publish_scan_snapshot(uint64_t pending_bytes, uint64_t eligible_bytes, uint32_t eligible_chunks, const std::array< uint32_t, 10 >& ratio_buckets) { m_pending_gc_bytes.store(pending_bytes, std::memory_order_relaxed); m_eligible_gc_bytes.store(eligible_bytes, std::memory_order_relaxed); @@ -344,8 +330,8 @@ class GCManager { } private: - void process_gc_task(chunk_id_t move_from_chunk, uint8_t priority, folly::Promise< bool > task, - const uint64_t task_id); + void process_gc_task(chunk_id_t move_from_chunk, uint8_t priority, + std::shared_ptr< sisl::async::value_awaitable< bool > > task, const uint64_t task_id); // this should be called only after gc_task meta blk is persisted. it will update the pg index table according // to the gc index table. return the move_to_chunk to chunkselector and put move_from_chunk to reserved chunk @@ -359,7 +345,7 @@ class GCManager { // tombstone in the pg index table // return true if the data copy is successful, false otherwise. bool copy_valid_data(chunk_id_t move_from_chunk, chunk_id_t move_to_chunk, - folly::ConcurrentHashMap< BlobRouteByChunk, BlobRouteValue >& copied_blobs, + boost::concurrent_flat_map< BlobRouteByChunk, BlobRouteValue >& copied_blobs, const uint8_t priority, const uint64_t task_id); // before we select a reserved chunk and start gc, we need: @@ -379,7 +365,7 @@ class GCManager { const uint64_t task_id); bool check_blob_consistency( - folly::ConcurrentHashMap< BlobRouteByChunk, BlobRouteValue > const& copied_blobs, + boost::concurrent_flat_map< BlobRouteByChunk, BlobRouteValue > const& copied_blobs, std::vector< std::pair< BlobRouteByChunkKey, BlobRouteValue > > const& valid_blob_indexes, const uint64_t task_id, const pg_id_t pg_id); @@ -392,7 +378,7 @@ class GCManager { friend class gc_task_guard; uint32_t m_pdev_id; std::shared_ptr< HeapChunkSelector > m_chunk_selector; - folly::MPMCQueue< chunk_id_t > m_reserved_chunk_queue; + sisl::BoundedMPMCQueue< chunk_id_t > m_reserved_chunk_queue; std::shared_ptr< GCBlobIndexTable > m_index_table; HSHomeObject* m_hs_home_object{nullptr}; bool m_enable_read_verify; @@ -402,8 +388,8 @@ class GCManager { // which is 30M/s. A block is 4K, so gc can read/write 30M/s / 4K = 7680 blocks per second. RateLimiter m_rate_limiter{HS_BACKEND_DYNAMIC_CONFIG(max_read_write_block_count_per_second)}; - std::shared_ptr< folly::IOThreadPoolExecutor > m_gc_executor; - std::shared_ptr< folly::IOThreadPoolExecutor > m_egc_executor; + std::shared_ptr< boost::asio::thread_pool > m_gc_executor; + std::shared_ptr< boost::asio::thread_pool > m_egc_executor; std::atomic_bool m_is_stopped{true}; // Tracks normal-priority GC tasks that are queued or actively running in m_gc_executor. // Incremented in add_gc_task after a task is enqueued; decremented in on_gc_task_completed. @@ -437,7 +423,7 @@ class GCManager { * @return the future to wait for the task to be completed. false means gc task fails. * TODO:: add error code as the returned value to indicate the reason of failure. */ - folly::SemiFuture< bool > submit_gc_task(task_priority priority, chunk_id_t chunk_id); + sisl::async::task< bool > submit_gc_task(task_priority priority, chunk_id_t chunk_id); /** * try to create a new gc actor for a pdev @@ -448,11 +434,6 @@ class GCManager { std::shared_ptr< pdev_gc_actor > try_create_pdev_gc_actor(uint32_t pdev_id, const homestore::superblk< GCManager::gc_actor_superblk >& gc_actor_sb); - // Returns the garbage ratio percentage [0.0, 100.0] for the given chunk if it is a valid GC candidate, - // or 0.0 if the chunk is not eligible (wrong state, no defrag blks, no pg, or pg not gc-able). - // Uses floating-point arithmetic to avoid truncation for chunks with very few defrag blocks. - float get_chunk_gc_ratio(chunk_id_t chunk_id); - // One-shot snapshot of chunk state relevant to GC decisions. Populated with a single // ExtendedVChunk lookup so scan_chunks_for_gc can compute both submission decisions AND // backlog metrics without a second hash+lock roundtrip. `gc_thresh_low` is the low-watermark @@ -491,9 +472,8 @@ class GCManager { private: std::shared_ptr< HeapChunkSelector > m_chunk_selector; - folly::ConcurrentHashMap< uint32_t, std::shared_ptr< pdev_gc_actor > > m_pdev_gc_actors; + boost::concurrent_flat_map< uint32_t, std::shared_ptr< pdev_gc_actor > > m_pdev_gc_actors; iomgr::timer_handle_t m_gc_timer_hdl{iomgr::null_timer_handle}; - iomgr::io_fiber_t m_gc_timer_fiber{nullptr}; HSHomeObject* m_hs_home_object{nullptr}; std::list< homestore::superblk< GCManager::gc_task_superblk > > m_recovered_gc_tasks; std::unordered_map< pg_id_t, atomic_uint64_t > m_pending_gc_task_num_per_pg; diff --git a/src/lib/homestore_backend/heap_chunk_selector.h b/src/lib/homestore_backend/heap_chunk_selector.h index 1ffa12c16..4a1c69add 100644 --- a/src/lib/homestore_backend/heap_chunk_selector.h +++ b/src/lib/homestore_backend/heap_chunk_selector.h @@ -2,10 +2,10 @@ #include "homeobject/common.hpp" -#include -#include +#include +#include #include -#include +#include #include #include diff --git a/src/lib/homestore_backend/hs_blob_manager.cpp b/src/lib/homestore_backend/hs_blob_manager.cpp index 6dc82be30..56305162a 100644 --- a/src/lib/homestore_backend/hs_blob_manager.cpp +++ b/src/lib/homestore_backend/hs_blob_manager.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include "hs_backend_config.hpp" #include "hs_homeobject.hpp" #include "replication_message.hpp" @@ -14,8 +17,8 @@ SISL_LOGGING_DECL(blobmgr) #define BLOG(level, trace_id, shard_id, blob_id, msg, ...) \ LOG##level##MOD(blobmgr, "[traceID={},shardID=0x{:x},pg={},shard=0x{:x},blob={}] " msg, trace_id, shard_id, \ - (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, \ - ##__VA_ARGS__) + (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), \ + blob_id, ##__VA_ARGS__) #define BLOGT(trace_id, shard_id, blob_id, msg, ...) BLOG(TRACE, trace_id, shard_id, blob_id, msg, ##__VA_ARGS__) #define BLOGD(trace_id, shard_id, blob_id, msg, ...) BLOG(DEBUG, trace_id, shard_id, blob_id, msg, ##__VA_ARGS__) @@ -64,6 +67,13 @@ BlobError toBlobError(ReplServiceError const& e) { } } +BlobError toBlobError(std::error_condition const& e) { + if (e.category() == homestore::repl_error_category_inst()) { + return toBlobError(static_cast< ReplServiceError >(e.value())); + } + return BlobError(BlobErrorCode::UNKNOWN); +} + struct put_blob_req_ctx : public repl_result_ctx< BlobManager::Result< HSHomeObject::BlobInfo > > { uint32_t blob_header_idx_{0}; @@ -88,7 +98,7 @@ BlobManager::AsyncResult< blob_id_t > HSHomeObject::_put_blob(ShardInfo const& s if (is_shutting_down()) { LOGI("service is being shut down"); - return folly::makeUnexpected(BlobErrorCode::SHUTTING_DOWN); + co_return std::unexpected(BlobError(BlobErrorCode::SHUTTING_DOWN)); } incr_pending_request_num(); // check user key size @@ -96,10 +106,10 @@ BlobManager::AsyncResult< blob_id_t > HSHomeObject::_put_blob(ShardInfo const& s BLOGE(tid, shard.id, 0, "input user key length > max_user_key_length {}", blob.user_key.size(), BlobHeader::max_user_key_length); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::INVALID_ARG)); + co_return std::unexpected(BlobError(BlobErrorCode::INVALID_ARG)); } auto& pg_id = shard.placement_group; - shared< homestore::ReplDev > repl_dev; + shared< homestore::repl_dev > repl_dev; blob_id_t new_blob_id; { auto hs_pg = get_hs_pg(pg_id); @@ -107,7 +117,7 @@ BlobManager::AsyncResult< blob_id_t > HSHomeObject::_put_blob(ShardInfo const& s if (hs_pg->pg_state_.is_state_set(PGStateMask::DISK_DOWN)) { LOGW("failed to put blob for pg={}, pg is disk down and not leader", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(BlobErrorCode::NOT_LEADER); + co_return std::unexpected(BlobError(BlobErrorCode::NOT_LEADER)); } repl_dev = hs_pg->repl_dev_; const_cast< HS_PG* >(hs_pg)->durable_entities_update( @@ -124,13 +134,13 @@ BlobManager::AsyncResult< blob_id_t > HSHomeObject::_put_blob(ShardInfo const& s if (!repl_dev->is_leader()) { BLOGW(tid, shard.id, new_blob_id, "failed to put blob for pg={}, not leader", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::NOT_LEADER, repl_dev->get_leader_id())); + co_return std::unexpected(BlobError(BlobErrorCode::NOT_LEADER, repl_dev->get_leader_id())); } if (!repl_dev->is_ready_for_traffic()) { BLOGW(tid, shard.id, new_blob_id, "failed to put blob for pg={}, not ready for traffic", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); + co_return std::unexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); } // Create a put_blob request which allocates for header, key and blob_header, user_key. Data sgs are added later @@ -195,22 +205,19 @@ BlobManager::AsyncResult< blob_id_t > HSHomeObject::_put_blob(ShardInfo const& s BLOGT(tid, req->blob_header()->shard_id, req->blob_header()->blob_id, "Put blob: header={} sgs={}", req->blob_header()->to_string(), req->data_sgs_string()); - repl_dev->async_alloc_write(req->cheader_buf(), req->ckey_buf(), req->data_sgs(), req, false /* part_of_batch */, - tid); - return req->result().deferValue( - [this, req, repl_dev, tid](const auto& result) -> BlobManager::AsyncResult< blob_id_t > { - if (result.hasError()) { - auto err = result.error(); - if (err.getCode() == BlobErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } - decr_pending_request_num(); - return folly::makeUnexpected(err); - } - auto blob_info = result.value(); - BLOGD(tid, blob_info.shard_id, blob_info.blob_id, "Blob Put request: Put blob success blkid={}", - blob_info.pbas.to_string()); - decr_pending_request_num(); - return blob_info.blob_id; - }); + repl_dev->async_alloc_write(req->cheader_buf(), req->ckey_buf(), req->data_sgs(), req, nullptr, tid); + auto result = co_await req->result(); + if (!result) { + auto err = result.error(); + if (err.getCode() == BlobErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } + decr_pending_request_num(); + co_return std::unexpected(err); + } + auto blob_info = result.value(); + BLOGD(tid, blob_info.shard_id, blob_info.blob_id, "Blob Put request: Put blob success blkid={}", + blob_info.pbas.to_string()); + decr_pending_request_num(); + co_return blob_info.blob_id; } bool HSHomeObject::local_add_blob_info(pg_id_t const pg_id, BlobInfo const& blob_info, trace_id_t tid) { @@ -275,7 +282,7 @@ bool HSHomeObject::local_add_blob_info(pg_id_t const pg_id, BlobInfo const& blob } void HSHomeObject::on_blob_put_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, - homestore::MultiBlkId const& pbas, + homestore::multi_blk_id const& pbas, cintrusive< homestore::repl_req_ctx >& hs_ctx) { LOGTRACEMOD(blobmgr, "blob put commit lsn={}, pbas={}", lsn, pbas.to_string()); repl_result_ctx< BlobManager::Result< BlobInfo > >* ctx{nullptr}; @@ -286,7 +293,7 @@ void HSHomeObject::on_blob_put_commit(int64_t lsn, sisl::blob const& header, sis auto msg_header = r_cast< ReplicationMessageHeader const* >(header.cbytes()); if (msg_header->corrupted()) { LOGE("replication message header is corrupted with crc error, lsn={}, traceID={}", lsn, tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH))); } + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); } return; } @@ -309,20 +316,22 @@ void HSHomeObject::on_blob_put_commit(int64_t lsn, sisl::blob const& header, sis } if (lsn >= shard_sealed_lsn) { - homestore::data_service().async_free_blk(pbas).thenValue([lsn, shard_id, blob_id, tid, pbas](auto&& err) { - if (err) { - BLOGW(tid, shard_id, blob_id, "failed to free blob data blk, err={}, lsn={}, blkid={}", err.message(), - lsn, pbas.to_string()); - } else { - BLOGD(tid, shard_id, blob_id, "succeed to free blob data blk, lsn={}, blkid={}", lsn, pbas.to_string()); - } - }); + sisl::async::detach_then( + homestore::data_service().async_free_blk(pbas), [lsn, shard_id, blob_id, tid, pbas](auto&& err) { + if (!err) { + BLOGW(tid, shard_id, blob_id, "failed to free blob data blk, err={}, lsn={}, blkid={}", err.error(), + lsn, pbas.to_string()); + } else { + BLOGD(tid, shard_id, blob_id, "succeed to free blob data blk, lsn={}, blkid={}", lsn, + pbas.to_string()); + } + }); BLOGD(tid, shard_id, blob_id, "try to commit put_blob message to a non-open shard, lsn={}, shard_sealed_lsn={}, skip it!", lsn, shard_sealed_lsn); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::SEALED_SHARD))); } + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::SEALED_SHARD)); } return; } @@ -336,8 +345,11 @@ void HSHomeObject::on_blob_put_commit(int64_t lsn, sisl::blob const& header, sis bool success = local_add_blob_info(pg_id, blob_info, tid); if (ctx) { - ctx->promise_.setValue(success ? BlobManager::Result< BlobInfo >(blob_info) - : folly::makeUnexpected(BlobError(BlobErrorCode::INDEX_ERROR))); + if (success) { + ctx->set_ok(blob_info); + } else { + ctx->set_err(BlobError(BlobErrorCode::INDEX_ERROR)); + } } } @@ -346,7 +358,7 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob(ShardInfo const& shard, trace_id_t tid) const { if (is_shutting_down()) { LOGI("service is being shutdown"); - return folly::makeUnexpected(BlobErrorCode::SHUTTING_DOWN); + co_return std::unexpected(BlobError(BlobErrorCode::SHUTTING_DOWN)); } incr_pending_request_num(); auto& pg_id = shard.placement_group; @@ -374,7 +386,7 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob(ShardInfo const& shard, LOGW("failed to get blob for pg={}, shardID=0x{:x},pg={},shard=0x{:x}, not ready for traffic", pg_id, shard.id, (shard.id >> homeobject::shard_width), (shard.id & homeobject::shard_mask)); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); + co_return std::unexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); } BLOGD(tid, shard.id, blob_id, "Blob Get request: pg={}, group={}, shard=0x{:x}, blob={}, offset={}, len={}", pg_id, @@ -383,21 +395,19 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob(ShardInfo const& shard, if (!r) { BLOGE(tid, shard.id, blob_id, "Blob not found in index during get blob"); decr_pending_request_num(); - return folly::makeUnexpected(r.error()); + co_return std::unexpected(r.error()); } - return _get_blob_data(repl_dev, shard.id, blob_id, req_offset, req_len, r.value() /* blkid*/, tid, - allow_skip_verify) - .deferValue([this](auto&& result) { - decr_pending_request_num(); - return std::forward< decltype(result) >(result); - }); + auto result = co_await _get_blob_data(repl_dev, shard.id, blob_id, req_offset, req_len, r.value() /* blkid*/, tid, + allow_skip_verify); + decr_pending_request_num(); + co_return result; } -BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data(const shared< homestore::ReplDev >& repl_dev, +BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data(const shared< homestore::repl_dev >& repl_dev, shard_id_t shard_id, blob_id_t blob_id, uint64_t req_offset, uint64_t req_len, - const homestore::MultiBlkId& blkid, trace_id_t tid, + const homestore::multi_blk_id& blkid, trace_id_t tid, bool allow_skip_verify) const { auto const blk_size = repl_dev->get_blk_size(); auto const total_size = blkid.blk_count() * blk_size; @@ -406,7 +416,7 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data(const shared< home // to make the optimization worthwhile. This requires req_len > 0 (known exact length). if (allow_skip_verify && req_len > 0 && (req_offset >= blk_size || req_offset + req_len + blk_size <= total_size - _data_block_size)) { - return _get_blob_data_partial(repl_dev, shard_id, blob_id, req_offset, req_len, blkid, tid); + co_return co_await _get_blob_data_partial(repl_dev, shard_id, blob_id, req_offset, req_len, blkid, tid); } sisl::io_blob_safe read_buf{total_size, io_align}; @@ -416,59 +426,48 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data(const shared< home sgs.iovs.emplace_back(iovec{.iov_base = read_buf.bytes(), .iov_len = read_buf.size()}); BLOGD(tid, shard_id, blob_id, "Reading from blkid={} to buf={}", blkid.to_string(), (void*)read_buf.bytes()); - return repl_dev->async_read(blkid, sgs, total_size) - .thenValue([this, tid, blob_id, shard_id, req_len, req_offset, blkid, repl_dev, - read_buf = std::move(read_buf)](auto&& result) mutable -> BlobManager::AsyncResult< Blob > { - if (result) { - BLOGE(tid, shard_id, blob_id, "Failed to get blob: err={}", blob_id, shard_id, result.value()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); - } - - auto verify_result = do_verify_blob(read_buf.cbytes(), shard_id, blob_id); - if (!verify_result.hasValue()) { return folly::makeUnexpected(verify_result.error()); } - std::string user_key = std::move(verify_result.value()); - - BlobHeader const* header = r_cast< BlobHeader const* >(read_buf.cbytes()); - if (req_offset + req_len > header->blob_size) { - BLOGE(tid, shard_id, blob_id, "Invalid offset length requested in get blob offset={} len={} size={}", - req_offset, req_len, header->blob_size); - return folly::makeUnexpected(BlobError(BlobErrorCode::INVALID_ARG)); - } - - // Copy the blob bytes from the offset. If request len is 0, take the - // whole blob size else copy only the request length. - auto res_len = req_len == 0 ? header->blob_size - req_offset : req_len; - auto body = sisl::io_blob_safe(res_len); - uint8_t const* blob_bytes = read_buf.bytes() + header->data_offset; - std::memcpy(body.bytes(), blob_bytes + req_offset, res_len); - - BLOGD(tid, shard_id, blob_id, "Blob get success: blkid={}", blkid.to_string()); - return Blob(std::move(body), std::move(user_key), header->object_offset, repl_dev->get_leader_id()); - }); + auto result = co_await repl_dev->async_read(blkid, sgs, total_size); + if (!result) { + BLOGE(tid, shard_id, blob_id, "Failed to get blob: err={}", result.error()); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } + + auto verify_result = do_verify_blob(read_buf.cbytes(), shard_id, blob_id); + if (!verify_result) { co_return std::unexpected(verify_result.error()); } + std::string user_key = std::move(verify_result.value()); + + BlobHeader const* header = r_cast< BlobHeader const* >(read_buf.cbytes()); + if (req_offset + req_len > header->blob_size) { + BLOGE(tid, shard_id, blob_id, "Invalid offset length requested in get blob offset={} len={} size={}", + req_offset, req_len, header->blob_size); + co_return std::unexpected(BlobError(BlobErrorCode::INVALID_ARG)); + } + + auto res_len = req_len == 0 ? header->blob_size - req_offset : req_len; + auto body = sisl::io_blob_safe(res_len); + uint8_t const* blob_bytes = read_buf.bytes() + header->data_offset; + std::memcpy(body.bytes(), blob_bytes + req_offset, res_len); + + BLOGD(tid, shard_id, blob_id, "Blob get success: blkid={}", blkid.to_string()); + co_return Blob(std::move(body), std::move(user_key), header->object_offset, repl_dev->get_leader_id()); } -BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data_partial(const shared< homestore::ReplDev >& repl_dev, +BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data_partial(const shared< homestore::repl_dev >& repl_dev, shard_id_t shard_id, blob_id_t blob_id, uint64_t req_offset, uint64_t req_len, - const homestore::MultiBlkId& blkid, + const homestore::multi_blk_id& blkid, trace_id_t tid) const { auto const blk_size = repl_dev->get_blk_size(); - - // In v4, BlobHeader is fixed at _data_block_size (4KB), and data starts immediately after - // We can skip reading the header entirely for partial reads that don't overlap with it auto const fixed_data_offset = _data_block_size; - // Calculate byte range within the storage (including header) auto const read_start_byte = fixed_data_offset + req_offset; auto const read_end_byte = read_start_byte + req_len; - // Calculate which blocks we need to read uint32_t start_blk = read_start_byte / blk_size; uint32_t num_blks = (read_end_byte + blk_size - 1) / blk_size - start_blk; uint32_t read_size = num_blks * blk_size; - // Create MultiBlkId for the range we need - homestore::MultiBlkId read_blkid; + homestore::multi_blk_id read_blkid; read_blkid.add(blkid.blk_num() + start_blk, num_blks, blkid.chunk_num()); sisl::io_blob_safe read_buf{read_size, io_align}; @@ -476,37 +475,24 @@ BlobManager::AsyncResult< Blob > HSHomeObject::_get_blob_data_partial(const shar sgs.size = read_size; sgs.iovs.emplace_back(iovec{.iov_base = read_buf.bytes(), .iov_len = read_buf.size()}); - BLOGD(tid, shard_id, blob_id, - "Reading partial data: offset={}, len={}, full_blkid={}, read_blkid={}, start_blk={}, num_blks={}", - req_offset, req_len, blkid.to_string(), read_blkid.to_string(), start_blk, num_blks); - - return repl_dev->async_read(read_blkid, sgs, read_size) - .thenValue([tid, blob_id, shard_id, req_offset, req_len, blkid, repl_dev, start_blk, blk_size, - read_buf = std::move(read_buf)](auto&& result) mutable -> BlobManager::AsyncResult< Blob > { - if (result) { - BLOGE(tid, shard_id, blob_id, "Failed to read partial data: err={}", result.value()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); - } - - // Calculate offset within read buffer - auto const data_start_in_storage = _data_block_size; - auto const req_start_in_storage = data_start_in_storage + req_offset; - auto const read_start_in_storage = start_blk * blk_size; - auto const offset_in_buf = req_start_in_storage - read_start_in_storage; - - uint8_t const* blob_bytes = read_buf.bytes() + offset_in_buf; - - // Copy the requested blob bytes - auto body = sisl::io_blob_safe(req_len); - std::memcpy(body.bytes(), blob_bytes, req_len); - - BLOGD(tid, shard_id, blob_id, "Blob partial get success: blkid={}", blkid.to_string()); - // user_key and object_offset are not available in partial read mode - return Blob(std::move(body), std::string{}, 0 /* object_offset */, repl_dev->get_leader_id()); - }); + BLOGD(tid, shard_id, blob_id, "Partial reading from blkid={} (offset={}) to buf={}", read_blkid.to_string(), + start_blk, (void*)read_buf.bytes()); + auto result = co_await repl_dev->async_read(read_blkid, sgs, read_size); + if (!result) { + BLOGE(tid, shard_id, blob_id, "Failed to get blob partial: err={}", result.error()); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } + + auto const byte_offset_in_read = read_start_byte - (start_blk * blk_size); + uint8_t const* blob_bytes = read_buf.bytes() + byte_offset_in_read; + auto body = sisl::io_blob_safe(req_len); + std::memcpy(body.bytes(), blob_bytes, req_len); + + BLOGD(tid, shard_id, blob_id, "Blob partial get success: blkid={}", blkid.to_string()); + co_return Blob(std::move(body), std::string{}, 0 /* object_offset */, repl_dev->get_leader_id()); } -homestore::ReplResult< homestore::blk_alloc_hints > +homestore::result< homestore::blk_alloc_hints > HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< homestore::repl_req_ctx >& hs_ctx) { repl_result_ctx< BlobManager::Result< BlobInfo > >* ctx{nullptr}; if (hs_ctx && hs_ctx->is_proposer()) { @@ -519,8 +505,8 @@ HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< LOGE("traceID={}, shardID=0x{:x}, pg={}, shard=0x{:x}, replication message header is corrupted with crc error", tid, msg_header->shard_id, (msg_header->shard_id >> homeobject::shard_width), (msg_header->shard_id & homeobject::shard_mask)); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH))); } - return folly::makeUnexpected(homestore::ReplServiceError::FAILED); + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); } + return std::unexpected(make_error_condition(homestore::ReplServiceError::FAILED)); } auto hs_pg = get_hs_pg(msg_header->pg_id); @@ -530,8 +516,8 @@ HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< "retry this later", tid, msg_header->shard_id, (msg_header->shard_id >> homeobject::shard_width), (msg_header->shard_id & homeobject::shard_mask), msg_header->pg_id); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_PG))); } - return folly::makeUnexpected(homestore::ReplServiceError::RESULT_NOT_EXIST_YET); + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::UNKNOWN_PG)); } + return std::unexpected(make_error_condition(homestore::ReplServiceError::RESULT_NOT_EXIST_YET)); } std::scoped_lock lock_guard(_shard_lock); @@ -541,8 +527,8 @@ HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< "engine will retry this later", tid, msg_header->shard_id, (msg_header->shard_id >> homeobject::shard_width), (msg_header->shard_id & homeobject::shard_mask)); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_SHARD))); } - return folly::makeUnexpected(homestore::ReplServiceError::RESULT_NOT_EXIST_YET); + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::UNKNOWN_SHARD)); } + return std::unexpected(make_error_condition(homestore::ReplServiceError::RESULT_NOT_EXIST_YET)); } auto hs_shard = d_cast< HS_Shard* >((*shard_iter->second).get()); @@ -557,12 +543,12 @@ HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< auto pg_index_table = hs_pg->index_table_; if (!pg_index_table) { LOGW("index table is not found for pg={}, skip statistics refresh", msg_header->pg_id); - return folly::makeUnexpected(homestore::ReplServiceError::RESULT_NOT_EXIST_YET); + return std::unexpected(make_error_condition(homestore::ReplServiceError::RESULT_NOT_EXIST_YET)); } // check if the blob already exists, if yes, return the blk id auto r = get_blob_from_index_table(pg_index_table, msg_header->shard_id, msg_header->blob_id); - if (r.hasValue()) { + if (r) { BLOGT(tid, msg_header->shard_id, msg_header->blob_id, "Blob has already been persisted, blk_num={}, blk_count={}", r.value().blk_num(), r.value().blk_count()); @@ -576,7 +562,7 @@ HSHomeObject::blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< BlobManager::NullAsyncResult HSHomeObject::_del_blob(ShardInfo const& shard, blob_id_t blob_id, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down"); - return folly::makeUnexpected(BlobErrorCode::SHUTTING_DOWN); + co_return std::unexpected(BlobError(BlobErrorCode::SHUTTING_DOWN)); } incr_pending_request_num(); BLOGT(tid, shard.id, blob_id, "deleting blob"); @@ -587,7 +573,7 @@ BlobManager::NullAsyncResult HSHomeObject::_del_blob(ShardInfo const& shard, blo if (hs_pg->pg_state_.is_state_set(PGStateMask::DISK_DOWN)) { LOGW("failed to delete blob for pg={}, pg is disk down and not leader", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(BlobErrorCode::NOT_LEADER); + co_return std::unexpected(BlobError(BlobErrorCode::NOT_LEADER)); } auto repl_dev = hs_pg->repl_dev_; @@ -599,13 +585,13 @@ BlobManager::NullAsyncResult HSHomeObject::_del_blob(ShardInfo const& shard, blo if (!repl_dev->is_leader()) { BLOGW(tid, shard.id, blob_id, "failed to del blob, not leader"); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::NOT_LEADER, repl_dev->get_leader_id())); + co_return std::unexpected(BlobError(BlobErrorCode::NOT_LEADER, repl_dev->get_leader_id())); } if (!repl_dev->is_ready_for_traffic()) { BLOGW(tid, shard.id, blob_id, "failed to del blob, not ready for traffic"); decr_pending_request_num(); - return folly::makeUnexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); + co_return std::unexpected(BlobError(BlobErrorCode::RETRY_REQUEST)); } // Create an unaligned header request unaligned @@ -621,21 +607,18 @@ BlobManager::NullAsyncResult HSHomeObject::_del_blob(ShardInfo const& shard, blo // Populate the key std::memcpy(req->key_buf().bytes(), &blob_id, sizeof(blob_id_t)); - repl_dev->async_alloc_write(req->cheader_buf(), req->ckey_buf(), sisl::sg_list{}, req, false /* part_of_batch */, - tid); - return req->result().deferValue( - [this, repl_dev, tid](const auto& result) -> folly::Expected< folly::Unit, BlobError > { - if (result.hasError()) { - auto err = result.error(); - if (err.getCode() == BlobErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } - decr_pending_request_num(); - return folly::makeUnexpected(err); - } - auto blob_info = result.value(); - BLOGT(tid, blob_info.shard_id, blob_info.blob_id, "Delete blob successful"); - decr_pending_request_num(); - return folly::Unit(); - }); + repl_dev->async_alloc_write(req->cheader_buf(), req->ckey_buf(), sisl::sg_list{}, req, nullptr, tid); + auto result = co_await req->result(); + if (!result) { + auto err = result.error(); + if (err.getCode() == BlobErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } + decr_pending_request_num(); + co_return std::unexpected(err); + } + [[maybe_unused]] auto blob_info = result.value(); + BLOGT(tid, blob_info.shard_id, blob_info.blob_id, "Delete blob successful"); + decr_pending_request_num(); + co_return std::monostate{}; } void HSHomeObject::on_blob_del_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, @@ -649,7 +632,7 @@ void HSHomeObject::on_blob_del_commit(int64_t lsn, sisl::blob const& header, sis if (msg_header->corrupted()) { BLOGE(tid, msg_header->shard_id, *r_cast< blob_id_t const* >(key.cbytes()), "replication message header is corrupted with crc error, lsn={} header={}", lsn, msg_header->to_string()); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH))); } + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); } return; } @@ -687,12 +670,8 @@ void HSHomeObject::on_blob_del_commit(int64_t lsn, sisl::blob const& header, sis auto existing_pbas = existing_value.pbas(); if (sisl_likely(existing_pbas != tombstone_pbas)) { - repl_dev->async_free_blks(lsn, existing_pbas).thenValue([hs_pg](auto&& err) { - if (err) { - // even if async_free_blks fails, as the blob is already updated to tombstone, it will be gc - // eventually. - LOGE("Failed to free blocks for tombstoned blob, error={}", err.value()); - } + sisl::async::detach_then(repl_dev->async_free_blks(lsn, existing_pbas), [hs_pg](auto&& err) { + if (!err) { LOGE("Failed to free blocks for tombstoned blob, error={}", err.error()); } const_cast< HS_PG* >(hs_pg)->durable_entities_update([](auto& de) { de.active_blob_count.fetch_sub(1, std::memory_order_relaxed); de.tombstone_blob_count.fetch_add(1, std::memory_order_relaxed); @@ -703,7 +682,7 @@ void HSHomeObject::on_blob_del_commit(int64_t lsn, sisl::blob const& header, sis } } - if (ctx) { ctx->promise_.setValue(BlobManager::Result< BlobInfo >({shard_id, blob_id, tombstone_pbas})); } + if (ctx) { ctx->set_ok({shard_id, blob_id, tombstone_pbas}); } } void HSHomeObject::compute_blob_payload_hash(BlobHeader::HashAlgorithm algorithm, const uint8_t* blob_bytes, @@ -734,7 +713,7 @@ void HSHomeObject::on_blob_message_rollback(int64_t lsn, sisl::blob const& heade const ReplicationMessageHeader* msg_header = r_cast< const ReplicationMessageHeader* >(header.cbytes()); if (msg_header->corrupted()) { LOGW("replication message header is corrupted with crc error, lsn={}, traceID={}", lsn, tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH))); } + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); } return; } @@ -743,7 +722,7 @@ void HSHomeObject::on_blob_message_rollback(int64_t lsn, sisl::blob const& heade case ReplicationMessageType::DEL_BLOB_MSG: { // TODO:: add rollback logic for put_blob and del_blob if necessary LOGI("traceID={}, lsn={}, mes_type={} is rollbacked", tid, lsn, msg_header->msg_type); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(BlobError(BlobErrorCode::ROLL_BACK))); } + if (ctx) { ctx->set_err(BlobError(BlobErrorCode::ROLL_BACK)); } break; } default: { @@ -762,19 +741,19 @@ BlobManager::Result< std::string > HSHomeObject::do_verify_blob(const void* blob // Check if header is valid if (!header->valid()) { LOGE("Invalid header found: [header={}]", header->to_string()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); + return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); } // Check if shard_id matches if (header->shard_id != expected_shard_id) { LOGE("Invalid shard_id in header: [header={}]", header->to_string()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); + return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); } // Check if blob_id matches (only if expected_blob_id != 0) if (expected_blob_id != 0 && header->blob_id != expected_blob_id) { LOGE("Invalid blob_id in header: [header={}]", header->to_string()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); + return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); } // Verify hash @@ -786,7 +765,7 @@ BlobManager::Result< std::string > HSHomeObject::do_verify_blob(const void* blob if (std::memcmp(computed_hash, header->hash, BlobHeader::blob_max_hash_len) != 0) { LOGE("Hash mismatch header, [header={}] [computed={:np}]", header->to_string(), spdlog::to_hex(computed_hash, computed_hash + BlobHeader::blob_max_hash_len)); - return folly::makeUnexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); + return std::unexpected(BlobError(BlobErrorCode::CHECKSUM_MISMATCH)); } return header->get_user_key().value(); // Must have a value as header verified above @@ -802,6 +781,6 @@ bool HSHomeObject::verify_blob(const void* blob, const shard_id_t shard_id, cons // Use the new _verify_blob method auto result = do_verify_blob(blob, shard_id, blob_id); - return result.hasValue(); + return (bool)result; } } // namespace homeobject diff --git a/src/lib/homestore_backend/hs_cp_callbacks.cpp b/src/lib/homestore_backend/hs_cp_callbacks.cpp index 711af667e..a94470c5d 100644 --- a/src/lib/homestore_backend/hs_cp_callbacks.cpp +++ b/src/lib/homestore_backend/hs_cp_callbacks.cpp @@ -15,6 +15,7 @@ *********************************************************************************/ #include #include +#include #include "hs_homeobject.hpp" using homestore::CP; @@ -29,7 +30,7 @@ std::unique_ptr< CPContext > HSHomeObject::MyCPCallbacks::on_switchover_cp(CP* c // when cp_flush is called, it means that all the dirty candidates are already in the dirty list. // new dirty candidates will arrive on next cp's context. -folly::Future< bool > HSHomeObject::MyCPCallbacks::cp_flush(CP* cp) { +sisl::async::task< bool > HSHomeObject::MyCPCallbacks::cp_flush(CP* cp) { std::vector< HSHomeObject::HS_PG* > dirty_pg_list; dirty_pg_list.reserve(home_obj_._pg_map.size()); @@ -75,7 +76,7 @@ folly::Future< bool > HSHomeObject::MyCPCallbacks::cp_flush(CP* cp) { } } - return folly::makeFuture< bool >(true); + co_return true; } void HSHomeObject::MyCPCallbacks::cp_cleanup(CP* cp) {} diff --git a/src/lib/homestore_backend/hs_homeobject.cpp b/src/lib/homestore_backend/hs_homeobject.cpp index 0a646c00f..84c68b056 100644 --- a/src/lib/homestore_backend/hs_homeobject.cpp +++ b/src/lib/homestore_backend/hs_homeobject.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include #include #include @@ -51,7 +51,7 @@ extern std::shared_ptr< HomeObject > init_homeobject(std::weak_ptr< HomeObjectAp } // repl application to init homestore -class HSReplApplication : public homestore::ReplApplication { +class HSReplApplication : public homestore::repl_application { public: HSReplApplication(homestore::repl_impl_type impl_type, bool need_timeline_consistency, HSHomeObject* home_object, std::weak_ptr< HomeObjectApplication > ho_application) : @@ -68,7 +68,7 @@ class HSReplApplication : public homestore::ReplApplication { bool need_timeline_consistency() const override { return _need_timeline_consistency; } - std::shared_ptr< homestore::ReplDevListener > create_repl_dev_listener(homestore::group_id_t group_id) override { + std::shared_ptr< homestore::repl_dev_listener > create_repl_dev_listener(homestore::group_id_t group_id) override { std::scoped_lock lock_guard(_repl_sm_map_lock); auto [it, inserted] = _repl_sm_map.emplace(group_id, nullptr); if (inserted) { it->second = std::make_shared< ReplicationStateMachine >(_home_object); } @@ -90,22 +90,33 @@ class HSReplApplication : public homestore::ReplApplication { std::pair< std::string, uint16_t > lookup_peer(homestore::replica_id_t uuid) const override { std::string endpoint; - // for folly::uri to parse correctly, we need to add "http://" prefix - static std::string const uri_prefix{"http://"}; if (auto app = _ho_application.lock(); app) { - endpoint = fmt::format("{}{}", uri_prefix, app->lookup_peer(uuid)); + endpoint = app->lookup_peer(uuid); } else { LOGW("HomeObjectApplication lifetime unexpected! Shutdown in progress?"); return {}; } + // Hand-parse host:port (formerly folly::Uri). Strip optional http(s)://, split on last ':'. + std::string_view ep{endpoint}; + if (ep.starts_with("http://")) { + ep.remove_prefix(7); + } else if (ep.starts_with("https://")) { + ep.remove_prefix(8); + } + std::pair< std::string, uint16_t > host_port; + auto const colon = ep.rfind(':'); + if (colon == std::string_view::npos) { + LOGE("can't extract host:port from uuid {}, endpoint={}", to_string(uuid), endpoint); + return {}; + } + host_port.first = std::string{ep.substr(0, colon)}; try { - folly::Uri uri(endpoint); - host_port.first = uri.host(); - host_port.second = uri.port(); - } catch (std::runtime_error const& e) { - LOGE("can't extract host from uuid {}, endpoint={}; error={}", to_string(uuid), endpoint, e.what()); + host_port.second = static_cast< uint16_t >(std::stoul(std::string{ep.substr(colon + 1)})); + } catch (std::exception const& e) { + LOGE("can't extract port from uuid {}, endpoint={}; error={}", to_string(uuid), endpoint, e.what()); + return {}; } return host_port; } @@ -139,7 +150,7 @@ class HSReplApplication : public homestore::ReplApplication { uint64_t HSHomeObject::_hs_chunk_size = HS_CHUNK_SIZE; DevType HSHomeObject::get_device_type(string const& devname) { - const iomgr::drive_type dtype = iomgr::DriveInterface::get_drive_type(devname); + const iomgr::drive_type dtype = iomgr::type_of(devname); if (dtype == iomgr::drive_type::block_hdd || dtype == iomgr::drive_type::file_on_hdd) { return DevType::HDD; } if (dtype == iomgr::drive_type::file_on_nvme || dtype == iomgr::drive_type::block_nvme) { return DevType::NVME; } return DevType::UNSUPPORTED; @@ -149,9 +160,8 @@ void HSHomeObject::init_homestore() { auto app = _application.lock(); RELEASE_ASSERT(app, "HomeObjectApplication lifetime unexpected!"); - LOGI("Starting iomgr with {} threads, spdk={}", app->threads(), false); - ioenvironment.with_iomgr(iomgr::iomgr_params{.num_threads = app->threads(), .is_spdk = app->spdk_mode()}) - .with_http_server(); + LOGI("Starting iomgr with {} threads", app->threads()); + ioenvironment.with_iomgr(iomgr::iomgr_params{.num_threads = app->threads()}).with_http_server(); http_mgr_ = std::make_unique< HttpManager >(*this); @@ -192,7 +202,7 @@ void HSHomeObject::init_homestore() { uint64_t max_snapshot_batch_size_in_bytes = HS_BACKEND_DYNAMIC_CONFIG(max_snapshot_batch_size_mb) * Mi; RELEASE_ASSERT(max_snapshot_batch_size_in_bytes <= INT_MAX, "snapshot size is larger than the grpc limit"); bool need_format = - HomeStore::instance() + home_store::instance() ->with_index_service(std::make_unique< BlobIndexServiceCallbacks >(this)) .with_repl_data_service(repl_app, chunk_selector_) .start(hs_input_params{.devices = device_info, @@ -288,7 +298,7 @@ void HSHomeObject::init_homestore() { LOGI(" vdev [{}]: dev_type={} size_pct={:.1f}%", svc_name(svc), params.dev_type, params.size_pct); } - HomeStore::instance()->format_and_start(std::move(format_opts)); + home_store::instance()->format_and_start(std::move(format_opts)); } else { RELEASE_ASSERT(!_our_id.is_nil(), "No SvcId read after HomeStore recovery!"); auto const new_id = app->discover_svcid(_our_id); @@ -306,7 +316,11 @@ void HSHomeObject::init_homestore() { } // when reaching here, all the logs before dc_lsn have been replayed. we can start gc now. - if (HS_BACKEND_DYNAMIC_CONFIG(enable_gc)) { + auto const enable_gc = HS_BACKEND_DYNAMIC_CONFIG(enable_gc); + auto const enable_scrubber = HS_BACKEND_DYNAMIC_CONFIG(enable_scrubber); + if (enable_gc || enable_scrubber) { pin_bg_timer_reactor(); } + + if (enable_gc) { LOGI("Starting GC manager"); gc_mgr_->start(); } else { @@ -314,7 +328,7 @@ void HSHomeObject::init_homestore() { } // start scrubber - if (HS_BACKEND_DYNAMIC_CONFIG(enable_scrubber)) { + if (enable_scrubber) { LOGI("Starting scrub manager"); scrub_mgr_->start(); } else { @@ -330,7 +344,7 @@ void HSHomeObject::on_replica_restart() { homestore::meta_service().register_handler( _pg_meta_name, [this](homestore::meta_blk* mblk, sisl::byte_view buf, size_t size) { - on_pg_meta_blk_found(std::move(buf), voidptr_cast(mblk)); + on_pg_meta_blk_found(std::move(buf), mblk); }, nullptr, true); @@ -471,8 +485,8 @@ void HSHomeObject::init_timer_thread() { void HSHomeObject::init_cp() { using namespace homestore; // Register to CP for flush dirty buffers; - HomeStore::instance()->cp_mgr().register_consumer(cp_consumer_t::HS_CLIENT, - std::move(std::make_unique< MyCPCallbacks >(*this))); + home_store::instance()->cp_mgr().register_consumer(cp_consumer_t::HS_CLIENT, + std::move(std::make_unique< MyCPCallbacks >(*this))); } // void HSHomeObject::trigger_timed_events() { persist_pg_sb(); } @@ -480,7 +494,7 @@ void HSHomeObject::init_cp() { void HSHomeObject::register_homestore_metablk_callback() { // register some callbacks for metadata recovery; using namespace homestore; - HomeStore::instance()->meta_service().register_handler( + home_store::instance()->meta_service().register_handler( _svc_meta_name, [this](homestore::meta_blk* mblk, sisl::byte_view buf, size_t size) { auto svc_sb = homestore::superblk< svc_info_superblk_t >(_svc_meta_name); @@ -493,6 +507,18 @@ void HSHomeObject::register_homestore_metablk_callback() { HSHomeObject::~HSHomeObject() { LOGI("HSHomeObject: Executing destruct procedure"); } +void HSHomeObject::pin_bg_timer_reactor() { + if (bg_timer_reactor_) { return; } + + // Pin GC/Scrub timers to one existing worker reactor. Do NOT create_reactor() a user reactor: + // late user reactors get every all_io/global iodev attached via on_reactor_start, and devices + // with an empty cb then crash in on_user_iodev_notification (std::bad_function_call). + iomanager.run_on_wait(iomgr::reactor_regex::random_worker, + [this]() { bg_timer_reactor_ = iomanager.this_reactor(); }); + RELEASE_ASSERT(bg_timer_reactor_, "failed to pin HomeObject background timer reactor"); + LOGI("HomeObject background timers pinned to worker reactor"); +} + void HSHomeObject::shutdown() { if (is_shutting_down()) { LOGI("HomeObject is already shutting down"); @@ -526,8 +552,8 @@ void HSHomeObject::shutdown() { if (scrub_mgr_) scrub_mgr_->stop(); LOGI("start shutting down HomeStore"); - homestore::HomeStore::instance()->shutdown(); - homestore::HomeStore::reset_instance(); + homestore::home_store::instance()->shutdown(); + homestore::home_store::reset_instance(); gc_mgr_.reset(); scrub_mgr_.reset(); iomanager.stop(); diff --git a/src/lib/homestore_backend/hs_homeobject.hpp b/src/lib/homestore_backend/hs_homeobject.hpp index 9a34448bb..d2e59dee8 100644 --- a/src/lib/homestore_backend/hs_homeobject.hpp +++ b/src/lib/homestore_backend/hs_homeobject.hpp @@ -2,11 +2,14 @@ #include #include +#include +#include +#include #include #include #include -#include +#include #include "heap_chunk_selector.h" #include "lib/homeobject_impl.hpp" @@ -41,8 +44,11 @@ class HttpManager; static constexpr uint64_t io_align{512}; PGError toPgError(homestore::ReplServiceError const&); +PGError toPgError(std::error_condition const&); BlobError toBlobError(homestore::ReplServiceError const&); +BlobError toBlobError(std::error_condition const&); ShardError toShardError(homestore::ReplServiceError const&); +ShardError toShardError(std::error_condition const&); ENUM(PGState, uint8_t, ALIVE = 0, DESTROYED); class HSHomeObject : public HomeObjectImpl { @@ -313,7 +319,7 @@ class HSHomeObject : public HomeObjectImpl { public: std::unique_ptr< homestore::CPContext > on_switchover_cp(homestore::CP* cur_cp, homestore::CP* new_cp) override; - folly::Future< bool > cp_flush(homestore::CP* cp) override; + sisl::async::task< bool > cp_flush(homestore::CP* cp) override; void cp_cleanup(homestore::CP* cp) override; int cp_progress_percent() override; @@ -369,7 +375,7 @@ class HSHomeObject : public HomeObjectImpl { }; homestore::superblk< pg_info_superblk > pg_sb_; - shared< homestore::ReplDev > repl_dev_; + shared< homestore::repl_dev > repl_dev_; std::shared_ptr< BlobIndexTable > index_table_; PGMetrics metrics_; HSHomeObject& home_obj_; @@ -382,9 +388,9 @@ class HSHomeObject : public HomeObjectImpl { mutable homestore::superblk< snapshot_rcvr_info_superblk > snp_rcvr_info_sb_; mutable homestore::superblk< snapshot_rcvr_shard_list_superblk > snp_rcvr_shard_list_sb_; - HS_PG(PGInfo info, shared< homestore::ReplDev > rdev, shared< BlobIndexTable > index_table, + HS_PG(PGInfo info, shared< homestore::repl_dev > rdev, shared< BlobIndexTable > index_table, std::shared_ptr< const std::vector< homestore::chunk_num_t > > pg_chunk_ids, HSHomeObject& home_obj); - HS_PG(homestore::superblk< pg_info_superblk >&& sb, shared< homestore::ReplDev > rdev, HSHomeObject& home_obj); + HS_PG(homestore::superblk< pg_info_superblk >&& sb, shared< homestore::repl_dev > rdev, HSHomeObject& home_obj); ~HS_PG() override = default; static PGInfo pg_info_from_sb(homestore::superblk< pg_info_superblk > const& sb); @@ -556,7 +562,7 @@ class HSHomeObject : public HomeObjectImpl { struct BlobInfo { shard_id_t shard_id; blob_id_t blob_id; - homestore::MultiBlkId pbas; + homestore::multi_blk_id pbas; }; enum class BlobState : uint8_t { @@ -565,7 +571,7 @@ class HSHomeObject : public HomeObjectImpl { ALL = 2, }; - inline const static homestore::MultiBlkId tombstone_pbas{0, 0, 0}; + inline const static homestore::multi_blk_id tombstone_pbas{0, 0, 0}; inline const static std::string delete_marker_blob_data{"HOMEOBJECT_BLOB_DELETE_MARKER"}; // ask followers to scrub @@ -599,9 +605,10 @@ class HSHomeObject : public HomeObjectImpl { private: PG* get_pg_metadata() const; objId expected_next_obj_id() const; - BlobManager::AsyncResult< blob_read_result > load_blob_data(const BlobInfo& blob_info); + BlobManager::AsyncResult< blob_read_result > load_blob_data(BlobInfo blob_info); + BlobManager::AsyncResult< blob_read_result > load_prefetched_blob(BlobInfo info, Clock::time_point blob_start); BlobManager::AsyncResult< blob_read_result > load_blob_data_with_blkid(shard_id_t shard_id, blob_id_t blob_id, - homestore::MultiBlkId blkid); + homestore::multi_blk_id blkid); bool prefetch_blobs_snapshot_data(); void pack_resync_message(sisl::io_blob_safe& dest_blob, SyncMessageType type); @@ -649,7 +656,7 @@ class HSHomeObject : public HomeObjectImpl { HSHomeObject& home_obj_; uint64_t snp_start_lsn_; - shared< homestore::ReplDev > repl_dev_; + shared< homestore::repl_dev > repl_dev_; uint64_t max_batch_size_; std::unique_ptr< DonerSnapshotMetrics > metrics_; bool stopped_{false}; @@ -671,7 +678,7 @@ class HSHomeObject : public HomeObjectImpl { constexpr static shard_id_t invalid_shard_id = 0; constexpr static shard_id_t shard_list_end_marker = ULLONG_MAX; - SnapshotReceiveHandler(HSHomeObject& home_obj, shared< homestore::ReplDev > repl_dev); + SnapshotReceiveHandler(HSHomeObject& home_obj, shared< homestore::repl_dev > repl_dev); int process_pg_snapshot_data(ResyncPGMetaData const& pg_meta); int process_shard_snapshot_data(ResyncShardMetaData const& shard_meta); @@ -761,11 +768,11 @@ class HSHomeObject : public HomeObjectImpl { }; HSHomeObject& home_obj_; - const shared< homestore::ReplDev > repl_dev_; + const shared< homestore::repl_dev > repl_dev_; std::shared_ptr< SnapshotContext > ctx_; std::unique_ptr< ReceiverSnapshotMetrics > metrics_; - folly::Future< bool > cp_fut; + std::optional< sisl::async::task< bool > > cp_fut; // Update the snp_info superblock void update_snp_info_sb(bool init = false); @@ -778,28 +785,32 @@ class HSHomeObject : public HomeObjectImpl { shared< GCManager > gc_mgr_; shared< ScrubManager > scrub_mgr_; unique< HttpManager > http_mgr_; + // Shared by GCManager and ScrubManager: one pinned worker reactor for thread timers. + iomgr::IOReactor* bg_timer_reactor_{nullptr}; + + void pin_bg_timer_reactor(); static constexpr size_t max_zpad_bufs = _data_block_size / io_align; std::array< sisl::io_blob_safe, max_zpad_bufs > zpad_bufs_; // Zero padded buffers for blob payload. - static homestore::ReplicationService& hs_repl_service() { return homestore::hs()->repl_service(); } + static homestore::replication_service& hs_repl_service() { return homestore::hs()->repl_service(); } // blob related - BlobManager::AsyncResult< Blob > _get_blob_data(const shared< homestore::ReplDev >& repl_dev, shard_id_t shard_id, + BlobManager::AsyncResult< Blob > _get_blob_data(const shared< homestore::repl_dev >& repl_dev, shard_id_t shard_id, blob_id_t blob_id, uint64_t req_offset, uint64_t req_len, - const homestore::MultiBlkId& blkid, trace_id_t tid, + const homestore::multi_blk_id& blkid, trace_id_t tid, bool allow_skip_verify = false) const; - BlobManager::AsyncResult< Blob > _get_blob_data_partial(const shared< homestore::ReplDev >& repl_dev, + BlobManager::AsyncResult< Blob > _get_blob_data_partial(const shared< homestore::repl_dev >& repl_dev, shard_id_t shard_id, blob_id_t blob_id, uint64_t req_offset, - uint64_t req_len, const homestore::MultiBlkId& blkid, + uint64_t req_len, const homestore::multi_blk_id& blkid, trace_id_t tid) const; // create pg related - static PGManager::NullAsyncResult do_create_pg(cshared< homestore::ReplDev > repl_dev, PGInfo&& pg_info, + static PGManager::NullAsyncResult do_create_pg(cshared< homestore::repl_dev > repl_dev, PGInfo&& pg_info, trace_id_t tid = 0); - folly::Expected< HSHomeObject::HS_PG*, PGError > local_create_pg(shared< homestore::ReplDev > repl_dev, - PGInfo pg_info, trace_id_t tid = 0); + std::expected< HSHomeObject::HS_PG*, PGError > local_create_pg(shared< homestore::repl_dev > repl_dev, + PGInfo pg_info, trace_id_t tid = 0); static std::string serialize_pg_info(const PGInfo& info); static PGInfo deserialize_pg_info(const unsigned char* pg_info_str, size_t size); void add_pg_to_map(unique< HS_PG > hs_pg); @@ -815,7 +826,7 @@ class HSHomeObject : public HomeObjectImpl { // recover part void register_homestore_metablk_callback(); - void on_pg_meta_blk_found(sisl::byte_view const& buf, void* meta_cookie); + void on_pg_meta_blk_found(sisl::byte_view const& buf, homestore::meta_blk* mblk); void on_shard_meta_blk_found(homestore::meta_blk* mblk, sisl::byte_view buf); void on_shard_meta_blk_recover_completed(bool success); void write_migrated_shard_metablks(); @@ -864,7 +875,7 @@ class HSHomeObject : public HomeObjectImpl { * @param repl_dev The replication device. * @param hs_ctx The replication request context. */ - void on_create_pg_message_commit(int64_t lsn, sisl::blob const& header, shared< homestore::ReplDev > repl_dev, + void on_create_pg_message_commit(int64_t lsn, sisl::blob const& header, shared< homestore::repl_dev > repl_dev, cintrusive< homestore::repl_req_ctx >& hs_ctx); /** @@ -945,7 +956,7 @@ class HSHomeObject : public HomeObjectImpl { * @param repl_dev The replication device. * @param hs_ctx The replication request context. */ - void on_shard_message_commit(int64_t lsn, sisl::blob const& header, shared< homestore::ReplDev > repl_dev, + void on_shard_message_commit(int64_t lsn, sisl::blob const& header, shared< homestore::repl_dev > repl_dev, cintrusive< homestore::repl_req_ctx >& hs_ctx); bool on_shard_message_pre_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, @@ -1012,6 +1023,8 @@ class HSHomeObject : public HomeObjectImpl { cshared< GCManager > gc_manager() const { return gc_mgr_; } cshared< ScrubManager > scrub_manager() const { return scrub_mgr_; } + iomgr::IOReactor* bg_timer_reactor() const { return bg_timer_reactor_; } + /** * @brief Reconciles the leaders for all PGs or a specific PG identified by pg_id. * @@ -1048,11 +1061,11 @@ class HSHomeObject : public HomeObjectImpl { void on_blob_message_rollback(int64_t lsn, sisl::blob const& header, sisl::blob const& key, cintrusive< homestore::repl_req_ctx >& hs_ctx); void on_blob_put_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, - const homestore::MultiBlkId& pbas, cintrusive< homestore::repl_req_ctx >& hs_ctx); + const homestore::multi_blk_id& pbas, cintrusive< homestore::repl_req_ctx >& hs_ctx); void on_blob_del_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, cintrusive< homestore::repl_req_ctx >& hs_ctx); bool local_add_blob_info(pg_id_t pg_id, BlobInfo const& blob_info, trace_id_t tid = 0); - homestore::ReplResult< homestore::blk_alloc_hints > + homestore::result< homestore::blk_alloc_hints > blob_put_get_blk_alloc_hints(sisl::blob const& header, cintrusive< homestore::repl_req_ctx >& ctx); void compute_blob_payload_hash(BlobHeader::HashAlgorithm algorithm, const uint8_t* blob_bytes, size_t blob_size, uint8_t* hash_bytes, size_t hash_len) const; @@ -1093,7 +1106,7 @@ class HSHomeObject : public HomeObjectImpl { std::pair< bool, homestore::btree_status_t > add_to_index_table(shared< BlobIndexTable > index_table, const BlobInfo& blob_info); - BlobManager::Result< homestore::MultiBlkId > + BlobManager::Result< homestore::multi_blk_id > get_blob_from_index_table(shared< BlobIndexTable > index_table, shard_id_t shard_id, blob_id_t blob_id) const; void print_btree_index(pg_id_t pg_id) const; @@ -1160,11 +1173,11 @@ class HSHomeObject : public HomeObjectImpl { // only leader will call incr and decr pending request num void incr_pending_request_num() const { - uint64_t now = pending_request_num.fetch_add(1); + [[maybe_unused]] uint64_t now = pending_request_num.fetch_add(1); LOGT("inc pending req, was {}", now); } void decr_pending_request_num() const { - uint64_t now = pending_request_num.fetch_sub(1); + [[maybe_unused]] uint64_t now = pending_request_num.fetch_sub(1); LOGT("desc pending req, was {}", now); DEBUG_ASSERT(now > 0, "pending == 0 "); } diff --git a/src/lib/homestore_backend/hs_http_manager.cpp b/src/lib/homestore_backend/hs_http_manager.cpp index 8cb52df00..cf5176ad4 100644 --- a/src/lib/homestore_backend/hs_http_manager.cpp +++ b/src/lib/homestore_backend/hs_http_manager.cpp @@ -1,3 +1,8 @@ +#include +#include +#include +#include +#include /********************************************************************************* * Modifications Copyright 2017-2019 eBay Inc. * @@ -41,63 +46,63 @@ std::string format_iso8601_time(const std::chrono::system_clock::time_point& tp) } // anonymous namespace HttpManager::HttpManager(HSHomeObject& ho) : ho_(ho) { - using namespace Pistache; - using namespace Pistache::Rest; - LOGINFO("Setting up HomeObject HTTP routes"); - std::vector< iomgr::http_route > routes = { - {Pistache::Http::Method::Get, "/api/v1/getObjLife", - Pistache::Rest::Routes::bind(&HttpManager::get_obj_life, this)}, - {Pistache::Http::Method::Get, "/api/v1/mallocStats", - Pistache::Rest::Routes::bind(&HttpManager::get_malloc_stats, this)}, - {Pistache::Http::Method::Post, "/api/v1/reconcile_leader", - Pistache::Rest::Routes::bind(&HttpManager::reconcile_leader, this)}, - {Pistache::Http::Method::Post, "/api/v1/yield_leadership_to_follower", - Pistache::Rest::Routes::bind(&HttpManager::yield_leadership_to_follower, this)}, - {Pistache::Http::Method::Get, "/api/v1/pg_quorum", - Pistache::Rest::Routes::bind(&HttpManager::get_pg_quorum, this)}, - {Pistache::Http::Method::Post, "/api/v1/flip_learner", - Pistache::Rest::Routes::bind(&HttpManager::flip_learner_flag, this)}, - {Pistache::Http::Method::Delete, "/api/v1/member", - Pistache::Rest::Routes::bind(&HttpManager::remove_member, this)}, - {Pistache::Http::Method::Delete, "/api/v1/pg_replacemember_task", - Pistache::Rest::Routes::bind(&HttpManager::clean_replace_member_task, this)}, - {Pistache::Http::Method::Get, "/api/v1/pg_replacemember_tasks", - Pistache::Rest::Routes::bind(&HttpManager::list_pg_replace_member_task, this)}, - {Pistache::Http::Method::Post, "/api/v1/reconcile_membership", - Pistache::Rest::Routes::bind(&HttpManager::reconcile_membership, this)}, - {Pistache::Http::Method::Delete, "/api/v1/pg", Pistache::Rest::Routes::bind(&HttpManager::exit_pg, this)}, - {Pistache::Http::Method::Post, "/api/v1/trigger_snapshot_creation", - Pistache::Rest::Routes::bind(&HttpManager::trigger_snapshot_creation, this)}, + std::vector< sisl::http_route > routes = { + {sisl::http_method::Get, "/api/v1/getObjLife", + [this](httplib::Request const& req, httplib::Response& res) { get_obj_life(req, res); }}, + {sisl::http_method::Get, "/api/v1/mallocStats", + [this](httplib::Request const& req, httplib::Response& res) { get_malloc_stats(req, res); }}, + {sisl::http_method::Post, "/api/v1/reconcile_leader", + [this](httplib::Request const& req, httplib::Response& res) { reconcile_leader(req, res); }}, + {sisl::http_method::Post, "/api/v1/yield_leadership_to_follower", + [this](httplib::Request const& req, httplib::Response& res) { yield_leadership_to_follower(req, res); }}, + {sisl::http_method::Get, "/api/v1/pg_quorum", + [this](httplib::Request const& req, httplib::Response& res) { get_pg_quorum(req, res); }}, + {sisl::http_method::Post, "/api/v1/flip_learner", + [this](httplib::Request const& req, httplib::Response& res) { flip_learner_flag(req, res); }}, + {sisl::http_method::Delete, "/api/v1/member", + [this](httplib::Request const& req, httplib::Response& res) { remove_member(req, res); }}, + {sisl::http_method::Delete, "/api/v1/pg_replacemember_task", + [this](httplib::Request const& req, httplib::Response& res) { clean_replace_member_task(req, res); }}, + {sisl::http_method::Get, "/api/v1/pg_replacemember_tasks", + [this](httplib::Request const& req, httplib::Response& res) { list_pg_replace_member_task(req, res); }}, + {sisl::http_method::Post, "/api/v1/reconcile_membership", + [this](httplib::Request const& req, httplib::Response& res) { reconcile_membership(req, res); }}, + {sisl::http_method::Delete, "/api/v1/pg", + [this](httplib::Request const& req, httplib::Response& res) { exit_pg(req, res); }}, + {sisl::http_method::Post, "/api/v1/trigger_snapshot_creation", + [this](httplib::Request const& req, httplib::Response& res) { trigger_snapshot_creation(req, res); }}, #ifdef _PRERELEASE - {Pistache::Http::Method::Post, "/api/v1/crashSystem", - Pistache::Rest::Routes::bind(&HttpManager::crash_system, this)}, + {sisl::http_method::Post, "/api/v1/crashSystem", + [this](httplib::Request const& req, httplib::Response& res) { crash_system(req, res); }}, #endif - {Pistache::Http::Method::Get, "/api/v1/pg", Pistache::Rest::Routes::bind(&HttpManager::get_pg, this)}, - {Pistache::Http::Method::Get, "/api/v1/chunks", - Pistache::Rest::Routes::bind(&HttpManager::get_pg_chunks, this)}, - {Pistache::Http::Method::Get, "/api/v1/shard", Pistache::Rest::Routes::bind(&HttpManager::get_shard, this)}, - {Pistache::Http::Method::Get, "/api/v1/chunk/dump", - Pistache::Rest::Routes::bind(&HttpManager::dump_chunk, this)}, - {Pistache::Http::Method::Get, "/api/v1/shard/dump", - Pistache::Rest::Routes::bind(&HttpManager::dump_shard, this)}, + {sisl::http_method::Get, "/api/v1/pg", + [this](httplib::Request const& req, httplib::Response& res) { get_pg(req, res); }}, + {sisl::http_method::Get, "/api/v1/chunks", + [this](httplib::Request const& req, httplib::Response& res) { get_pg_chunks(req, res); }}, + {sisl::http_method::Get, "/api/v1/shard", + [this](httplib::Request const& req, httplib::Response& res) { get_shard(req, res); }}, + {sisl::http_method::Get, "/api/v1/chunk/dump", + [this](httplib::Request const& req, httplib::Response& res) { dump_chunk(req, res); }}, + {sisl::http_method::Get, "/api/v1/shard/dump", + [this](httplib::Request const& req, httplib::Response& res) { dump_shard(req, res); }}, // we support triggering gc for: // 1 all the chunks in all the pg: no input param // 2 all the chunks in a specific pg: input param is pg_id // 3 a specific chunk: input param is pchunk_id - {Pistache::Http::Method::Post, "/api/v1/trigger_gc", - Pistache::Rest::Routes::bind(&HttpManager::trigger_gc, this)}, - {Pistache::Http::Method::Get, "/api/v1/gc_job_status", - Pistache::Rest::Routes::bind(&HttpManager::get_gc_job_status, this)}, - {Pistache::Http::Method::Post, "/api/v1/trigger_pg_scrub", - Pistache::Rest::Routes::bind(&HttpManager::trigger_pg_scrub, this)}, - {Pistache::Http::Method::Get, "/api/v1/scrub_job_status", - Pistache::Rest::Routes::bind(&HttpManager::get_scrub_job_status, this)}, - {Pistache::Http::Method::Post, "/api/v1/cancel_scrub_job", - Pistache::Rest::Routes::bind(&HttpManager::cancel_scrub_job, this)}}; + {sisl::http_method::Post, "/api/v1/trigger_gc", + [this](httplib::Request const& req, httplib::Response& res) { trigger_gc(req, res); }}, + {sisl::http_method::Get, "/api/v1/gc_job_status", + [this](httplib::Request const& req, httplib::Response& res) { get_gc_job_status(req, res); }}, + {sisl::http_method::Post, "/api/v1/trigger_pg_scrub", + [this](httplib::Request const& req, httplib::Response& res) { trigger_pg_scrub(req, res); }}, + {sisl::http_method::Get, "/api/v1/scrub_job_status", + [this](httplib::Request const& req, httplib::Response& res) { get_scrub_job_status(req, res); }}, + {sisl::http_method::Post, "/api/v1/cancel_scrub_job", + [this](httplib::Request const& req, httplib::Response& res) { cancel_scrub_job(req, res); }}}; auto http_server = ioenvironment.get_http_server(); if (!http_server) { @@ -109,40 +114,48 @@ HttpManager::HttpManager(HSHomeObject& ho) : ho_(ho) { } catch (std::runtime_error const& e) { LOGERROR("setup routes failed, {}", e.what()); } } -void HttpManager::get_obj_life(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::get_obj_life(httplib::Request const& request, httplib::Response& response) { nlohmann::json j; sisl::ObjCounterRegistry::foreach ([&j](const std::string& name, int64_t created, int64_t alive) { std::stringstream ss; ss << "created=" << created << " alive=" << alive; j[name] = ss.str(); }); - response.send(Pistache::Http::Code::Ok, j.dump()); + response.status = 200; + response.set_content(j.dump(), "application/json"); } -void HttpManager::get_malloc_stats(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - response.send(Pistache::Http::Code::Ok, sisl::get_malloc_stats_detailed().dump(2)); +void HttpManager::get_malloc_stats(httplib::Request const& request, httplib::Response& response) { + response.status = 200; + response.set_content(sisl::get_malloc_stats_detailed().dump(2), "application/json"); } -void HttpManager::reconcile_leader(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - const auto pg_id_param = request.query().get("pg_id"); +void HttpManager::reconcile_leader(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_id_param = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; int32_t pg_id = std::stoi(pg_id_param.value_or("-1")); LOGINFO("Received reconcile leader request for pg_id {}", pg_id); ho_.reconcile_pg_leader(pg_id); - response.send(Pistache::Http::Code::Ok, "Reconcile leader request submitted"); + response.status = 200; + response.set_content("Reconcile leader request submitted", "text/plain"); } -void HttpManager::yield_leadership_to_follower(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { - const auto pg_id_param = request.query().get("pg_id"); +void HttpManager::yield_leadership_to_follower(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_id_param = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; int32_t pg_id = std::stoi(pg_id_param.value_or("-1")); - const auto candidate_param = request.query().get("candidate"); + std::optional< std::string > candidate_param = request.has_param("candidate") + ? std::optional< std::string >{request.get_param_value("candidate")} + : std::nullopt; if (candidate_param && candidate_param->empty()) { - response.send(Pistache::Http::Code::Bad_Request, "candidate must not be empty"); + response.status = 400; + response.set_content("candidate must not be empty", "text/plain"); return; } if (candidate_param && pg_id < 0) { - response.send(Pistache::Http::Code::Bad_Request, "candidate requires pg_id to be specified"); + response.status = 400; + response.set_content("candidate requires pg_id to be specified", "text/plain"); return; } @@ -153,46 +166,56 @@ void HttpManager::yield_leadership_to_follower(const Pistache::Rest::Request& re try { candidate = boost::uuids::string_generator()(candidate_str); } catch (const std::exception&) { - response.send(Pistache::Http::Code::Bad_Request, "Invalid candidate UUID format"); + response.status = 400; + response.set_content("Invalid candidate UUID format", "text/plain"); return; } auto hs_pg = ho_.get_hs_pg(static_cast< uint16_t >(pg_id)); if (!hs_pg) { - response.send(Pistache::Http::Code::Not_Found, "pg not found"); + response.status = 404; + response.set_content("pg not found", "text/plain"); return; } auto const& members = hs_pg->pg_info_.members; if (!std::any_of(members.begin(), members.end(), [&](const auto& m) { return m.id == *candidate; })) { - response.send(Pistache::Http::Code::Bad_Request, - fmt::format("candidate {} is not a member of pg {}", candidate_str, pg_id)); + response.status = 400; + response.set_content(fmt::format("candidate {} is not a member of pg {}", candidate_str, pg_id), + "text/plain"); return; } } LOGINFO("Received yield leadership request for pg_id {} to follower, candidate={}", pg_id, candidate_str); ho_.yield_pg_leadership_to_follower(pg_id, candidate); - response.send(Pistache::Http::Code::Ok, "Yield leadership request submitted"); + response.status = 200; + response.set_content("Yield leadership request submitted", "text/plain"); } -void HttpManager::trigger_snapshot_creation(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { +void HttpManager::trigger_snapshot_creation(httplib::Request const& request, httplib::Response& response) { // Extract and validate pg_id parameter (required) - const auto pg_id_param = request.query().get("pg_id"); + std::optional< std::string > pg_id_param = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; if (!pg_id_param) { - response.send(Pistache::Http::Code::Bad_Request, "pg_id is required"); + response.status = 400; + response.set_content("pg_id is required", "text/plain"); return; } const int32_t pg_id = std::stoi(pg_id_param.value()); // Extract compact_lsn parameter (optional, default: -1 means use current HS status) - const auto compact_lsn_param = request.query().get("compact_lsn"); + std::optional< std::string > compact_lsn_param = request.has_param("compact_lsn") + ? std::optional< std::string >{request.get_param_value("compact_lsn")} + : std::nullopt; const int64_t compact_lsn = std::stoll(compact_lsn_param.value_or("-1")); // Extract wait_for_commit parameter (optional, default: true) - const auto wait_for_commit_param = request.query().get("wait_for_commit"); + std::optional< std::string > wait_for_commit_param = request.has_param("wait_for_commit") + ? std::optional< std::string >{request.get_param_value("wait_for_commit")} + : std::nullopt; std::string wait_for_commit_mode = wait_for_commit_param.value_or("true"); if (wait_for_commit_mode != "true" && wait_for_commit_mode != "false") { - response.send(Pistache::Http::Code::Bad_Request, "wait_for_commit must be 'true' or 'false'"); + response.status = 400; + response.set_content("wait_for_commit must be 'true' or 'false'", "text/plain"); return; } bool wait_for_commit = (wait_for_commit_mode == "true"); @@ -201,19 +224,23 @@ void HttpManager::trigger_snapshot_creation(const Pistache::Rest::Request& reque wait_for_commit); ho_.trigger_snapshot_creation(pg_id, compact_lsn, wait_for_commit); - response.send(Pistache::Http::Code::Ok, "Snapshot creation request submitted"); + response.status = 200; + response.set_content("Snapshot creation request submitted", "text/plain"); } -void HttpManager::get_pg(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto pg_str = request.query().get("pg_id"); +void HttpManager::get_pg(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_str = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; if (!pg_str) { - response.send(Pistache::Http::Code::Bad_Request, "pg_id is required"); + response.status = 400; + response.set_content("pg_id is required", "text/plain"); return; } uint16_t pg_id = std::stoul(pg_str.value()); auto hs_pg = ho_.get_hs_pg(pg_id); if (!hs_pg) { - response.send(Pistache::Http::Code::Not_Found, "pg not found"); + response.status = 404; + response.set_content("pg not found", "text/plain"); return; } auto peers = hs_pg->repl_dev_->get_replication_status(); @@ -233,30 +260,38 @@ void HttpManager::get_pg(const Pistache::Rest::Request& request, Pistache::Http: member_json["name"] = member.name; json["pg"]["members"].push_back(member_json); } - response.send(Pistache::Http::Code::Ok, json.dump()); + response.status = 200; + response.set_content(json.dump(), "application/json"); } -void HttpManager::get_pg_chunks(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto pg_str = request.query().get("pg_id"); +void HttpManager::get_pg_chunks(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_str = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; if (!pg_str) { - response.send(Pistache::Http::Code::Bad_Request, "pg_id is required"); + response.status = 400; + response.set_content("pg_id is required", "text/plain"); return; } uint16_t pg_id = std::stoul(pg_str.value()); auto hs_pg = ho_.get_hs_pg(pg_id); if (!hs_pg) { - response.send(Pistache::Http::Code::Not_Found, "pg not found"); + response.status = 404; + response.set_content("pg not found", "text/plain"); return; } auto json = ho_.chunk_selector()->dump_chunks_info(pg_id); json["pg"]["blk_size"] = hs_pg->repl_dev_->get_blk_size(); - response.send(Pistache::Http::Code::Ok, json.dump()); + response.status = 200; + response.set_content(json.dump(), "application/json"); } -void HttpManager::get_shard(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto shard_str = request.query().get("shard_id"); +void HttpManager::get_shard(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > shard_str = request.has_param("shard_id") + ? std::optional< std::string >{request.get_param_value("shard_id")} + : std::nullopt; if (!shard_str) { - response.send(Pistache::Http::Code::Bad_Request, "shard_id is required"); + response.status = 400; + response.set_content("shard_id is required", "text/plain"); return; } uint64_t shard_id = std::stoull(shard_str.value(), nullptr, 0); @@ -264,7 +299,8 @@ void HttpManager::get_shard(const Pistache::Rest::Request& request, Pistache::Ht j["shard_id"] = shard_id; auto chk = ho_.get_shard_v_chunk_id(shard_id); if (!chk) { - response.send(Pistache::Http::Code::Not_Found, "shard not found"); + response.status = 404; + response.set_content("shard not found", "text/plain"); return; } auto pchk = ho_.get_shard_p_chunk_id(shard_id); @@ -276,15 +312,18 @@ void HttpManager::get_shard(const Pistache::Rest::Request& request, Pistache::Ht } auto hs_pg = ho_.get_hs_pg(pg_id); if (!hs_pg) { - response.send(Pistache::Http::Code::Internal_Server_Error, "pg not found"); + response.status = 500; + response.set_content("pg not found", "text/plain"); return; } - auto r = ho_.shard_manager()->get_shard(shard_id).get(); - if (!r) { - response.send(Pistache::Http::Code::Internal_Server_Error, "failed to get shard"); + // Sync in-memory lookup — avoid sync_get(shard_manager()->get_shard) on HTTP thread. + auto const* shard = ho_._get_hs_shard(shard_id); + if (!shard) { + response.status = 500; + response.set_content("failed to get shard", "text/plain"); return; } - const auto& shard_info = r.value(); + const auto& shard_info = shard->info; j["created_time"] = shard_info.created_time; j["last_modified_time"] = shard_info.last_modified_time; j["state"] = shard_info.state; @@ -293,25 +332,32 @@ void HttpManager::get_shard(const Pistache::Rest::Request& request, Pistache::Ht j["meta"] = std::string(reinterpret_cast< const char* >(shard_info.meta)); auto blobs = ho_.get_shard_blobs(shard_id); if (!blobs) { - response.send(Pistache::Http::Code::Internal_Server_Error, "failed to get shard blobs"); + response.status = 500; + response.set_content("failed to get shard blobs", "text/plain"); return; } j["total_blob_count"] = blobs.value().size(); - response.send(Pistache::Http::Code::Ok, j.dump()); + response.status = 200; + response.set_content(j.dump(), "application/json"); } -void HttpManager::dump_chunk(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto pg_str = request.query().get("pg_id"); - auto chunk_str = request.query().get("v_chunk_id"); +void HttpManager::dump_chunk(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_str = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; + std::optional< std::string > chunk_str = request.has_param("v_chunk_id") + ? std::optional< std::string >{request.get_param_value("v_chunk_id")} + : std::nullopt; if (!pg_str || !chunk_str) { - response.send(Pistache::Http::Code::Bad_Request, "pg_id and v_chunk_id are required"); + response.status = 400; + response.set_content("pg_id and v_chunk_id are required", "text/plain"); return; } uint16_t pg_id = std::stoul(pg_str.value()); auto hs_pg = ho_.get_hs_pg(pg_id); if (!hs_pg) { - response.send(Pistache::Http::Code::Not_Found, "pg not found"); + response.status = 404; + response.set_content("pg not found", "text/plain"); return; } uint16_t v_chunk_id = std::stoul(chunk_str.value()); @@ -335,13 +381,17 @@ void HttpManager::dump_chunk(const Pistache::Rest::Request& request, Pistache::H j["shards"].push_back(shard_json); } j["total_shard_count"] = shards.size(); - response.send(Pistache::Http::Code::Ok, j.dump()); + response.status = 200; + response.set_content(j.dump(), "application/json"); } -void HttpManager::dump_shard(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto shard_str = request.query().get("shard_id"); +void HttpManager::dump_shard(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > shard_str = request.has_param("shard_id") + ? std::optional< std::string >{request.get_param_value("shard_id")} + : std::nullopt; if (!shard_str) { - response.send(Pistache::Http::Code::Bad_Request, "shard_id is required"); + response.status = 400; + response.set_content("shard_id is required", "text/plain"); return; } uint64_t shard_id = std::stoull(shard_str.value(), nullptr, 0); @@ -349,7 +399,8 @@ void HttpManager::dump_shard(const Pistache::Rest::Request& request, Pistache::H j["shard_id"] = shard_id; auto chk = ho_.get_shard_v_chunk_id(shard_id); if (!chk) { - response.send(Pistache::Http::Code::Not_Found, "shard not found"); + response.status = 404; + response.set_content("shard not found", "text/plain"); return; } j["v_chunk_id"] = chk.value(); @@ -359,12 +410,14 @@ void HttpManager::dump_shard(const Pistache::Rest::Request& request, Pistache::H j["v_chunk_state"] = enum_name(vchunk->m_state); } - auto s = ho_.shard_manager()->get_shard(shard_id).get(); - if (!s) { - response.send(Pistache::Http::Code::Internal_Server_Error, "failed to get shard"); + // Sync in-memory lookup — avoid sync_get(shard_manager()->get_shard) on HTTP thread. + auto const* shard = ho_._get_hs_shard(shard_id); + if (!shard) { + response.status = 500; + response.set_content("failed to get shard", "text/plain"); return; } - const auto& shard_info = s.value(); + const auto& shard_info = shard->info; j["created_time"] = shard_info.created_time; j["last_modified_time"] = shard_info.last_modified_time; j["state"] = shard_info.state; @@ -374,7 +427,8 @@ void HttpManager::dump_shard(const Pistache::Rest::Request& request, Pistache::H auto r = ho_.get_shard_blobs(shard_id); if (!r) { - response.send(Pistache::Http::Code::Internal_Server_Error, "failed to get shard blobs"); + response.status = 500; + response.set_content("failed to get shard blobs", "text/plain"); return; } for (auto const& blob : r.value()) { @@ -385,12 +439,13 @@ void HttpManager::dump_shard(const Pistache::Rest::Request& request, Pistache::H blob_json["chunk_num"] = blob.pbas.chunk_num(); j["blobs"].push_back(blob_json); } - response.send(Pistache::Http::Code::Ok, j.dump()); + response.status = 200; + response.set_content(j.dump(), "application/json"); } -void HttpManager::flip_learner_flag(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::flip_learner_flag(httplib::Request const& request, httplib::Response& response) { try { - auto body = request.body(); + auto body = request.body; auto j = nlohmann::json::parse(body); std::string pg_id_str = j.at("pg_id").get< std::string >(); @@ -403,22 +458,29 @@ void HttpManager::flip_learner_flag(const Pistache::Rest::Request& request, Pist auto tid = generateRandomTraceId(); LOGINFO("Flipping learner flag, pg_id={}, member_id={}, learner={}, commit_quorum={}, tid={}", pg_id, boost::uuids::to_string(member_id), learner, commit_quorum, tid); - auto result = ho_.flip_learner_flag(pg_id, member_id, learner == "true", commit_quorum, tid).get(); - if (!result) { - LOGI("PG flip learner flag failed, err={}", result.error()); - response.send(Pistache::Http::Code::Internal_Server_Error, - fmt::format("Failed to flip learner flag, err={}", result.error())); - return; - } - response.send(Pistache::Http::Code::Ok); + // ADR: no sync_get on HTTP thread — submit async and return 202 Accepted. + bool is_learner = (learner == "true"); + sisl::async::detach_then(ho_.flip_learner_flag(pg_id, member_id, is_learner, commit_quorum, tid), + [pg_id, member_id, tid](auto result) { + if (!result) { + LOGERROR("PG flip learner flag failed, pg_id={}, member_id={}, err={}, tid={}", + pg_id, boost::uuids::to_string(member_id), result.error(), tid); + } + }); + nlohmann::json accepted; + accepted["status"] = "accepted"; + accepted["message"] = "flip_learner request submitted"; + response.status = 202; + response.set_content(accepted.dump(), "application/json"); } catch (const std::exception& e) { - response.send(Pistache::Http::Code::Bad_Request, std::string("Invalid JSON: ") + e.what()); + response.status = 400; + response.set_content(std::string("Invalid JSON: ") + e.what(), "text/plain"); } } -void HttpManager::remove_member(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::remove_member(httplib::Request const& request, httplib::Response& response) { try { - auto body = request.body(); + auto body = request.body; auto j = nlohmann::json::parse(body); std::string pg_id_str = j.at("pg_id").get< std::string >(); @@ -430,50 +492,61 @@ void HttpManager::remove_member(const Pistache::Rest::Request& request, Pistache auto tid = generateRandomTraceId(); LOGINFO("Remove member, pg_id={}, member_id={}, commit_quorum={}, tid={}", pg_id, boost::uuids::to_string(member_id), commit_quorum, tid); - auto result = ho_.remove_member(pg_id, member_id, commit_quorum, tid).get(); - if (!result) { - // Some times remove member may fail with RETRY_REQUEST if the target member is not responding, - // in this case return 503 so that the caller can retry later. - auto code = result.error() == PGError::RETRY_REQUEST ? Pistache::Http::Code::Service_Unavailable - : Pistache::Http::Code::Internal_Server_Error; - response.send(code, fmt::format("Failed to remove member, err={}", result.error())); - return; - } - response.send(Pistache::Http::Code::Ok); + // ADR: no sync_get on HTTP thread — submit async and return 202 Accepted. + // Errors (including RETRY_REQUEST) are logged; clients should poll membership / retry via a new request. + sisl::async::detach_then(ho_.remove_member(pg_id, member_id, commit_quorum, tid), + [pg_id, member_id, tid](auto result) { + if (!result) { + LOGERROR("Remove member failed, pg_id={}, member_id={}, err={}, tid={}", pg_id, + boost::uuids::to_string(member_id), result.error(), tid); + } + }); + nlohmann::json accepted; + accepted["status"] = "accepted"; + accepted["message"] = "remove_member request submitted"; + response.status = 202; + response.set_content(accepted.dump(), "application/json"); } catch (const std::exception& e) { - response.send(Pistache::Http::Code::Bad_Request, std::string("Invalid JSON: ") + e.what()); + response.status = 400; + response.set_content(std::string("Invalid JSON: ") + e.what(), "text/plain"); } } -void HttpManager::clean_replace_member_task(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { +void HttpManager::clean_replace_member_task(httplib::Request const& request, httplib::Response& response) { try { - auto body = request.body(); + auto body = request.body; auto j = nlohmann::json::parse(body); std::string pg_id_str = j.at("pg_id").get< std::string >(); pg_id_t pg_id = std::stoull(pg_id_str); - std::string task_id = j.at("task_id").get< std::string >(); + // Keep task_id alive for the async op (API takes std::string&). + auto task_id = std::make_shared< std::string >(j.at("task_id").get< std::string >()); std::string commit_quorum_str = j.at("commit_quorum").get< std::string >(); uint32_t commit_quorum = std::stoul(commit_quorum_str); auto tid = generateRandomTraceId(); - LOGINFO("Clean replace member task, pg_id={}, task_id={}, commit_quorum={}, tid={}", pg_id, task_id, + LOGINFO("Clean replace member task, pg_id={}, task_id={}, commit_quorum={}, tid={}", pg_id, *task_id, commit_quorum, tid); - auto result = ho_.clean_replace_member_task(pg_id, task_id, commit_quorum, tid).get(); - if (!result) { - response.send(Pistache::Http::Code::Internal_Server_Error, - fmt::format("Failed to clean replace member task, err={}", result.error())); - return; - } - response.send(Pistache::Http::Code::Ok); + // ADR: no sync_get on HTTP thread — submit async and return 202 Accepted. + sisl::async::detach_then( + ho_.clean_replace_member_task(pg_id, *task_id, commit_quorum, tid), [pg_id, task_id, tid](auto result) { + if (!result) { + LOGERROR("Clean replace member task failed, pg_id={}, task_id={}, err={}, tid={}", pg_id, *task_id, + result.error(), tid); + } + }); + nlohmann::json accepted; + accepted["status"] = "accepted"; + accepted["message"] = "clean_replace_member_task request submitted"; + response.status = 202; + response.set_content(accepted.dump(), "application/json"); } catch (const std::exception& e) { - response.send(Pistache::Http::Code::Bad_Request, std::string("Invalid JSON: ") + e.what()); + response.status = 400; + response.set_content(std::string("Invalid JSON: ") + e.what(), "text/plain"); } } -void HttpManager::reconcile_membership(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { +void HttpManager::reconcile_membership(httplib::Request const& request, httplib::Response& response) { try { - auto body = request.body(); + auto body = request.body; auto j = nlohmann::json::parse(body); std::string pg_id_str = j.at("pg_id").get< std::string >(); @@ -483,8 +556,8 @@ void HttpManager::reconcile_membership(const Pistache::Rest::Request& request, bool success = ho_.reconcile_membership(pg_id); if (!success) { - response.send(Pistache::Http::Code::Internal_Server_Error, - fmt::format("Failed to reconcile membership for pg_id={}", pg_id)); + response.status = 500; + response.set_content(fmt::format("Failed to reconcile membership for pg_id={}", pg_id), "text/plain"); return; } @@ -493,19 +566,20 @@ void HttpManager::reconcile_membership(const Pistache::Rest::Request& request, result["pg_id"] = pg_id; result["message"] = "Membership reconciled successfully"; - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); } catch (const std::exception& e) { - response.send(Pistache::Http::Code::Bad_Request, std::string("Invalid JSON: ") + e.what()); + response.status = 400; + response.set_content(std::string("Invalid JSON: ") + e.what(), "text/plain"); } } -void HttpManager::list_pg_replace_member_task(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { +void HttpManager::list_pg_replace_member_task(httplib::Request const& request, httplib::Response& response) { auto tid = generateRandomTraceId(); auto ret = ho_.list_all_replace_member_tasks(tid); - if (ret.hasError()) { - response.send(Pistache::Http::Code::Internal_Server_Error, - fmt::format("Failed to list replace member task, err={}", ret.error())); + if (!ret) { + response.status = 500; + response.set_content(fmt::format("Failed to list replace member task, err={}", ret.error()), "text/plain"); return; } LOGINFO("list pg replace member tasks, count={}, tid={}", ret.value().size(), tid); @@ -517,15 +591,18 @@ void HttpManager::list_pg_replace_member_task(const Pistache::Rest::Request& req task_j["replica_in"] = to_string(task.replica_in); j.push_back(task_j); } - response.send(Pistache::Http::Code::Ok, j.dump(2)); + response.status = 200; + response.set_content(j.dump(2), "application/json"); } // This API is used to get the PG quorum status, typically used by CM to fix its view of the PG status after a pg move // failure. -void HttpManager::get_pg_quorum(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto pg_id_str{request.query().get("pg_id")}; +void HttpManager::get_pg_quorum(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > pg_id_str = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; if (pg_id_str == std::nullopt) { - response.send(Pistache::Http::Code::Bad_Request, "Missing pg_id query parameter"); + response.status = 400; + response.set_content("Missing pg_id query parameter", "text/plain"); return; } pg_id_t pg_id = std::stoull(pg_id_str.value()); @@ -545,18 +622,25 @@ void HttpManager::get_pg_quorum(const Pistache::Rest::Request& request, Pistache member_j["last_succ_resp_us"] = peer.last_succ_resp_us; j["members"].push_back(member_j); } - response.send(Pistache::Http::Code::Ok, j.dump(2)); + response.status = 200; + response.set_content(j.dump(2), "application/json"); } else { - response.send(Pistache::Http::Code::Internal_Server_Error, fmt::format("Failed to get pg quorum")); + response.status = 500; + response.set_content(fmt::format("Failed to get pg quorum"), "text/plain"); } } -void HttpManager::exit_pg(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto group_id_str{request.query().get("group_id")}; - auto peer_id_str{request.query().get("replica_id")}; +void HttpManager::exit_pg(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > group_id_str = request.has_param("group_id") + ? std::optional< std::string >{request.get_param_value("group_id")} + : std::nullopt; + std::optional< std::string > peer_id_str = request.has_param("replica_id") + ? std::optional< std::string >{request.get_param_value("replica_id")} + : std::nullopt; auto tid = generateRandomTraceId(); if (group_id_str == std::nullopt || peer_id_str == std::nullopt) { - response.send(Pistache::Http::Code::Bad_Request, "Missing group_id or replica_id query parameter"); + response.status = 400; + response.set_content("Missing group_id or replica_id query parameter", "text/plain"); return; } uuid_t group_id; @@ -565,37 +649,43 @@ void HttpManager::exit_pg(const Pistache::Rest::Request& request, Pistache::Http group_id = boost::uuids::string_generator()(group_id_str.value()); peer_id = boost::uuids::string_generator()(peer_id_str.value()); } catch (const std::runtime_error& e) { - response.send(Pistache::Http::Code::Bad_Request, "Invalid group_id or replica_id query parameter"); + response.status = 400; + response.set_content("Invalid group_id or replica_id query parameter", "text/plain"); return; } LOGINFO("Exit pg request received for group_id={}, peer_id={}, tid={}", group_id_str.value(), peer_id_str.value(), tid); auto ret = ho_.exit_pg(group_id, peer_id, tid); - if (ret.hasError()) { - response.send(Pistache::Http::Code::Internal_Server_Error, - fmt::format("Failed to list replace member task, err={}", ret.error())); + if (!ret) { + response.status = 500; + response.set_content(fmt::format("Failed to list replace member task, err={}", ret.error()), "text/plain"); return; } - response.send(Pistache::Http::Code::Ok, "Exit pg request submitted"); + response.status = 200; + response.set_content("Exit pg request submitted", "text/plain"); } -void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::trigger_pg_scrub(httplib::Request const& request, httplib::Response& response) { auto scrub_mgr = ho_.scrub_manager(); if (!scrub_mgr) { - response.send(Pistache::Http::Code::Internal_Server_Error, "Scrub manager not available"); + response.status = 500; + response.set_content("Scrub manager not available", "text/plain"); return; } // Get query parameters - const auto pg_id_param = request.query().get("pg_id"); - const auto is_deep_param = request.query().get("deep"); + std::optional< std::string > pg_id_param = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; + std::optional< std::string > is_deep_param = + request.has_param("deep") ? std::optional< std::string >{request.get_param_value("deep")} : std::nullopt; // Validate pg_id parameter (required) if (!pg_id_param || pg_id_param.value().empty()) { nlohmann::json error; error["error"] = "Missing required parameter: pg_id"; error["usage"] = "POST /api/v1/trigger_pg_scrub?pg_id=&deep="; - response.send(Pistache::Http::Code::Bad_Request, error.dump()); + response.status = 400; + response.set_content(error.dump(), "application/json"); return; } @@ -606,7 +696,8 @@ void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pista nlohmann::json error; error["error"] = "pg_id out of range"; error["pg_id"] = pg_id_param.value(); - response.send(Pistache::Http::Code::Bad_Request, error.dump()); + response.status = 400; + response.set_content(error.dump(), "application/json"); return; } pg_id = static_cast< uint16_t >(val); @@ -614,13 +705,15 @@ void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pista nlohmann::json error; error["error"] = "Invalid pg_id format: not a number"; error["pg_id"] = pg_id_param.value(); - response.send(Pistache::Http::Code::Bad_Request, error.dump()); + response.status = 400; + response.set_content(error.dump(), "application/json"); return; } catch (const std::out_of_range& e) { nlohmann::json error; error["error"] = "pg_id out of range"; error["pg_id"] = pg_id_param.value(); - response.send(Pistache::Http::Code::Bad_Request, error.dump()); + response.status = 400; + response.set_content(error.dump(), "application/json"); return; } @@ -639,7 +732,8 @@ void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pista nlohmann::json error; error["error"] = "PG not found"; error["pg_id"] = pg_id; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } @@ -660,12 +754,13 @@ void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pista result["message"] = "Scrub task submitted, query status using /api/v1/scrub_job_status?job_id=" + job_id; // Return immediately with HTTP 202 Accepted - response.send(Pistache::Http::Code::Accepted, result.dump()); + response.status = 202; + response.set_content(result.dump(), "application/json"); // Submit scrub task (MANUALLY trigger type) - runs asynchronously - scrub_mgr->submit_scrub_task(pg_id, is_deep, SCRUB_TRIGGER_TYPE::MANUALLY) - .via(&folly::InlineExecutor::instance()) - .thenValue([job_info, is_deep](std::shared_ptr< ScrubManager::ShallowScrubReport > report) { + sisl::async::detach_then( + scrub_mgr->submit_scrub_task(pg_id, is_deep, SCRUB_TRIGGER_TYPE::MANUALLY), + [job_info, is_deep](std::shared_ptr< ScrubManager::ShallowScrubReport > report) { if (!report) { job_info->try_complete(ScrubJobStatus::FAILED, "Scrub task failed or was cancelled"); return; @@ -763,27 +858,29 @@ void HttpManager::trigger_pg_scrub(const Pistache::Rest::Request& request, Pista // Complete the job with success status and report job_info->try_complete(ScrubJobStatus::COMPLETED, "", report_summary); - }) - .thenError([job_info](const folly::exception_wrapper& ew) { - job_info->try_complete(ScrubJobStatus::FAILED, ew.what().c_str()); }); } -void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::trigger_gc(httplib::Request const& request, httplib::Response& response) { auto gc_mgr = ho_.gc_manager(); if (!gc_mgr) { - response.send(Pistache::Http::Code::Internal_Server_Error, "GC manager not available"); + response.status = 500; + response.set_content("GC manager not available", "text/plain"); return; } auto chunk_selector = ho_.chunk_selector(); if (!chunk_selector) { - response.send(Pistache::Http::Code::Internal_Server_Error, "Chunk selector not available"); + response.status = 500; + response.set_content("Chunk selector not available", "text/plain"); return; } - const auto chunk_id_param = request.query().get("chunk_id"); - const auto pg_id_param = request.query().get("pg_id"); + std::optional< std::string > chunk_id_param = request.has_param("chunk_id") + ? std::optional< std::string >{request.get_param_value("chunk_id")} + : std::nullopt; + std::optional< std::string > pg_id_param = + request.has_param("pg_id") ? std::optional< std::string >{request.get_param_value("pg_id")} : std::nullopt; if (chunk_id_param && !chunk_id_param.value().empty()) { // trigger gc for a specific chunk, the chunk_id is pchunk_id, not vchunk_id @@ -795,7 +892,8 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H nlohmann::json error; error["chunk_id"] = chunk_id; error["error"] = "Chunk not found"; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } @@ -803,7 +901,8 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H nlohmann::json error; error["chunk_id"] = chunk_id; error["error"] = "Chunk belongs to no pg"; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } @@ -817,13 +916,15 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H if (chunk->m_state == ChunkState::GC) { result["message"] = "chunk is already under GC now, this task will not be executed!"; - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); return; } result["message"] = "GC triggered for chunk, pls query job status using gc_job_status API"; // return response before starting the GC so that we don't block the client. - response.send(Pistache::Http::Code::Accepted, result.dump()); + response.status = 202; + response.set_content(result.dump(), "application/json"); auto job_info = std::make_shared< GCJobInfo >(job_id, pg_id, chunk_id); { @@ -841,13 +942,10 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H repl_dev->clear_chunk_req(chunk_id); const auto priority = chunk->m_state == ChunkState::INUSE ? task_priority::emergent : task_priority::normal; - gc_mgr->submit_gc_task(priority, chunk_id) - .via(&folly::InlineExecutor::instance()) - .thenValue([this, job_info, repl_dev](bool res) { - job_info->status = res ? GCJobStatus::COMPLETED : GCJobStatus::FAILED; - // Resume accepting new requests for this pg - repl_dev->resume_accepting_reqs(); - }); + sisl::async::detach_then(gc_mgr->submit_gc_task(priority, chunk_id), [this, job_info, repl_dev](bool res) { + job_info->status = res ? GCJobStatus::COMPLETED : GCJobStatus::FAILED; + repl_dev->resume_accepting_reqs(); + }); } else if (pg_id_param && !pg_id_param.value().empty()) { // trigger gc for all chunks in a specific pg const auto pg_id = std::stoul(pg_id_param.value()); @@ -857,7 +955,8 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H nlohmann::json error; error["pg_id"] = pg_id; error["error"] = "PG not found"; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } @@ -867,7 +966,8 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H result["job_id"] = job_id; result["message"] = "GC triggered for a single pg, pls query job status using gc_job_status API"; // return response before starting the GC so that we don't block the client. - response.send(Pistache::Http::Code::Accepted, result.dump()); + response.status = 202; + response.set_content(result.dump(), "application/json"); auto job_info = std::make_shared< GCJobInfo >(job_id, pg_id); { @@ -878,7 +978,7 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H LOGINFO("GC job {} stopping GC scan timer", job_id); gc_mgr->stop_gc_scan_timer(); - trigger_gc_for_pg(pg_id, job_id).thenValue([job_info, gc_mgr, job_id](auto&&) { + sisl::async::detach_then(trigger_gc_for_pg(pg_id, job_id), [job_info, gc_mgr, job_id](auto&&) { job_info->status = job_info->failed_count ? GCJobStatus::FAILED : GCJobStatus::COMPLETED; LOGINFO("GC job {} completed: total={}, success={}, failed={}", job_info->job_id, job_info->total_chunks, job_info->success_count, job_info->failed_count); @@ -904,25 +1004,27 @@ void HttpManager::trigger_gc(const Pistache::Rest::Request& request, Pistache::H LOGINFO("GC job {} no PGs found, marking as completed", job_id); job_info->status = GCJobStatus::COMPLETED; result["message"] = "No PGs found, GC completed"; - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); return; } result["message"] = "GC triggered for all chunks, pls query job status using gc_job_status API"; // return response before starting the GC so that we don't block the client. - response.send(Pistache::Http::Code::Accepted, result.dump()); + response.status = 202; + response.set_content(result.dump(), "application/json"); LOGINFO("GC job {} will process {} PGs", job_id, pg_ids.size()); LOGINFO("GC job {} stopping GC scan timer", job_id); gc_mgr->stop_gc_scan_timer(); - std::vector< folly::Future< folly::Unit > > pg_futures; + std::vector< sisl::async::task< std::monostate > > pg_futures; for (const auto& pg_id : pg_ids) { pg_futures.push_back(trigger_gc_for_pg(pg_id, job_id)); } // Set job status after all PGs are processed - folly::collectAllUnsafe(pg_futures).thenValue([job_info, gc_mgr, job_id](auto&&) { + sisl::async::detach_then(sisl::async::when_all(std::move(pg_futures)), [job_info, gc_mgr, job_id](auto&&) { job_info->status = job_info->failed_count ? GCJobStatus::FAILED : GCJobStatus::COMPLETED; LOGINFO("GC job {} completed: total={}, success={}, failed={}", job_info->job_id, job_info->total_chunks, job_info->success_count, job_info->failed_count); @@ -974,14 +1076,16 @@ void HttpManager::get_job_status(const std::string& job_id, nlohmann::json& resu } } -void HttpManager::get_gc_job_status(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto job_id_param = request.query().get("job_id"); +void HttpManager::get_gc_job_status(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > job_id_param = + request.has_param("job_id") ? std::optional< std::string >{request.get_param_value("job_id")} : std::nullopt; if (job_id_param && !job_id_param.value().empty()) { const auto job_id = job_id_param.value(); LOGINFO("query job {} status!", job_id); nlohmann::json result; get_job_status(job_id, result); - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); return; } @@ -1001,74 +1105,49 @@ void HttpManager::get_gc_job_status(const Pistache::Rest::Request& request, Pist result["jobs"].push_back(job_json); } - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); } -folly::Future< folly::Unit > HttpManager::trigger_gc_for_pg(uint16_t pg_id, const std::string& job_id) { - auto hs_pg = const_cast< HSHomeObject::HS_PG* >(ho_.get_hs_pg(pg_id)); - RELEASE_ASSERT(hs_pg, "HS PG {} not found during GC job {}", pg_id, job_id); - - LOGINFO("GC job {} draining pending GC tasks for PG {}", job_id, pg_id); +sisl::async::task< std::monostate > HttpManager::trigger_gc_for_pg(uint16_t pg_id, const std::string& job_id) { auto gc_mgr = ho_.gc_manager(); - gc_mgr->drain_pg_pending_gc_task(pg_id); - auto pg_sb = hs_pg->pg_sb_.get(); - std::vector< homestore::chunk_num_t > pg_chunks(pg_sb->get_chunk_ids(), pg_sb->get_chunk_ids() + pg_sb->num_chunks); - - LOGINFO("GC job {} processing PG {} with {} chunks", job_id, pg_id, pg_chunks.size()); - hs_pg->repl_dev_->quiesce_reqs(); - std::vector< folly::SemiFuture< bool > > gc_task_futures; - std::shared_ptr< GCJobInfo > job_info; { std::shared_lock lock(gc_job_mutex_); job_info = gc_jobs_map_.get(job_id); } + if (!job_info) { co_return std::monostate{}; } - auto chunk_selector = ho_.chunk_selector(); + auto hs_pg = const_cast< HSHomeObject::HS_PG* >(ho_.get_hs_pg(pg_id)); + if (!hs_pg) { co_return std::monostate{}; } - for (const auto& chunk_id : pg_chunks) { + auto pg_sb = hs_pg->pg_sb_.get(); + std::vector< sisl::async::task< bool > > gc_task_futures; + for (uint32_t i = 0; i < pg_sb->num_chunks; ++i) { + auto chunk_id = pg_sb->get_chunk_ids()[i]; + auto chunk = ho_.chunk_selector()->get_extend_vchunk(chunk_id); + if (!chunk || chunk->m_state == ChunkState::GC) { continue; } job_info->total_chunks++; - // Determine priority based on chunk state (INUSE means has open shard) - auto chunk = chunk_selector->get_extend_vchunk(chunk_id); - RELEASE_ASSERT(chunk, "Chunk {} not found during GC job {}", chunk_id, job_id); - auto priority = chunk->m_state == ChunkState::INUSE ? task_priority::emergent : task_priority::normal; + const auto priority = chunk->m_state == ChunkState::INUSE ? task_priority::emergent : task_priority::normal; + gc_task_futures.push_back(gc_mgr->submit_gc_task(priority, chunk_id)); + } - // Clear in-memory requests only for emergent priority chunks (chunks with open shards) - if (priority == task_priority::emergent) { hs_pg->repl_dev_->clear_chunk_req(chunk_id); } - - // Submit GC task for this chunk - auto future = gc_mgr->submit_gc_task(priority, chunk_id); - gc_task_futures.push_back(std::move(future)); - LOGDEBUG("GC job {} for chunk {} in PG {} with priority={}", job_id, chunk_id, pg_id, - (priority == task_priority::emergent) ? "emergent" : "normal"); - } - - return folly::collectAllUnsafe(gc_task_futures) - .thenValue([job_info](auto&& results) { - for (auto const& ok : results) { - RELEASE_ASSERT(ok.hasValue(), "we never throw any exception when copying data"); - if (ok.value()) { - job_info->success_count++; - } else { - job_info->failed_count++; - } - } - }) - .thenValue([this, pg_id, job_info](auto&& rets) { - LOGINFO("All GC tasks for PG {} have been processed", pg_id); - const auto& job_id = job_info->job_id; - - auto hs_pg = const_cast< HSHomeObject::HS_PG* >(ho_.get_hs_pg(pg_id)); - RELEASE_ASSERT(hs_pg, "HS PG {} not found during GC job {}", pg_id, job_id); - // Resume accepting new requests for this pg - hs_pg->repl_dev_->resume_accepting_reqs(); - LOGINFO("GC job {} resumed accepting requests for PG {}", job_id, pg_id); - }); + if (gc_task_futures.empty()) { co_return std::monostate{}; } + + auto results = co_await sisl::async::when_all(std::move(gc_task_futures)); + for (auto const& res : results) { + if (res) { + job_info->success_count++; + } else { + job_info->failed_count++; + } + } + co_return std::monostate{}; } -void HttpManager::get_scrub_job_status(const Pistache::Rest::Request& request, - Pistache::Http::ResponseWriter response) { - auto job_id_param = request.query().get("job_id"); +void HttpManager::get_scrub_job_status(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > job_id_param = + request.has_param("job_id") ? std::optional< std::string >{request.get_param_value("job_id")} : std::nullopt; if (job_id_param && !job_id_param.value().empty()) { // Query specific job @@ -1085,12 +1164,14 @@ void HttpManager::get_scrub_job_status(const Pistache::Rest::Request& request, nlohmann::json error; error["error"] = "Job not found"; error["job_id"] = job_id; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } nlohmann::json result = build_scrub_job_json(job_info); - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); return; } @@ -1110,7 +1191,8 @@ void HttpManager::get_scrub_job_status(const Pistache::Rest::Request& request, result["jobs"].push_back(build_scrub_job_json(job_info)); } - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); } nlohmann::json HttpManager::build_scrub_job_json(const std::shared_ptr< ScrubJobInfo >& job_info) { @@ -1167,14 +1249,16 @@ nlohmann::json HttpManager::build_scrub_job_json(const std::shared_ptr< ScrubJob return result; } -void HttpManager::cancel_scrub_job(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { - auto job_id_param = request.query().get("job_id"); +void HttpManager::cancel_scrub_job(httplib::Request const& request, httplib::Response& response) { + std::optional< std::string > job_id_param = + request.has_param("job_id") ? std::optional< std::string >{request.get_param_value("job_id")} : std::nullopt; if (!job_id_param || job_id_param.value().empty()) { nlohmann::json error; error["error"] = "Missing required parameter: job_id"; error["usage"] = "POST /api/v1/cancel_scrub_job?job_id="; - response.send(Pistache::Http::Code::Bad_Request, error.dump()); + response.status = 400; + response.set_content(error.dump(), "application/json"); return; } @@ -1191,7 +1275,8 @@ void HttpManager::cancel_scrub_job(const Pistache::Rest::Request& request, Pista nlohmann::json error; error["error"] = "Job not found"; error["job_id"] = job_id; - response.send(Pistache::Http::Code::Not_Found, error.dump()); + response.status = 404; + response.set_content(error.dump(), "application/json"); return; } @@ -1224,7 +1309,8 @@ void HttpManager::cancel_scrub_job(const Pistache::Rest::Request& request, Pista result["job_id"] = job_id; result["message"] = "Job is not running, cannot cancel"; result["current_status"] = current_status_str; - response.send(Pistache::Http::Code::Bad_Request, result.dump()); + response.status = 400; + response.set_content(result.dump(), "application/json"); return; } @@ -1233,7 +1319,8 @@ void HttpManager::cancel_scrub_job(const Pistache::Rest::Request& request, Pista if (!scrub_mgr) { nlohmann::json error; error["error"] = "Scrub manager not available"; - response.send(Pistache::Http::Code::Internal_Server_Error, error.dump()); + response.status = 500; + response.set_content(error.dump(), "application/json"); return; } @@ -1246,13 +1333,15 @@ void HttpManager::cancel_scrub_job(const Pistache::Rest::Request& request, Pista nlohmann::json result; result["job_id"] = job_id; result["message"] = "Scrub job cancelled successfully"; - response.send(Pistache::Http::Code::Ok, result.dump()); + response.status = 200; + response.set_content(result.dump(), "application/json"); } #ifdef _PRERELEASE -void HttpManager::crash_system(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response) { +void HttpManager::crash_system(httplib::Request const& request, httplib::Response& response) { std::string crash_type; - const auto _crash_type{request.query().get("type")}; + const std::optional< std::string > _crash_type = + request.has_param("type") ? std::optional< std::string >{request.get_param_value("type")} : std::nullopt; if (_crash_type) { crash_type = _crash_type.value(); } std::string resp = ""; @@ -1264,7 +1353,8 @@ void HttpManager::crash_system(const Pistache::Rest::Request& request, Pistache: } else { resp = "crash type " + crash_type + " not supported yet"; } - response.send(Pistache::Http::Code::Ok, resp); + response.status = 200; + response.set_content(resp, "text/plain"); } #endif diff --git a/src/lib/homestore_backend/hs_http_manager.hpp b/src/lib/homestore_backend/hs_http_manager.hpp index 02a24dcc4..c4057c9e0 100644 --- a/src/lib/homestore_backend/hs_http_manager.hpp +++ b/src/lib/homestore_backend/hs_http_manager.hpp @@ -13,13 +13,23 @@ * specific language governing permissions and limitations under the License. * *********************************************************************************/ +#pragma once + #include -#include +#include +#include -#include -#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include namespace homeobject { class HSHomeObject; @@ -29,33 +39,33 @@ class HttpManager { HttpManager(HSHomeObject& ho); private: - void get_obj_life(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_malloc_stats(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void reconcile_leader(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void yield_leadership_to_follower(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void trigger_snapshot_creation(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_pg(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_pg_chunks(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void dump_chunk(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void dump_shard(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_shard(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void flip_learner_flag(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void remove_member(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void clean_replace_member_task(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void list_pg_replace_member_task(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void reconcile_membership(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_pg_quorum(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void exit_pg(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void trigger_gc(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_gc_job_status(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - folly::Future< folly::Unit > trigger_gc_for_pg(uint16_t pg_id, const std::string& job_id); + void get_obj_life(httplib::Request const& request, httplib::Response& response); + void get_malloc_stats(httplib::Request const& request, httplib::Response& response); + void reconcile_leader(httplib::Request const& request, httplib::Response& response); + void yield_leadership_to_follower(httplib::Request const& request, httplib::Response& response); + void trigger_snapshot_creation(httplib::Request const& request, httplib::Response& response); + void get_pg(httplib::Request const& request, httplib::Response& response); + void get_pg_chunks(httplib::Request const& request, httplib::Response& response); + void dump_chunk(httplib::Request const& request, httplib::Response& response); + void dump_shard(httplib::Request const& request, httplib::Response& response); + void get_shard(httplib::Request const& request, httplib::Response& response); + void flip_learner_flag(httplib::Request const& request, httplib::Response& response); + void remove_member(httplib::Request const& request, httplib::Response& response); + void clean_replace_member_task(httplib::Request const& request, httplib::Response& response); + void list_pg_replace_member_task(httplib::Request const& request, httplib::Response& response); + void reconcile_membership(httplib::Request const& request, httplib::Response& response); + void get_pg_quorum(httplib::Request const& request, httplib::Response& response); + void exit_pg(httplib::Request const& request, httplib::Response& response); + void trigger_gc(httplib::Request const& request, httplib::Response& response); + void get_gc_job_status(httplib::Request const& request, httplib::Response& response); + sisl::async::task< std::monostate > trigger_gc_for_pg(uint16_t pg_id, const std::string& job_id); void get_job_status(const std::string& job_id, nlohmann::json& result); - void trigger_pg_scrub(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void get_scrub_job_status(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); - void cancel_scrub_job(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); + void trigger_pg_scrub(httplib::Request const& request, httplib::Response& response); + void get_scrub_job_status(httplib::Request const& request, httplib::Response& response); + void cancel_scrub_job(httplib::Request const& request, httplib::Response& response); #ifdef _PRERELEASE - void crash_system(const Pistache::Rest::Request& request, Pistache::Http::ResponseWriter response); + void crash_system(httplib::Request const& request, httplib::Response& response); #endif private: @@ -134,9 +144,9 @@ class HttpManager { std::shared_mutex gc_job_mutex_; std::shared_mutex scrub_job_mutex_; - // we don`t have an external DB to store the job status, so we only keep the status of the lastest 100 jobs for - // query. or, we can evict the job after it is completed after a timeout period. - folly::EvictingCacheMap< std::string, std::shared_ptr< GCJobInfo > > gc_jobs_map_{100}; - folly::EvictingCacheMap< std::string, std::shared_ptr< ScrubJobInfo > > scrub_jobs_map_{100}; + // Keep status of the latest 100 jobs for query (formerly folly EvictingCacheMap). + // get() does NOT refresh LRU — only set() promotes; matches write-ordered eviction. + sisl::LruMap< std::string, std::shared_ptr< GCJobInfo > > gc_jobs_map_{100}; + sisl::LruMap< std::string, std::shared_ptr< ScrubJobInfo > > scrub_jobs_map_{100}; }; -} // namespace homeobject \ No newline at end of file +} // namespace homeobject diff --git a/src/lib/homestore_backend/hs_pg_manager.cpp b/src/lib/homestore_backend/hs_pg_manager.cpp index 7ad853e2c..c9af1e34e 100644 --- a/src/lib/homestore_backend/hs_pg_manager.cpp +++ b/src/lib/homestore_backend/hs_pg_manager.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include "hs_backend_config.hpp" #include @@ -61,6 +64,13 @@ PGError toPgError(ReplServiceError const& e) { } } +PGError toPgError(std::error_condition const& e) { + if (e.category() == homestore::repl_error_category_inst()) { + return toPgError(static_cast< ReplServiceError >(e.value())); + } + return PGError::UNKNOWN; +} + PGReplaceMemberTaskStatus toPGReplaceMemberTaskStatus(ReplaceMemberStatus const& status) { switch (status) { case ReplaceMemberStatus::COMPLETED: @@ -78,7 +88,7 @@ PGReplaceMemberTaskStatus toPGReplaceMemberTaskStatus(ReplaceMemberStatus const& } } -[[maybe_unused]] static homestore::ReplDev& pg_repl_dev(PG const& pg) { +[[maybe_unused]] static homestore::repl_dev& pg_repl_dev(PG const& pg) { return *(static_cast< HSHomeObject::HS_PG const& >(pg).repl_dev_); } @@ -86,7 +96,7 @@ PGManager::NullAsyncResult HSHomeObject::_create_pg(PGInfo&& pg_info, std::set< trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down"); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + co_return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); @@ -97,72 +107,62 @@ PGManager::NullAsyncResult HSHomeObject::_create_pg(PGInfo&& pg_info, std::set< LOGW("PG already exists with different info! pg={}, pg_info={}, hs_pg_info={}", pg_id, pg_info.to_string(), hs_pg->pg_info_.to_string()); decr_pending_request_num(); - return folly::makeUnexpected(PGError::INVALID_ARG); + co_return std::unexpected(PGError::INVALID_ARG); } LOGW("PG already exists! pg={}", pg_id); decr_pending_request_num(); - return folly::Unit(); + co_return std::monostate{}; } const auto chunk_size = chunk_selector()->get_chunk_size(); if (pg_info.size < chunk_size) { LOGW("Not support to create PG which pg_size={} < chunk_size={}", pg_info.size, chunk_size); decr_pending_request_num(); - return folly::makeUnexpected(PGError::INVALID_ARG); + co_return std::unexpected(PGError::INVALID_ARG); } auto const num_chunk = chunk_selector()->select_chunks_for_pg(pg_id, pg_info.size); if (!num_chunk.has_value()) { LOGW("Failed to select chunks for pg={}", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(PGError::NO_SPACE_LEFT); + co_return std::unexpected(PGError::NO_SPACE_LEFT); } if (pg_info.expected_member_num == 0) { pg_info.expected_member_num = pg_info.members.size(); } pg_info.chunk_size = chunk_size; pg_info.replica_set_uuid = boost::uuids::random_generator()(); const auto repl_dev_group_id = pg_info.replica_set_uuid; - return hs_repl_service() - .create_repl_dev(pg_info.replica_set_uuid, peers) - .via(executor_) - .thenValue([this, pg_info = std::move(pg_info), tid](auto&& v) mutable -> PGManager::NullAsyncResult { + + auto v = co_await hs_repl_service().create_repl_dev(pg_info.replica_set_uuid, peers); #ifdef _PRERELEASE - if (iomgr_flip::instance()->test_flip("create_pg_create_repl_dev_error")) { - LOGW("Simulating create repl dev error in creating pg"); - v = folly::makeUnexpected(ReplServiceError::FAILED); - } + if (iomgr_flip::instance()->test_flip("create_pg_create_repl_dev_error")) { + LOGW("Simulating create repl dev error in creating pg"); + v = std::unexpected(make_error_condition(ReplServiceError::FAILED)); + } #endif - if (v.hasError()) { return folly::makeUnexpected(toPgError(v.error())); } - // we will write a PGHeader across the raft group and when it is committed - // all raft members will create PGinfo and index table for this PG. - - // FIXME:https://github.com/eBay/HomeObject/pull/136#discussion_r1470504271 - return do_create_pg(v.value(), std::move(pg_info), tid); - }) - .thenValue([this, pg_id, repl_dev_group_id, tid](auto&& r) -> PGManager::NullAsyncResult { - // reclaim resources if failed to create pg - if (r.hasError()) { - bool res = chunk_selector_->return_pg_chunks_to_dev_heap(pg_id); - RELEASE_ASSERT(res, "Failed to return pg={} chunks to dev_heap", pg_id); - // no matter if create repl dev successfully, remove it. - // if don't have repl dev, it will return ReplServiceError::SERVER_NOT_FOUND - return hs_repl_service() - .remove_repl_dev(repl_dev_group_id) - .deferValue([r, repl_dev_group_id, this](auto&& e) -> PGManager::NullAsyncResult { - if (e != ReplServiceError::OK) { - LOGW("Failed to remove repl device which group_id={}, error={}", repl_dev_group_id, e); - } - decr_pending_request_num(); - // still return the original error - return folly::makeUnexpected(r.error()); - }); - } - decr_pending_request_num(); - return folly::Unit(); - }); + if (!v) { + bool res = chunk_selector_->return_pg_chunks_to_dev_heap(pg_id); + RELEASE_ASSERT(res, "Failed to return pg={} chunks to dev_heap", pg_id); + auto e = co_await hs_repl_service().remove_repl_dev(repl_dev_group_id); + if (!e) { LOGW("Failed to remove repl device which group_id={}, error={}", repl_dev_group_id, e.error()); } + decr_pending_request_num(); + co_return std::unexpected(toPgError(v.error())); + } + + auto r = co_await do_create_pg(v.value(), std::move(pg_info), tid); + if (!r) { + bool res = chunk_selector_->return_pg_chunks_to_dev_heap(pg_id); + RELEASE_ASSERT(res, "Failed to return pg={} chunks to dev_heap", pg_id); + auto e = co_await hs_repl_service().remove_repl_dev(repl_dev_group_id); + if (!e) { LOGW("Failed to remove repl device which group_id={}, error={}", repl_dev_group_id, e.error()); } + decr_pending_request_num(); + co_return std::unexpected(r.error()); + } + decr_pending_request_num(); + co_return std::monostate{}; } -PGManager::NullAsyncResult HSHomeObject::do_create_pg(cshared< homestore::ReplDev > repl_dev, PGInfo&& pg_info, +PGManager::NullAsyncResult HSHomeObject::do_create_pg(cshared< homestore::repl_dev > repl_dev, PGInfo&& pg_info, trace_id_t tid) { auto serailized_pg_info = serialize_pg_info(pg_info); auto info_size = serailized_pg_info.size(); @@ -177,20 +177,19 @@ PGManager::NullAsyncResult HSHomeObject::do_create_pg(cshared< homestore::ReplDe #ifdef _PRERELEASE if (iomgr_flip::instance()->test_flip("create_pg_raft_message_error")) { LOGW("Simulating raft message error in creating pg"); - return folly::makeUnexpected(PGError::UNKNOWN); + co_return std::unexpected(PGError::UNKNOWN); } #endif // replicate this create pg message to all raft members of this group - repl_dev->async_alloc_write(req->header_buf(), sisl::blob{}, sisl::sg_list{}, req, false /* part_of_batch */, tid); - return req->result().deferValue([req](auto const& e) -> PGManager::NullAsyncResult { - if (!e) { return folly::makeUnexpected(e.error()); } - return folly::Unit(); - }); + repl_dev->async_alloc_write(req->header_buf(), sisl::blob{}, sisl::sg_list{}, req, nullptr, tid); + auto e = co_await req->result(); + if (!e) { co_return std::unexpected(e.error()); } + co_return std::monostate{}; } -folly::Expected< HSHomeObject::HS_PG*, PGError > HSHomeObject::local_create_pg(shared< ReplDev > repl_dev, - PGInfo pg_info, trace_id_t tid) { +std::expected< HSHomeObject::HS_PG*, PGError > HSHomeObject::local_create_pg(shared< repl_dev > repl_dev, + PGInfo pg_info, trace_id_t tid) { auto pg_id = pg_info.id; if (auto hs_pg = get_hs_pg(pg_id); hs_pg) { // pg info may have changed due to replace_member, so we just log pg info here @@ -203,19 +202,19 @@ folly::Expected< HSHomeObject::HS_PG*, PGError > HSHomeObject::local_create_pg(s if (pg_info.chunk_size != local_chunk_size) { LOGE("Chunk sizes are inconsistent, leader_chunk_size={}, local_chunk_size={}, trace_id={}", pg_info.chunk_size, local_chunk_size, tid); - return folly::makeUnexpected< PGError >(PGError::UNKNOWN); + return std::unexpected< PGError >(PGError::UNKNOWN); } // select chunks for pg auto const num_chunk = chunk_selector()->select_chunks_for_pg(pg_id, pg_info.size); if (!num_chunk.has_value()) { LOGW("Failed to select chunks for pg={}, trace_id={}", pg_id, tid); - return folly::makeUnexpected(PGError::NO_SPACE_LEFT); + return std::unexpected(PGError::NO_SPACE_LEFT); } auto chunk_ids = chunk_selector()->get_pg_chunks(pg_id); if (chunk_ids == nullptr) { LOGW("Failed to get pg chunks, pg={}, trace_id={}", pg_id, tid); - return folly::makeUnexpected(PGError::NO_SPACE_LEFT); + return std::unexpected(PGError::NO_SPACE_LEFT); } // create index table and pg @@ -244,7 +243,7 @@ folly::Expected< HSHomeObject::HS_PG*, PGError > HSHomeObject::local_create_pg(s } void HSHomeObject::on_create_pg_message_commit(int64_t lsn, sisl::blob const& header, - shared< homestore::ReplDev > repl_dev, + shared< homestore::repl_dev > repl_dev, cintrusive< homestore::repl_req_ctx >& hs_ctx) { repl_result_ctx< PGManager::NullResult >* ctx{nullptr}; if (hs_ctx && hs_ctx->is_proposer()) { @@ -257,7 +256,7 @@ void HSHomeObject::on_create_pg_message_commit(int64_t lsn, sisl::blob const& he if (msg_header->corrupted()) { LOGE("create PG message header is corrupted , lsn={}; header={}, trace_id={}", lsn, msg_header->to_string(), tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(PGError::CRC_MISMATCH)); } + if (ctx) { ctx->set_err(PGError::CRC_MISMATCH); } return; } @@ -267,17 +266,17 @@ void HSHomeObject::on_create_pg_message_commit(int64_t lsn, sisl::blob const& he if (crc32_ieee(init_crc32, serailized_pg_info_buf, serailized_pg_info_size) != msg_header->payload_crc) { // header & value is inconsistent; LOGE("create PG message header is inconsistent with value, lsn={}, trace_id={}", lsn, tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(PGError::CRC_MISMATCH)); } + if (ctx) { ctx->set_err(PGError::CRC_MISMATCH); } return; } auto pg_info = deserialize_pg_info(serailized_pg_info_buf, serailized_pg_info_size); auto ret = local_create_pg(std::move(repl_dev), pg_info); if (ctx) { - if (ret.hasError()) { - ctx->promise_.setValue(folly::makeUnexpected(ret.error())); + if (!ret) { + ctx->set_err(ret.error()); } else { - ctx->promise_.setValue(folly::Unit()); + ctx->set_ok(); } } } @@ -291,21 +290,21 @@ PGManager::NullAsyncResult HSHomeObject::_replace_member(pg_id_t pg_id, std::str uint32_t commit_quorum, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down, trace_id={}", tid); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + co_return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); auto hs_pg = get_hs_pg(pg_id); if (hs_pg == nullptr) { decr_pending_request_num(); - return folly::makeUnexpected(PGError::UNKNOWN_PG); + co_return std::unexpected(PGError::UNKNOWN_PG); } auto& repl_dev = pg_repl_dev(*hs_pg); if (!repl_dev.is_leader() && commit_quorum == 0) { // Only leader can replace a member decr_pending_request_num(); - return folly::makeUnexpected(PGError::NOT_LEADER); + co_return std::unexpected(PGError::NOT_LEADER); } auto group_id = repl_dev.group_id(); @@ -316,14 +315,10 @@ PGManager::NullAsyncResult HSHomeObject::_replace_member(pg_id_t pg_id, std::str out_replica.id = old_member_id; replica_member_info in_replica = to_replica_member_info(new_member); - return hs_repl_service() - .replace_member(group_id, task_id, out_replica, in_replica, commit_quorum, tid) - .via(executor_) - .thenValue([this](auto&& v) mutable -> PGManager::NullAsyncResult { - decr_pending_request_num(); - if (v.hasError()) { return folly::makeUnexpected(toPgError(v.error())); } - return folly::Unit(); - }); + auto v = co_await hs_repl_service().replace_member(group_id, task_id, out_replica, in_replica, commit_quorum, tid); + decr_pending_request_num(); + if (!v) { co_return std::unexpected(toPgError(v.error())); } + co_return std::monostate{}; } PGMember HSHomeObject::to_pg_member(const replica_member_info& replica_info) const { @@ -555,21 +550,21 @@ PGManager::NullAsyncResult HSHomeObject::_flip_learner_flag(pg_id_t pg_id, peer_ uint32_t commit_quorum, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down, trace_id={}", tid); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + co_return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); auto hs_pg = get_hs_pg(pg_id); if (hs_pg == nullptr) { decr_pending_request_num(); - return folly::makeUnexpected(PGError::UNKNOWN_PG); + co_return std::unexpected(PGError::UNKNOWN_PG); } auto& repl_dev = pg_repl_dev(*hs_pg); if (!repl_dev.is_leader() && commit_quorum == 0) { // Only leader can replace a member decr_pending_request_num(); - return folly::makeUnexpected(PGError::NOT_LEADER); + co_return std::unexpected(PGError::NOT_LEADER); } auto group_id = repl_dev.group_id(); @@ -579,94 +574,81 @@ PGManager::NullAsyncResult HSHomeObject::_flip_learner_flag(pg_id_t pg_id, peer_ replica_member_info replica; replica.id = member_id; - return hs_repl_service() - .flip_learner_flag(group_id, replica, is_learner, commit_quorum, true, tid) - .via(executor_) - .thenValue([this](auto&& v) mutable -> PGManager::NullAsyncResult { - decr_pending_request_num(); - if (v.hasError()) { return folly::makeUnexpected(toPgError(v.error())); } - LOGI("PG flip learner flag done"); - return folly::Unit(); - }); + auto v = co_await hs_repl_service().flip_learner_flag(group_id, replica, is_learner, commit_quorum, true, tid); + decr_pending_request_num(); + if (!v) { co_return std::unexpected(toPgError(v.error())); } + co_return std::monostate{}; } PGManager::NullAsyncResult HSHomeObject::_remove_member(pg_id_t pg_id, peer_id_t const& member_id, uint32_t commit_quorum, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down, trace_id={}", tid); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + co_return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); auto hs_pg = get_hs_pg(pg_id); if (hs_pg == nullptr) { decr_pending_request_num(); - return folly::makeUnexpected(PGError::UNKNOWN_PG); + co_return std::unexpected(PGError::UNKNOWN_PG); } auto& repl_dev = pg_repl_dev(*hs_pg); if (!repl_dev.is_leader() && commit_quorum == 0) { // Only leader can replace a member decr_pending_request_num(); - return folly::makeUnexpected(PGError::NOT_LEADER); + co_return std::unexpected(PGError::NOT_LEADER); } auto group_id = repl_dev.group_id(); LOGI("PG remove member, pg_id={}, member={} trace_id={}", pg_id, boost::uuids::to_string(member_id), tid); - return hs_repl_service() - .remove_member(group_id, member_id, commit_quorum, tid) - .via(executor_) - .thenValue([this](auto&& v) mutable -> PGManager::NullAsyncResult { - decr_pending_request_num(); - if (v.hasError()) { return folly::makeUnexpected(toPgError(v.error())); } - return folly::Unit(); - }); + auto v = co_await hs_repl_service().remove_member(group_id, member_id, commit_quorum, true, tid); + decr_pending_request_num(); + if (!v) { co_return std::unexpected(toPgError(v.error())); } + co_return std::monostate{}; } PGManager::NullAsyncResult HSHomeObject::_clean_replace_member_task(pg_id_t pg_id, std::string& task_id, uint32_t commit_quorum, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down, trace_id={}", tid); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + co_return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); auto hs_pg = get_hs_pg(pg_id); if (hs_pg == nullptr) { decr_pending_request_num(); - return folly::makeUnexpected(PGError::UNKNOWN_PG); + co_return std::unexpected(PGError::UNKNOWN_PG); } auto& repl_dev = pg_repl_dev(*hs_pg); if (!repl_dev.is_leader() && commit_quorum == 0) { // Only leader can replace a member decr_pending_request_num(); - return folly::makeUnexpected(PGError::NOT_LEADER); + co_return std::unexpected(PGError::NOT_LEADER); } auto group_id = repl_dev.group_id(); LOGI("PG clean replace member task, pg={}, task={} trace_id={}", pg_id, task_id, tid); - return hs_repl_service() - .clean_replace_member_task(group_id, task_id, commit_quorum, tid) - .via(executor_) - .thenValue([this](auto&& v) mutable -> PGManager::NullAsyncResult { - decr_pending_request_num(); - if (v.hasError()) { return folly::makeUnexpected(toPgError(v.error())); } - return folly::Unit(); - }); + auto v = co_await hs_repl_service().clean_replace_member_task(group_id, task_id, commit_quorum, tid); + decr_pending_request_num(); + if (!v) { co_return std::unexpected(toPgError(v.error())); } + co_return std::monostate{}; } PGManager::Result< std::vector< replace_member_task > > HSHomeObject::_list_all_replace_member_tasks(trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down, trace_id={}", tid); - return folly::makeUnexpected(PGError::SHUTTING_DOWN); + return std::unexpected(PGError::SHUTTING_DOWN); } incr_pending_request_num(); auto ret = hs_repl_service().list_replace_member_tasks(); - if (ret.hasError()) { + if (!ret) { LOGE("Failed to list replace member tasks, error={}", ret.error()); decr_pending_request_num(); - return folly::makeUnexpected(toPgError(ret.error())); + return std::unexpected(toPgError(ret.error())); } // Convert homestore::replace_member_task to homeobject::replace_member_task @@ -743,21 +725,21 @@ bool HSHomeObject::pg_destroy(pg_id_t pg_id, bool need_to_pause_pg_state_machine PGManager::NullResult HSHomeObject::_exit_pg(uuid_t group_id, peer_id_t peer_id, trace_id_t tid) { if (group_id == boost::uuids::nil_uuid()) { LOGI("group_id is nil, nothing to exit, trace_id={}", tid); - return folly::makeUnexpected(PGError::INVALID_ARG); + return std::unexpected(PGError::INVALID_ARG); } // mark pg as destoryed and then permanent_destroy will call destory_pg to reclaim pg resource. auto ret = hs_repl_service().destroy_repl_dev(group_id); - if (ret == ReplServiceError::SERVER_NOT_FOUND) { - LOGW("repl dev not found, ignore, group_id={}, trace_id={}", group_id, tid); - return folly::Unit(); - } - if (ret != ReplServiceError::OK) { - LOGE("Failed to destroy repl dev for group_id={}, error={}, trace_id={}", group_id, ret, tid); - return folly::makeUnexpected(toPgError(ret)); + if (!ret) { + if (ret.error() == ReplServiceError::SERVER_NOT_FOUND) { + LOGW("repl dev not found, ignore, group_id={}, trace_id={}", group_id, tid); + return std::monostate(); + } + LOGE("Failed to destroy repl dev for group_id={}, error={}, trace_id={}", group_id, ret.error().message(), tid); + return std::unexpected(toPgError(ret.error())); } - return folly::Unit(); + return std::monostate(); } bool HSHomeObject::pause_pg_state_machine(pg_id_t pg_id) { @@ -832,7 +814,7 @@ void HSHomeObject::destroy_pg_index_table(pg_id_t pg_id) { auto uuid_str = boost::uuids::to_string(index_table->uuid()); index_table_pg_map_.erase(uuid_str); hs()->index_service().remove_index_table(index_table); - index_table->destroy(); + sisl::async::detach(index_table->destroy()); LOGD("pg={} index table is destroyed", pg_id); } else { LOGD("pg={} index table is not found, skip destroy", pg_id); @@ -848,7 +830,7 @@ void HSHomeObject::destroy_pg_superblk(pg_id_t pg_id) { RELEASE_ASSERT(success, "Failed to trigger CP flush"); LOGI("CP Flush triggered by pg_destroy completed, pg={}", pg_id); }; - on_complete(std::move(fut).get()); + on_complete(sisl::async::sync_get(std::move(fut))); { auto lg = std::scoped_lock(_pg_lock); @@ -926,15 +908,15 @@ PGInfo HSHomeObject::deserialize_pg_info(const unsigned char* json_str, size_t s return pg_info; } -void HSHomeObject::on_pg_meta_blk_found(sisl::byte_view const& buf, void* meta_cookie) { +void HSHomeObject::on_pg_meta_blk_found(sisl::byte_view const& buf, homestore::meta_blk* mblk) { LOGI("on_pg_meta_blk_found is called") homestore::superblk< pg_info_superblk > pg_sb(_pg_meta_name); - pg_sb.load(buf, meta_cookie); + pg_sb.load(buf, mblk); const auto pg_id = pg_sb->id; - shared< homestore::ReplDev > rdev; + shared< homestore::repl_dev > rdev; auto v = hs_repl_service().get_repl_dev(pg_sb->replica_set_uuid); - if (v.hasError()) { + if (!v) { // We have a pg_super_blk but cannot find the corresponding repl_dev. This happens when repl_dev // is marked as destroyed (m_rd_sb->destroy_pending = 0x1) in raft_repl_dev::leave(), but a crash // occurs before pg_destroy is called. @@ -995,7 +977,7 @@ PGInfo HSHomeObject::HS_PG::pg_info_from_sb(homestore::superblk< pg_info_superbl return pginfo; } -HSHomeObject::HS_PG::HS_PG(PGInfo info, shared< homestore::ReplDev > rdev, shared< BlobIndexTable > index_table, +HSHomeObject::HS_PG::HS_PG(PGInfo info, shared< homestore::repl_dev > rdev, shared< BlobIndexTable > index_table, std::shared_ptr< const std::vector< chunk_num_t > > pg_chunk_ids, HSHomeObject& home_obj) : PG{std::move(info)}, pg_sb_{_pg_meta_name}, @@ -1041,7 +1023,7 @@ HSHomeObject::HS_PG::HS_PG(PGInfo info, shared< homestore::ReplDev > rdev, share register_data_rpc_handlers(); } -HSHomeObject::HS_PG::HS_PG(superblk< pg_info_superblk >&& sb, shared< ReplDev > rdev, HSHomeObject& home_obj) : +HSHomeObject::HS_PG::HS_PG(superblk< pg_info_superblk >&& sb, shared< repl_dev > rdev, HSHomeObject& home_obj) : PG{pg_info_from_sb(sb)}, pg_sb_{std::move(sb)}, repl_dev_{std::move(rdev)}, @@ -1391,7 +1373,7 @@ void HSHomeObject::on_create_pg_message_rollback(int64_t lsn, sisl::blob const& if (msg_header->corrupted()) { LOGE("create PG message header is corrupted , lsn={}, header={}", lsn, msg_header->to_string()); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(PGError::CRC_MISMATCH)); } + if (ctx) { ctx->set_err(PGError::CRC_MISMATCH); } return; } @@ -1401,7 +1383,7 @@ void HSHomeObject::on_create_pg_message_rollback(int64_t lsn, sisl::blob const& if (crc32_ieee(init_crc32, serailized_pg_info_buf, serailized_pg_info_size) != msg_header->payload_crc) { // header & value is inconsistent; LOGE("create PG message header is inconsistent with value, lsn={}", lsn); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(PGError::CRC_MISMATCH)); } + if (ctx) { ctx->set_err(PGError::CRC_MISMATCH); } return; } @@ -1409,7 +1391,7 @@ void HSHomeObject::on_create_pg_message_rollback(int64_t lsn, sisl::blob const& case ReplicationMessageType::CREATE_PG_MSG: { // TODO:: add rollback logic for create pg rollback if necessary LOGI("lsn={}, msg_type={}, pg={}, create pg is rollbacked", lsn, msg_header->msg_type, msg_header->pg_id); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(PGError::ROLL_BACK)); } + if (ctx) { ctx->set_err(PGError::ROLL_BACK); } break; } default: { diff --git a/src/lib/homestore_backend/hs_shard_manager.cpp b/src/lib/homestore_backend/hs_shard_manager.cpp index 87ee827e6..15466acb0 100644 --- a/src/lib/homestore_backend/hs_shard_manager.cpp +++ b/src/lib/homestore_backend/hs_shard_manager.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include #include @@ -93,6 +96,13 @@ ShardError toShardError(ReplServiceError const& e) { } } +ShardError toShardError(std::error_condition const& e) { + if (e.category() == homestore::repl_error_category_inst()) { + return toShardError(static_cast< ReplServiceError >(e.value())); + } + return ShardError(ShardErrorCode::UNKNOWN); +} + uint64_t ShardManager::max_shard_size() { return Gi; } uint64_t ShardManager::max_shard_num_in_pg() { return ((uint64_t)0x01) << shard_width; } @@ -157,43 +167,43 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_create_shard(pg_id_t pg_ow if (is_shutting_down()) { LOGI("service is being shut down"); - return folly::makeUnexpected(ShardError(ShardErrorCode::SHUTTING_DOWN)); + co_return std::unexpected(ShardError(ShardErrorCode::SHUTTING_DOWN)); } incr_pending_request_num(); if (!meta.empty() && meta.length() > ShardInfo::meta_length - 1) { LOGW("meta length {} exceeds max meta length {}, trace_id={}", meta.length(), ShardInfo::meta_length - 1, tid); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::INVALID_ARG)); + co_return std::unexpected(ShardError(ShardErrorCode::INVALID_ARG)); } auto hs_pg = get_hs_pg(pg_owner); if (!hs_pg) { LOGW("failed to create shard with non-exist pg={}", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); + co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); } if (hs_pg->pg_state_.is_state_set(PGStateMask::DISK_DOWN)) { LOGW("failed to create shard for pg={}, pg is disk down and not leader", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NOT_LEADER)); + co_return std::unexpected(ShardError(ShardErrorCode::NOT_LEADER)); } auto repl_dev = hs_pg->repl_dev_; if (!repl_dev) { LOGW("failed to get repl dev instance for pg={}", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::PG_NOT_READY)); + co_return std::unexpected(ShardError(ShardErrorCode::PG_NOT_READY)); } if (!repl_dev->is_leader()) { LOGW("failed to create shard for pg={}, not leader", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NOT_LEADER, repl_dev->get_leader_id())); + co_return std::unexpected(ShardError(ShardErrorCode::NOT_LEADER, repl_dev->get_leader_id())); } if (!repl_dev->is_ready_for_traffic()) { LOGW("failed to create shard for pg={}, not ready for traffic", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::RETRY_REQUEST)); + co_return std::unexpected(ShardError(ShardErrorCode::RETRY_REQUEST)); } auto new_shard_id = generate_new_shard_id(pg_owner); SLOGD(tid, new_shard_id, "Create shard request: pg={}, size={}", pg_owner, size_bytes); @@ -204,7 +214,7 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_create_shard(pg_id_t pg_ow if (exVchunk == nullptr) { SLOGW(tid, new_shard_id, "no available chunk left to create shard for pg={}", pg_owner); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NO_SPACE_LEFT)); + co_return std::unexpected(ShardError(ShardErrorCode::NO_SPACE_LEFT)); } // we will not persist shard header/footer to disk anymore, and the emergency gc will never be triggered by @@ -221,10 +231,10 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_create_shard(pg_id_t pg_ow bool res = chunk_selector()->release_chunk(pg_owner, v_chunk_id); RELEASE_ASSERT(res, "Failed to release v_chunk_id={}, pg={}", v_chunk_id, pg_owner); - gc_manager()->submit_gc_task(task_priority::normal, pchunk_id); + sisl::async::detach(gc_manager()->submit_gc_task(task_priority::normal, pchunk_id)); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NO_SPACE_LEFT)); + co_return std::unexpected(ShardError(ShardErrorCode::NO_SPACE_LEFT)); } SLOGD(tid, new_shard_id, "vchunk_id={}", v_chunk_id); @@ -266,34 +276,33 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_create_shard(pg_id_t pg_ow std::memcpy(req->header_extn(), sb_blob.cbytes(), sizeof(shard_info_superblk)); // replicate this create shard message to PG members (log-only, no data blocks); - repl_dev->async_alloc_write(req->cheader_buf(), sisl::blob{}, sisl::sg_list{}, req, false /* part_of_batch */, tid); - return req->result().deferValue([this, req, repl_dev, tid, pg_owner, new_shard_id, - v_chunk_id](const auto& result) -> ShardManager::AsyncResult< ShardInfo > { - if (result.hasError()) { - auto err = result.error(); - if (err.getCode() == ShardErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } - - // we will never get no_space_left error here. - bool res = chunk_selector()->release_chunk(pg_owner, v_chunk_id); - RELEASE_ASSERT(res, "Failed to release v_chunk_id={}, pg={}", v_chunk_id, pg_owner); - - SLOGE(tid, new_shard_id, "got {} when creating shard at leader, failed to create shard {}!", err.getCode(), - new_shard_id); - - decr_pending_request_num(); - return folly::makeUnexpected(err); - } - auto shard_info = result.value(); - SLOGD(tid, shard_info.id, "Shard created success."); + repl_dev->async_alloc_write(req->cheader_buf(), sisl::blob{}, sisl::sg_list{}, req, nullptr, tid); + auto result = co_await req->result(); + + if (!result) { + auto err = result.error(); + if (err.getCode() == ShardErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } + + // we will never get no_space_left error here. + bool res = chunk_selector()->release_chunk(pg_owner, v_chunk_id); + RELEASE_ASSERT(res, "Failed to release v_chunk_id={}, pg={}", v_chunk_id, pg_owner); + + SLOGE(tid, new_shard_id, "got {} when creating shard at leader, failed to create shard {}!", err.getCode(), + new_shard_id); + decr_pending_request_num(); - return shard_info; - }); + co_return std::unexpected(err); + } + auto shard_info = result.value(); + SLOGD(tid, shard_info.id, "Shard created success."); + decr_pending_request_num(); + co_return shard_info; } ShardManager::AsyncResult< ShardInfo > HSHomeObject::_seal_shard(ShardInfo const& info, trace_id_t tid) { if (is_shutting_down()) { LOGI("service is being shut down"); - return folly::makeUnexpected(ShardError(ShardErrorCode::SHUTTING_DOWN)); + co_return std::unexpected(ShardError(ShardErrorCode::SHUTTING_DOWN)); } incr_pending_request_num(); @@ -304,39 +313,39 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_seal_shard(ShardInfo const if (!hs_pg) { SLOGW(tid, shard_id, "pg={} not found", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); + co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); } if (hs_pg->pg_state_.is_state_set(PGStateMask::DISK_DOWN)) { LOGW("failed to seal shard for pg={}, pg is disk down and not leader", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NOT_LEADER)); + co_return std::unexpected(ShardError(ShardErrorCode::NOT_LEADER)); } auto repl_dev = hs_pg->repl_dev_; if (!repl_dev) { SLOGW(tid, shard_id, "failed to get repl dev instance for pg={}", pg_id); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::PG_NOT_READY)); + co_return std::unexpected(ShardError(ShardErrorCode::PG_NOT_READY)); } if (!repl_dev->is_leader()) { SLOGW(tid, shard_id, "failed to seal shard, not leader"); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::NOT_LEADER, repl_dev->get_leader_id())); + co_return std::unexpected(ShardError(ShardErrorCode::NOT_LEADER, repl_dev->get_leader_id())); } if (!repl_dev->is_ready_for_traffic()) { SLOGW(tid, shard_id, "failed to seal shard, not ready for traffic"); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::RETRY_REQUEST)); + co_return std::unexpected(ShardError(ShardErrorCode::RETRY_REQUEST)); } const auto v_chunkID = get_shard_v_chunk_id(shard_id); if (!v_chunkID.has_value()) { SLOGW(tid, shard_id, "failed to seal shard, vchunk id not found"); decr_pending_request_num(); - return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); + co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); } const auto v_chunk_id = v_chunkID.value(); @@ -365,20 +374,19 @@ ShardManager::AsyncResult< ShardInfo > HSHomeObject::_seal_shard(ShardInfo const std::memcpy(req->header_extn(), sb_blob.cbytes(), sizeof(shard_info_superblk)); // replicate this seal shard message to PG members (log-only, no data blocks); - repl_dev->async_alloc_write(req->cheader_buf(), sisl::blob{}, sisl::sg_list{}, req, false /* part_of_batch */, tid); - return req->result().deferValue( - [this, req, repl_dev, tid](const auto& result) -> ShardManager::AsyncResult< ShardInfo > { - if (result.hasError()) { - auto err = result.error(); - if (err.getCode() == ShardErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } - decr_pending_request_num(); - return folly::makeUnexpected(err); - } - auto shard_info = result.value(); - SLOGD(tid, shard_info.id, "Seal shard request: Shard sealed success, is_open={}", shard_info.is_open()); - decr_pending_request_num(); - return shard_info; - }); + repl_dev->async_alloc_write(req->cheader_buf(), sisl::blob{}, sisl::sg_list{}, req, nullptr, tid); + auto result = co_await req->result(); + + if (!result) { + auto err = result.error(); + if (err.getCode() == ShardErrorCode::NOT_LEADER) { err.current_leader = repl_dev->get_leader_id(); } + decr_pending_request_num(); + co_return std::unexpected(err); + } + auto shard_info = result.value(); + SLOGD(tid, shard_info.id, "Seal shard request: Shard sealed success, is_open={}", shard_info.is_open()); + decr_pending_request_num(); + co_return shard_info; } bool HSHomeObject::on_shard_message_pre_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, @@ -391,7 +399,7 @@ bool HSHomeObject::on_shard_message_pre_commit(int64_t lsn, sisl::blob const& he const ReplicationMessageHeader* msg_header = r_cast< const ReplicationMessageHeader* >(header.cbytes()); if (msg_header->corrupted()) { LOGW("replication message header is corrupted with crc error, lsn={}, traceID={}", lsn, tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(ShardError(ShardErrorCode::CRC_MISMATCH))); } + if (ctx) { ctx->set_err(ShardError(ShardErrorCode::CRC_MISMATCH)); } // TODO::if fail to pre_commit, shuold we crash here? return false; @@ -423,7 +431,7 @@ void HSHomeObject::on_shard_message_rollback(int64_t lsn, sisl::blob const& head switch (msg_header->msg_type) { case ReplicationMessageType::CREATE_SHARD_MSG: { SLOGD(tid, msg_header->shard_id, "rollback create shard message, type={}, lsn= {}", msg_header->msg_type, lsn); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(ShardError(ShardErrorCode::RETRY_REQUEST))); } + if (ctx) { ctx->set_err(ShardError(ShardErrorCode::RETRY_REQUEST)); } break; } case ReplicationMessageType::SEAL_SHARD_MSG: { @@ -431,7 +439,7 @@ void HSHomeObject::on_shard_message_rollback(int64_t lsn, sisl::blob const& head // in the shard map. Just fail the proposer's promise. SLOGD(tid, msg_header->shard_id, "rollback seal shard message, type={}, lsn={}", msg_header->msg_type, lsn); // TODO:set a proper error code - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(ShardError(ShardErrorCode::RETRY_REQUEST))); } + if (ctx) { ctx->set_err(ShardError(ShardErrorCode::RETRY_REQUEST)); } break; } @@ -465,7 +473,7 @@ void HSHomeObject::local_create_shard(ShardInfo shard_info, homestore::chunk_num } } -void HSHomeObject::on_shard_message_commit(int64_t lsn, sisl::blob const& h, shared< homestore::ReplDev > repl_dev, +void HSHomeObject::on_shard_message_commit(int64_t lsn, sisl::blob const& h, shared< homestore::repl_dev > repl_dev, cintrusive< homestore::repl_req_ctx >& hs_ctx) { repl_result_ctx< ShardManager::Result< ShardInfo > >* ctx{nullptr}; if (hs_ctx && hs_ctx->is_proposer()) { @@ -475,7 +483,7 @@ void HSHomeObject::on_shard_message_commit(int64_t lsn, sisl::blob const& h, sha auto header = r_cast< const ReplicationMessageHeader* >(h.cbytes()); if (header->corrupted()) { LOGW("replication message header is corrupted with crc error, lsn={}, traceID={}", lsn, tid); - if (ctx) { ctx->promise_.setValue(folly::makeUnexpected(ShardError(ShardErrorCode::CRC_MISMATCH))); } + if (ctx) { ctx->set_err(ShardError(ShardErrorCode::CRC_MISMATCH)); } // TODO::if fail to commit, shuold we crash here? return; } @@ -508,7 +516,7 @@ void HSHomeObject::on_shard_message_commit(int64_t lsn, sisl::blob const& h, sha shard_info.create_lsn = lsn; local_create_shard(shard_info, v_chunk_id, tid); - if (ctx) { ctx->promise_.setValue(ShardManager::Result< ShardInfo >(shard_info)); } + if (ctx) { ctx->set_ok(shard_info); } SLOGD(tid, shard_info.id, "Commit done for creating shard"); @@ -570,7 +578,7 @@ void HSHomeObject::on_shard_message_commit(int64_t lsn, sisl::blob const& h, sha bool res = chunk_selector()->release_chunk(pg_id, v_chunkID.value()); RELEASE_ASSERT(res, "Failed to release v_chunk_id={}, pg={}", v_chunkID.value(), pg_id); - if (ctx) { ctx->promise_.setValue(ShardManager::Result< ShardInfo >(shard_info)); } + if (ctx) { ctx->set_ok(shard_info); } SLOGD(tid, shard_info.id, "Commit done for sealing shard at lsn={}", lsn); break; } diff --git a/src/lib/homestore_backend/index_kv.cpp b/src/lib/homestore_backend/index_kv.cpp index c83dc7d6f..17dd87d0d 100644 --- a/src/lib/homestore_backend/index_kv.cpp +++ b/src/lib/homestore_backend/index_kv.cpp @@ -95,7 +95,7 @@ std::pair< bool, homestore::btree_status_t > HSHomeObject::add_to_index_table(sh return {false, status}; } -BlobManager::Result< homestore::MultiBlkId > +BlobManager::Result< homestore::multi_blk_id > HSHomeObject::get_blob_from_index_table(shared< BlobIndexTable > index_table, shard_id_t shard_id, blob_id_t blob_id) const { BlobRouteKey index_key{BlobRoute{shard_id, blob_id}}; @@ -104,12 +104,12 @@ HSHomeObject::get_blob_from_index_table(shared< BlobIndexTable > index_table, sh if (homestore::btree_status_t::success != index_table->get(get_req)) { LOGDEBUG("Failed to get from index table [route={}]", index_key); - return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); + return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); } // blob get API if (const auto& pbas = index_value.pbas(); pbas != tombstone_pbas) return pbas; - return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); + return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); } void HSHomeObject::print_btree_index(pg_id_t pg_id) const { @@ -153,7 +153,7 @@ HSHomeObject::query_blobs_in_shard(pg_id_t pg_id, uint64_t cur_shard_seq_num, bl auto const ret = index_table->query(query_req, out_vector); if (ret != homestore::btree_status_t::success && ret != homestore::btree_status_t::has_more) { LOGE("Failed to query blobs in index table for ret={} shard={} start_blob_id={}", ret, shard_id, start_blob_id); - return folly::makeUnexpected(BlobErrorCode::INDEX_ERROR); + return std::unexpected(BlobErrorCode::INDEX_ERROR); } std::vector< BlobInfo > blob_info_vec; diff --git a/src/lib/homestore_backend/index_kv.hpp b/src/lib/homestore_backend/index_kv.hpp index 8a20de1e6..70cf1569c 100644 --- a/src/lib/homestore_backend/index_kv.hpp +++ b/src/lib/homestore_backend/index_kv.hpp @@ -98,7 +98,7 @@ class BlobRouteByChunkKey : public homestore::BtreeKey { class BlobRouteValue : public homestore::BtreeValue { public: BlobRouteValue() = default; - BlobRouteValue(const homestore::MultiBlkId& pbas) : pbas_(pbas) {} + BlobRouteValue(const homestore::multi_blk_id& pbas) : pbas_(pbas) {} BlobRouteValue(const BlobRouteValue& other) : homestore::BtreeValue() { pbas_ = other.pbas_; }; BlobRouteValue(const sisl::blob& b, bool copy) : homestore::BtreeValue() { deserialize(b, copy); } BlobRouteValue(const homestore::BtreeValue& other) : BlobRouteValue(other.serialize(), true) {} @@ -110,12 +110,12 @@ class BlobRouteValue : public homestore::BtreeValue { } sisl::blob serialize() const override { - auto& pba = const_cast< homestore::MultiBlkId& >(pbas_); + auto& pba = const_cast< homestore::multi_blk_id& >(pbas_); return pba.serialize(); } uint32_t serialized_size() const override { return pbas_.serialized_size(); } - static uint32_t get_fixed_size() { return homestore::MultiBlkId::expected_serialized_size(1 /* num_pieces */); } + static uint32_t get_fixed_size() { return homestore::multi_blk_id::expected_serialized_size(1 /* num_pieces */); } void deserialize(const sisl::blob& b, bool copy) override { pbas_.deserialize(b, copy); } std::string to_string() const override { return fmt::format("{}", pbas_.to_string()); } @@ -124,10 +124,10 @@ class BlobRouteValue : public homestore::BtreeValue { return os; } - homestore::MultiBlkId pbas() const { return pbas_; } + homestore::multi_blk_id pbas() const { return pbas_; } private: - homestore::MultiBlkId pbas_; + homestore::multi_blk_id pbas_; }; } // namespace homeobject @@ -141,8 +141,8 @@ struct formatter< homeobject::BlobRouteKey > { } template < typename FormatContext > - auto format(homeobject::BlobRouteKey const& r, FormatContext& ctx) { - return fmt::v10::format_to(ctx.out(), "{}", r.key()); + auto format(homeobject::BlobRouteKey const& r, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", r.key()); } }; @@ -154,8 +154,8 @@ struct formatter< homeobject::BlobRouteByChunkKey > { } template < typename FormatContext > - auto format(homeobject::BlobRouteByChunkKey const& r, FormatContext& ctx) { - return fmt::v10::format_to(ctx.out(), "{}", r.key()); + auto format(homeobject::BlobRouteByChunkKey const& r, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", r.key()); } }; } // namespace fmt diff --git a/src/lib/homestore_backend/pg_blob_iterator.cpp b/src/lib/homestore_backend/pg_blob_iterator.cpp index 438eeda02..1a4b1b7fa 100644 --- a/src/lib/homestore_backend/pg_blob_iterator.cpp +++ b/src/lib/homestore_backend/pg_blob_iterator.cpp @@ -1,4 +1,5 @@ #include "hs_homeobject.hpp" +#include #include #include #include @@ -270,13 +271,35 @@ bool HSHomeObject::PGBlobIterator::create_shard_snapshot_data(sisl::io_blob_safe } typedef HSHomeObject::PGBlobIterator::blob_read_result blob_read_result; -BlobManager::AsyncResult< blob_read_result > HSHomeObject::PGBlobIterator::load_blob_data(const BlobInfo& blob_info) { +BlobManager::AsyncResult< blob_read_result > HSHomeObject::PGBlobIterator::load_blob_data(BlobInfo blob_info) { return load_blob_data_with_blkid(blob_info.shard_id, blob_info.blob_id, blob_info.pbas); } +BlobManager::AsyncResult< blob_read_result > +HSHomeObject::PGBlobIterator::load_prefetched_blob(BlobInfo info, Clock::time_point blob_start) { +#ifdef _PRERELEASE + if (iomgr_flip::instance()->test_flip("pg_blob_iterator_load_blob_data_error")) { + LOGD("Simulating resync blob prefetch failure: pg={}, blob={}", pg_id, info.blob_id); + COUNTER_INCREMENT(*metrics_, snp_dnr_error_count, 1); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } +#endif + auto result = co_await load_blob_data(std::move(info)); + if (!result && result.error().code == BlobErrorCode::READ_FAILED) { + LOGE("Failed to prefetch resync blob: pg={}, shard=0x{:x}, blob={}, blkid={}", + info.shard_id >> homeobject::shard_width, info.shard_id & homeobject::shard_mask, info.blob_id, + info.pbas.to_string()); + COUNTER_INCREMENT(*metrics_, snp_dnr_error_count, 1); + } else { + LOGT("Prefetched resync blob: pg={}, blob={}, blkid={}", pg_id, info.blob_id, info.pbas.to_string()); + HISTOGRAM_OBSERVE(*metrics_, snp_dnr_blob_process_latency, get_elapsed_time_us(blob_start)); + } + co_return result; +} + BlobManager::AsyncResult< blob_read_result > HSHomeObject::PGBlobIterator::load_blob_data_with_blkid(shard_id_t shard_id, blob_id_t blob_id, - homestore::MultiBlkId blkid) { + homestore::multi_blk_id blkid) { auto const total_size = blkid.blk_count() * repl_dev_->get_blk_size(); sisl::io_blob_safe read_buf{total_size, io_align}; @@ -286,54 +309,49 @@ HSHomeObject::PGBlobIterator::load_blob_data_with_blkid(shard_id_t shard_id, blo LOGT("Reading resync blob: pg={}, shard=0x{:x}, blob={}, blkid={}, bytes={}", (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, blkid.to_string(), total_size); - return repl_dev_->async_read(blkid, sgs, total_size) - .thenValue([this, blob_id, shard_id, blkid, read_buf = std::move(read_buf)]( - auto&& result) mutable -> BlobManager::AsyncResult< blob_read_result > { - if (result) { - LOGE("Failed to read resync blob: pg={}, shard=0x{:x}, blob={}, blkid={}, error={}", - (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, - blkid.to_string(), result.value()); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); - } + auto result = co_await repl_dev_->async_read(blkid, sgs, total_size); + if (!result) { + LOGE("Failed to read resync blob: pg={}, shard=0x{:x}, blob={}, blkid={}, error={}", + (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, blkid.to_string(), + result.error().message()); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } - if (home_obj_.verify_blob(read_buf.cbytes(), shard_id, blob_id)) { - LOGT("Read resync blob: pg={}, shard=0x{:x}, blob={}", (shard_id >> homeobject::shard_width), - (shard_id & homeobject::shard_mask), blob_id); - return blob_read_result(blob_id, std::move(read_buf), ResyncBlobState::NORMAL); - } + if (home_obj_.verify_blob(read_buf.cbytes(), shard_id, blob_id)) { + LOGT("Read resync blob: pg={}, shard=0x{:x}, blob={}", (shard_id >> homeobject::shard_width), + (shard_id & homeobject::shard_mask), blob_id); + co_return blob_read_result(blob_id, std::move(read_buf), ResyncBlobState::NORMAL); + } - // verify_blob failed — check if GC moved the blob to a new blkid since cur_blob_list_ was captured. - auto index_table = home_obj_.get_index_table(pg_id); - if (!index_table) { - LOGE( - "Cannot resolve resync blob verification failure: pg={} no longer exists, shard_id=0x{:x}, blob={}", - pg_id, shard_id, blob_id); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); - } - auto current_pbas = home_obj_.get_blob_from_index_table(index_table, shard_id, blob_id); - if (!current_pbas) { - // Blob was deleted concurrently after generate_shard_blob_list captured its pbas. - // Do not send stale bytes as CORRUPTED — signal READ_FAILED so the snapshot restarts - // and generate_shard_blob_list picks up tombstone_pbas, skipping the blob cleanly. - LOGW("Resync blob was deleted during read; restarting snapshot: pg={}, shard_id=0x{:x}, blob={}", pg_id, - shard_id, blob_id); - return folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED)); - } - if (current_pbas.value() == blkid) { - // blkid unchanged — genuinely corrupted data at this location. - // Metrics for corrupted blobs are handled on the follower side. - LOGE("Resync blob verification failed: pg={}, shard=0x{:x}, blob={}, blkid={}", - (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, - blkid.to_string()); - return blob_read_result(blob_id, std::move(read_buf), ResyncBlobState::CORRUPTED); - } + // verify_blob failed — check if GC moved the blob to a new blkid since cur_blob_list_ was captured. + auto index_table = home_obj_.get_index_table(pg_id); + if (!index_table) { + LOGE("Cannot resolve resync blob verification failure: pg={} no longer exists, shard_id=0x{:x}, blob={}", pg_id, + shard_id, blob_id); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } + auto current_pbas = home_obj_.get_blob_from_index_table(index_table, shard_id, blob_id); + if (!current_pbas) { + // Blob was deleted concurrently after generate_shard_blob_list captured its pbas. + // Do not send stale bytes as CORRUPTED — signal READ_FAILED so the snapshot restarts + // and generate_shard_blob_list picks up tombstone_pbas, skipping the blob cleanly. + LOGW("Resync blob was deleted during read; restarting snapshot: pg={}, shard_id=0x{:x}, blob={}", pg_id, + shard_id, blob_id); + co_return std::unexpected(BlobError(BlobErrorCode::READ_FAILED)); + } + if (current_pbas.value() == blkid) { + // blkid unchanged — genuinely corrupted data at this location. + // Metrics for corrupted blobs are handled on the follower side. + LOGE("Resync blob verification failed: pg={}, shard=0x{:x}, blob={}, blkid={}", + (shard_id >> homeobject::shard_width), (shard_id & homeobject::shard_mask), blob_id, blkid.to_string()); + co_return blob_read_result(blob_id, std::move(read_buf), ResyncBlobState::CORRUPTED); + } - // GC moved the blob — retry with the updated blkid. Folly flattens the returned future. - LOGI("Resync blob relocated by GC during read; retrying: pg={}, shard_id=0x{:x}, blob={}, old_blkid={}, " - "new_blkid={}", - pg_id, shard_id, blob_id, blkid.to_string(), current_pbas.value().to_string()); - return load_blob_data_with_blkid(shard_id, blob_id, current_pbas.value()); - }); + // GC moved the blob — retry with the updated blkid. + LOGI("Resync blob relocated by GC during read; retrying: pg={}, shard_id=0x{:x}, blob={}, old_blkid={}, " + "new_blkid={}", + pg_id, shard_id, blob_id, blkid.to_string(), current_pbas.value().to_string()); + co_return co_await load_blob_data_with_blkid(shard_id, blob_id, current_pbas.value()); } bool HSHomeObject::PGBlobIterator::prefetch_blobs_snapshot_data() { @@ -372,11 +390,6 @@ bool HSHomeObject::PGBlobIterator::prefetch_blobs_snapshot_data() { [](const BlobInfo& a, const BlobInfo& b) { return a.pbas < b.pbas; }); for (auto info : prefetch_list) { #ifdef _PRERELEASE - if (iomgr_flip::instance()->test_flip("pg_blob_iterator_load_blob_data_error")) { - LOGD("Simulating resync blob prefetch failure: pg={}, blob={}", pg_id, info.blob_id); - prefetched_blobs_.emplace(info.blob_id, folly::makeUnexpected(BlobError(BlobErrorCode::READ_FAILED))); - continue; - } auto delay = iomgr_flip::instance()->get_test_flip< long >("simulate_read_snapshot_load_blob_delay", static_cast< long >(info.blob_id)); if (delay) { @@ -388,24 +401,7 @@ bool HSHomeObject::PGBlobIterator::prefetch_blobs_snapshot_data() { LOGT("Submitting resync blob read: pg={}, shard_id=0x{:x}, blob={}", pg_id, info.shard_id, info.blob_id); // Fixme: Re-enable retries uint8_t retries = HS_BACKEND_DYNAMIC_CONFIG(snapshot_blob_load_retry); - prefetched_blobs_.emplace( - info.blob_id, - std::move(load_blob_data(info)) - .via(folly::getKeepAliveToken(folly::InlineExecutor::instance())) - .thenValue( - [&, info, blob_start](auto&& result) mutable -> BlobManager::AsyncResult< blob_read_result > { - if (result.hasError() && result.error().code == BlobErrorCode::READ_FAILED) { - LOGE("Failed to prefetch resync blob: pg={}, shard=0x{:x}, blob={}, blkid={}", - info.shard_id >> homeobject::shard_width, info.shard_id & homeobject::shard_mask, - info.blob_id, info.pbas.to_string()); - COUNTER_INCREMENT(*metrics_, snp_dnr_error_count, 1); - } else { - LOGT("Prefetched resync blob: pg={}, blob={}, blkid={}", pg_id, info.blob_id, - info.pbas.to_string()); - HISTOGRAM_OBSERVE(*metrics_, snp_dnr_blob_process_latency, get_elapsed_time_us(blob_start)); - } - return result; - })); + prefetched_blobs_.emplace(info.blob_id, load_prefetched_blob(info, blob_start)); } LOGD("Resync prefetch window: pg={}, shard_seq=0x{:x}, cursor_blob={}, frontier={}, submitted_blobs={}, " "skipped_blobs={}, inflight_bytes={}, limit_bytes={}", @@ -457,11 +453,11 @@ bool HSHomeObject::PGBlobIterator::create_blobs_snapshot_data(sisl::io_blob_safe break; } auto const expect_blob_size = info.pbas.blk_count() * repl_dev_->get_blk_size(); - auto res = std::move(it->second).get(); + auto res = sisl::async::sync_get(std::move(it->second)); prefetched_blobs_.erase(it); inflight_prefetch_bytes_ -= expect_blob_size; - if (res.hasError()) { + if (!res) { LOGE("Resync batch failed to retrieve blob: pg={}, shard_seq=0x{:x}, batch={}, blob={}, error={}", pg_id, cur_obj_id.shard_seq_num, cur_obj_id.batch_id, info.blob_id, res.error()); hit_error = true; @@ -534,7 +530,7 @@ void HSHomeObject::PGBlobIterator::stop() { // Wait for all inflight prefetch blobs to finish and drain the data for (auto& blob : prefetched_blobs_) { LOGT("Draining prefetched resync blob: pg={}, blob={}", pg_id, blob.first); - std::move(blob.second).get(); + sisl::async::sync_get(std::move(blob.second)); } prefetched_blobs_.clear(); inflight_prefetch_bytes_ = 0; diff --git a/src/lib/homestore_backend/replication_message.hpp b/src/lib/homestore_backend/replication_message.hpp index e485a4bce..f7cebfa89 100644 --- a/src/lib/homestore_backend/replication_message.hpp +++ b/src/lib/homestore_backend/replication_message.hpp @@ -5,14 +5,14 @@ #include #include -#include +#include namespace homeobject { -VENUM(ReplicationMessageType, uint16_t, CREATE_PG_MSG = 0, CREATE_SHARD_MSG = 1, SEAL_SHARD_MSG = 2, PUT_BLOB_MSG = 3, - DEL_BLOB_MSG = 4, UNKNOWN_MSG = 5); -VENUM(SyncMessageType, uint16_t, PG_META = 0, SHARD_META = 1, SHARD_BATCH = 2, LAST_MSG = 3); -VENUM(ResyncBlobState, uint8_t, NORMAL = 0, DELETED = 1, CORRUPTED = 2); +ENUM(ReplicationMessageType, uint16_t, CREATE_PG_MSG = 0, CREATE_SHARD_MSG = 1, SEAL_SHARD_MSG = 2, PUT_BLOB_MSG = 3, + DEL_BLOB_MSG = 4, UNKNOWN_MSG = 5); +ENUM(SyncMessageType, uint16_t, PG_META = 0, SHARD_META = 1, SHARD_BATCH = 2, LAST_MSG = 3); +ENUM(ResyncBlobState, uint8_t, NORMAL = 0, DELETED = 1, CORRUPTED = 2); // magic num comes from the first 8 bytes of 'echo homeobject_replication | md5sum' static constexpr uint64_t HOMEOBJECT_REPLICATION_MAGIC = 0x11153ca24efc8d34; diff --git a/src/lib/homestore_backend/replication_state_machine.cpp b/src/lib/homestore_backend/replication_state_machine.cpp index 159aff4b6..f1fd55aff 100644 --- a/src/lib/homestore_backend/replication_state_machine.cpp +++ b/src/lib/homestore_backend/replication_state_machine.cpp @@ -1,3 +1,5 @@ +#include +#include #include "replication_message.hpp" #include "replication_state_machine.hpp" #include "hs_backend_config.hpp" @@ -5,8 +7,8 @@ #include "generated/resync_pg_data_generated.h" #include "generated/resync_shard_data_generated.h" #include "generated/resync_blob_data_generated.h" -#include -#include +#include +#include #include "hs_homeobject.hpp" namespace homeobject { @@ -18,7 +20,7 @@ uint64_t ReplicationStateMachine::snapshot_offset_for_next_shard(shard_id_t shar } void ReplicationStateMachine::on_commit(int64_t lsn, const sisl::blob& header, const sisl::blob& key, - const std::vector< homestore::MultiBlkId >& pbas, + const std::vector< homestore::multi_blk_id >& pbas, cintrusive< homestore::repl_req_ctx >& ctx) { const ReplicationMessageHeader* msg_header = r_cast< const ReplicationMessageHeader* >(header.cbytes()); RELEASE_ASSERT_EQ(pbas.size(), 1, "Invalid blklist size"); @@ -26,12 +28,12 @@ void ReplicationStateMachine::on_commit(int64_t lsn, const sisl::blob& header, c LOGT("applying raft log commit with lsn={}, msg type={}", lsn, msg_header->msg_type); switch (msg_header->msg_type) { case ReplicationMessageType::CREATE_PG_MSG: { - home_object_->on_create_pg_message_commit(lsn, header, repl_dev(), ctx); + home_object_->on_create_pg_message_commit(lsn, header, device(), ctx); break; } case ReplicationMessageType::CREATE_SHARD_MSG: case ReplicationMessageType::SEAL_SHARD_MSG: { - home_object_->on_shard_message_commit(lsn, header, repl_dev(), ctx); + home_object_->on_shard_message_commit(lsn, header, device(), ctx); break; } @@ -62,7 +64,7 @@ void ReplicationStateMachine::notify_committed_lsn(int64_t lsn) { "current no_space_left_error_info lsn={}, committed rreq lsn={}", target_lsn, lsn); - if (repl_dev()->is_leader()) { + if (device()->is_leader()) { // no need to handle stale no_space_left as a leader, since that log(casuing no_space_left) is not in the // log store, and will not be committed. LOGT("I am leader, reset no_space_left_error_info(lsn={}, chunk_id={}) after lsn={} is committed", target_lsn, @@ -177,25 +179,25 @@ void ReplicationStateMachine::on_error(ReplServiceError error, const sisl::blob& switch (msg_header->msg_type) { case ReplicationMessageType::CREATE_PG_MSG: { auto result_ctx = boost::static_pointer_cast< repl_result_ctx< PGManager::NullResult > >(ctx).get(); - result_ctx->promise_.setValue(folly::makeUnexpected(homeobject::toPgError(error))); + result_ctx->set_err(homeobject::toPgError(error)); break; } case ReplicationMessageType::CREATE_SHARD_MSG: { // Create shard is log only, no need to release chunk anymore, just return error to caller. auto result_ctx = boost::static_pointer_cast< repl_result_ctx< ShardManager::Result< ShardInfo > > >(ctx).get(); - result_ctx->promise_.setValue(folly::makeUnexpected(toShardError(error))); + result_ctx->set_err(toShardError(error)); break; } case ReplicationMessageType::SEAL_SHARD_MSG: { auto result_ctx = boost::static_pointer_cast< repl_result_ctx< ShardManager::Result< ShardInfo > > >(ctx).get(); - result_ctx->promise_.setValue(folly::makeUnexpected(toShardError(error))); + result_ctx->set_err(toShardError(error)); break; } case ReplicationMessageType::PUT_BLOB_MSG: case ReplicationMessageType::DEL_BLOB_MSG: { auto result_ctx = boost::static_pointer_cast< repl_result_ctx< BlobManager::Result< HSHomeObject::BlobInfo > > >(ctx).get(); - result_ctx->promise_.setValue(folly::makeUnexpected(toBlobError(error))); + result_ctx->set_err(toBlobError(error)); break; } default: { @@ -205,7 +207,7 @@ void ReplicationStateMachine::on_error(ReplServiceError error, const sisl::blob& } } -homestore::ReplResult< homestore::blk_alloc_hints > +homestore::result< homestore::blk_alloc_hints > ReplicationStateMachine::get_blk_alloc_hints(sisl::blob const& header, uint32_t data_size, cintrusive< homestore::repl_req_ctx >& hs_ctx) { const ReplicationMessageHeader* msg_header = r_cast< const ReplicationMessageHeader* >(header.cbytes()); @@ -236,21 +238,21 @@ ReplicationStateMachine::get_blk_alloc_hints(sisl::blob const& header, uint32_t void ReplicationStateMachine::on_start_replace_member(const std::string& task_id, const homestore::replica_member_info& member_out, const homestore::replica_member_info& member_in, trace_id_t tid) { - home_object_->on_pg_start_replace_member(repl_dev()->group_id(), task_id, member_out, member_in, tid); + home_object_->on_pg_start_replace_member(device()->group_id(), task_id, member_out, member_in, tid); } void ReplicationStateMachine::on_complete_replace_member(const std::string& task_id, const homestore::replica_member_info& member_out, const homestore::replica_member_info& member_in, trace_id_t tid) { - home_object_->on_pg_complete_replace_member(repl_dev()->group_id(), task_id, member_out, member_in, tid); + home_object_->on_pg_complete_replace_member(device()->group_id(), task_id, member_out, member_in, tid); } void ReplicationStateMachine::on_clean_replace_member_task(const std::string& task_id, const homestore::replica_member_info& member_out, const homestore::replica_member_info& member_in, trace_id_t tid) { - home_object_->on_pg_clean_replace_member_task(repl_dev()->group_id(), task_id, member_out, member_in, tid); + home_object_->on_pg_clean_replace_member_task(device()->group_id(), task_id, member_out, member_in, tid); } void ReplicationStateMachine::on_destroy(const homestore::group_id_t& group_id) { @@ -266,22 +268,22 @@ void ReplicationStateMachine::on_destroy(const homestore::group_id_t& group_id) } void ReplicationStateMachine::on_remove_member(const homestore::replica_id_t& member, trace_id_t tid) { - home_object_->on_remove_member(repl_dev()->group_id(), member, tid); + home_object_->on_remove_member(device()->group_id(), member, tid); } -homestore::AsyncReplResult<> +homestore::async_status ReplicationStateMachine::create_snapshot(std::shared_ptr< homestore::snapshot_context > context) { std::lock_guard lk(m_snapshot_lock); if (get_snapshot_context() != nullptr && context->get_lsn() < m_snapshot_context->get_lsn()) { LOGD("Skipping older snapshot context: group={}, requested_lsn={}, current_lsn={}", - boost::uuids::to_string(repl_dev()->group_id()), context->get_lsn(), m_snapshot_context->get_lsn()); - return folly::makeSemiFuture< homestore::ReplResult< folly::Unit > >(folly::Unit{}); + boost::uuids::to_string(device()->group_id()), context->get_lsn(), m_snapshot_context->get_lsn()); + co_return homestore::ok(); } - LOGI("Created resync snapshot context: group={}, lsn={}", boost::uuids::to_string(repl_dev()->group_id()), + LOGI("Created resync snapshot context: group={}, lsn={}", boost::uuids::to_string(device()->group_id()), context->get_lsn()); set_snapshot_context(context); - return folly::makeSemiFuture< homestore::ReplResult< folly::Unit > >(folly::Unit{}); + co_return homestore::ok(); } bool ReplicationStateMachine::apply_snapshot(std::shared_ptr< homestore::snapshot_context > context) { @@ -289,7 +291,7 @@ bool ReplicationStateMachine::apply_snapshot(std::shared_ptr< homestore::snapsho auto delay = iomgr_flip::instance()->get_test_flip< long >("simulate_apply_snapshot_delay"); if (delay) { LOGD("Simulating resync snapshot apply delay: group={}, delay_ms={}", - boost::uuids::to_string(repl_dev()->group_id()), delay.get()); + boost::uuids::to_string(device()->group_id()), delay.get()); std::this_thread::sleep_for(std::chrono::milliseconds(delay.get())); } // Currently, nuraft will pause state machine and resume it after the last snp obj is saved. So we don't need to @@ -299,7 +301,7 @@ bool ReplicationStateMachine::apply_snapshot(std::shared_ptr< homestore::snapsho std::lock_guard lk(m_snapshot_lock); set_snapshot_context(context); - LOGI("Applied resync snapshot: group={}, lsn={}", boost::uuids::to_string(repl_dev()->group_id()), + LOGI("Applied resync snapshot: group={}, lsn={}", boost::uuids::to_string(device()->group_id()), context->get_lsn()); return true; } @@ -316,12 +318,12 @@ int ReplicationStateMachine::read_snapshot_obj(std::shared_ptr< homestore::snaps std::lock_guard lk(m_snp_sync_ctx_lock); if (snp_obj->user_ctx == nullptr) { // Create the pg blob iterator for the first time. - pg_iter = std::make_shared< HSHomeObject::PGBlobIterator >(*home_object_, repl_dev()->group_id(), + pg_iter = std::make_shared< HSHomeObject::PGBlobIterator >(*home_object_, device()->group_id(), context->get_lsn()); auto pg_iter_ptr = new std::shared_ptr< HSHomeObject::PGBlobIterator >(pg_iter); snp_obj->user_ctx = static_cast< void* >(pg_iter_ptr); LOGD("Allocated PGBlobIterator: iterator={}, group={}, snapshot_lsn={}", snp_obj->user_ctx, - boost::uuids::to_string(repl_dev()->group_id()), context->get_lsn()); + boost::uuids::to_string(device()->group_id()), context->get_lsn()); } else { auto pg_iter_ptr = static_cast< std::shared_ptr< HSHomeObject::PGBlobIterator >* >(snp_obj->user_ctx); pg_iter = *pg_iter_ptr; @@ -340,7 +342,7 @@ int ReplicationStateMachine::read_snapshot_obj(std::shared_ptr< homestore::snaps // We use pg blob iterator to go over all the blobs in all the shards in that PG. // Once all the shards are done, follower will return next obj Id = LAST_OBJ_ID(ULLONG_MAX) as a end marker, // leader will stop sending the snapshot data. - auto log_str = fmt::format("group={}, lsn={}", uuids::to_string(repl_dev()->group_id()), context->get_lsn()); + auto log_str = fmt::format("group={}, lsn={}", uuids::to_string(device()->group_id()), context->get_lsn()); if (snp_obj->offset == LAST_OBJ_ID) { // No more shards to read, baseline resync is finished after this. snp_obj->is_last_obj = true; @@ -399,7 +401,7 @@ void ReplicationStateMachine::write_snapshot_obj(std::shared_ptr< homestore::sna std::shared_ptr< homestore::snapshot_obj > snp_obj) { RELEASE_ASSERT(context != nullptr, "Context null"); RELEASE_ASSERT(snp_obj != nullptr, "Snapshot data null"); - auto r_dev = repl_dev(); + auto r_dev = device(); if (!m_snp_rcv_handler) { m_snp_rcv_handler = std::make_unique< HSHomeObject::SnapshotReceiveHandler >(*home_object_, r_dev); if (m_snp_rcv_handler->load_prev_context_and_metrics()) { @@ -422,7 +424,7 @@ void ReplicationStateMachine::write_snapshot_obj(std::shared_ptr< homestore::sna // we only reset this if destroying pg happens in BR case. for other cases (on_destroy and _exit_pg), // since this replica will leave the PG and no later logs will be received, no need to reset this. reset_no_space_left_error_info(); - repl_dev()->reset_latch_lsn(); + device()->reset_latch_lsn(); LOGI("Completed resync snapshot write: {}", log_suffix); return; } @@ -554,7 +556,7 @@ void ReplicationStateMachine::write_snapshot_obj(std::shared_ptr< homestore::sna void ReplicationStateMachine::free_user_snp_ctx(void*& user_snp_ctx) { if (user_snp_ctx == nullptr) { - LOGE("User snapshot context null group={}", boost::uuids::to_string(repl_dev()->group_id())); + LOGE("User snapshot context null group={}", boost::uuids::to_string(device()->group_id())); return; } std::lock_guard lk(m_snp_sync_ctx_lock); @@ -569,9 +571,9 @@ void ReplicationStateMachine::free_user_snp_ctx(void*& user_snp_ctx) { std::shared_ptr< homestore::snapshot_context > ReplicationStateMachine::get_snapshot_context() { if (m_snapshot_context == nullptr) { // Try to load from snapshot superblk first - auto sb_data = home_object_->get_snapshot_sb_data(repl_dev()->group_id()); + auto sb_data = home_object_->get_snapshot_sb_data(device()->group_id()); if (sb_data.size() > 0) { - m_snapshot_context = repl_dev()->deserialize_snapshot_context(sb_data); + m_snapshot_context = device()->deserialize_snapshot_context(sb_data); LOGI("Loaded previous snapshot from superblk, lsn={}", m_snapshot_context->get_lsn()); } } @@ -579,164 +581,119 @@ std::shared_ptr< homestore::snapshot_context > ReplicationStateMachine::get_snap } void ReplicationStateMachine::set_snapshot_context(std::shared_ptr< homestore::snapshot_context > context) { - home_object_->update_snapshot_sb(repl_dev()->group_id(), context); + home_object_->update_snapshot_sb(device()->group_id(), context); m_snapshot_context = context; } -folly::Future< std::error_code > ReplicationStateMachine::on_fetch_data(const int64_t lsn, const sisl::blob& header, - const homestore::MultiBlkId& local_blk_id, - sisl::sg_list& sgs) { +sisl::async::task< iomgr::io_result > +ReplicationStateMachine::on_fetch_data(const int64_t lsn, const sisl::blob& header, + const homestore::multi_blk_id& local_blk_id, sisl::sg_list& sgs) { if (0 == header.size()) { LOGW("Header is empty in on_fetch_data for lsn {}", lsn); - return folly::makeFuture< std::error_code >(std::make_error_code(std::errc::invalid_argument)); + co_return std::unexpected(std::make_error_condition(std::errc::invalid_argument)); } - // the lsn here will mostly be -1 ,since this lsn has not been appeneded and thus get no lsn - // however, there is a corner case that fetch_data happens after push_data is received and log is appended. in - // this case, lsn will be the corresponding lsn. - const ReplicationMessageHeader* msg_header = r_cast< const ReplicationMessageHeader* >(header.cbytes()); if (msg_header->corrupted()) { LOGW("replication message header is corrupted with crc error, lsn={}, header={}", lsn, msg_header->to_string()); - return folly::makeFuture< std::error_code >(std::make_error_code(std::errc::bad_message)); + co_return std::unexpected(std::make_error_condition(std::errc::bad_message)); } LOGD("fetch data with lsn={}, msg type={}", lsn, msg_header->msg_type); - // for nuobject case, we can make this assumption, since we use append_blk_allocator. RELEASE_ASSERT(sgs.iovs.size() == 1, "sgs iovs size should be 1, lsn={}, msg_type={}", lsn, msg_header->msg_type); - auto const total_size = local_blk_id.blk_count() * repl_dev()->get_blk_size(); + auto const total_size = local_blk_id.blk_count() * device()->get_blk_size(); RELEASE_ASSERT(total_size == sgs.size, "total_blk_size does not match, lsn={}, msg_type={}, expected size={}, given buffer size={}", lsn, msg_header->msg_type, total_size, sgs.size); auto given_buffer = (uint8_t*)(sgs.iovs[0].iov_base); std::memset(given_buffer, 0, total_size); - // in homeobject, we have only have one kind of requests that will write data(thus fetch_data might happen) to a - // chunk: put_blob: will write user data to a chunk - - // for any type that writes data to a chunk, we need to handle the fetch_data request for it. switch (msg_header->msg_type) { case ReplicationMessageType::PUT_BLOB_MSG: { - const auto blob_id = msg_header->blob_id; const auto shard_id = msg_header->shard_id; LOGD("fetch data with blob_id={}, shard=0x{:x}", blob_id, shard_id); - // we first try to read data according to the local_blk_id to see if it matches the blob_id - return std::move(homestore::data_service().async_read(local_blk_id, given_buffer, total_size)) - .thenValue([this, lsn, blob_id, shard_id, given_buffer, total_size](auto&& err) { - // io error - if (err) { - LOGE("FetchData fails to read blob, lsn={}, blob_id={}, shard_id={}, err_value={}, error={}", lsn, - blob_id, shard_id, err.value(), err.message()); - throw std::system_error(err); - } - - // folly future has no machenism to bypass the later thenValue in the then value chain. so for all - // the case that no need to schedule the later async_read, we throw a system_error with no error - // code to bypass the next thenValue. - - // if data matches - if (home_object_->verify_blob(given_buffer, shard_id, blob_id)) { - LOGD("local_blk_id matches blob data, lsn={}, blob_id={}, shard_id={}", lsn, blob_id, shard_id); - throw std::system_error(std::error_code{}); - } - - // if data does not match, try to read data according to the index table. this might happen if the - // chunk has once been gc. - pg_id_t pg_id = shard_id >> homeobject::shard_width; - auto hs_pg = home_object_->get_hs_pg(pg_id); - if (!hs_pg) { - LOGE("pg not found for pg={}, shardID=0x{:x}", pg_id, shard_id); - // TODO: use a proper error code. - throw std::system_error(std::make_error_code(std::errc::bad_address)); - } - const auto& index_table = hs_pg->index_table_; - - BlobRouteKey index_key{BlobRoute{shard_id, blob_id}}; - BlobRouteValue index_value; - homestore::BtreeSingleGetRequest get_req{&index_key, &index_value}; - - LOGD("fetch data with blob_id={}, shardID=0x{:x}, pg={} from index table", blob_id, shard_id, pg_id); - - bool should_return_delete_marker{false}; - homestore::MultiBlkId pbas; - - auto rc = index_table->get(get_req); - if (sisl_unlikely(homestore::btree_status_t::success != rc)) { - // blob never exists or has been gc - LOGD("on_fetch_data: failed to get from index table, blob never exists or has been gc, blob_id={}, " - "shardID=0x{:x}, pg={}", - blob_id, shard_id, pg_id); - should_return_delete_marker = true; - } else { - pbas = index_value.pbas(); - if (sisl_unlikely(pbas == HSHomeObject::tombstone_pbas)) { - LOGD("on_fetch_data: got tombstone pba for blob_id={}, shardID=0x{:x}, pg={}", blob_id, - shard_id, pg_id); - should_return_delete_marker = true; - } - } - - if (should_return_delete_marker) { - LOGD("on_fetch_data: return delete marker for blob_id={}, shardID=0x{:x}, pg={}", blob_id, shard_id, - pg_id); - std::memset(given_buffer, 0, total_size); - RELEASE_ASSERT(HSHomeObject::delete_marker_blob_data.size() <= total_size, - "delete marker blob size is larger than total_size"); - std::memcpy(given_buffer, HSHomeObject::delete_marker_blob_data.data(), - HSHomeObject::delete_marker_blob_data.size()); - - throw std::system_error(std::error_code{}); - } - - RELEASE_ASSERT(pbas.blk_count() * repl_dev()->get_blk_size() == total_size, - "pbas blk size does not match total_size"); - - LOGD("on_fetch_data: read data with blob_id={}, shardID=0x{:x}, pg={} from pbas={}", blob_id, shard_id, - pg_id, pbas.to_string()); - - return homestore::data_service().async_read(pbas, given_buffer, total_size); - }) - .thenValue([this, lsn, blob_id, shard_id, given_buffer, total_size](auto&& err) { - // io error - if (err) throw std::system_error(err); - // if data matches - if (home_object_->verify_blob(given_buffer, shard_id, blob_id)) { - LOGD("pba matches blob data, lsn={}, blob_id={}, shardID=0x{:x}, pg={}", lsn, blob_id, shard_id, - (shard_id >> homeobject::shard_width)); - return std::error_code{}; - } else { - // there is a scenario that the chunk is gced after we get the pba, but before we schecdule - // the read. we can try to read the index table and read data again, but for the simlicity - // here, we just return error, and let follower to retry fetch data. - return std::make_error_code(std::errc::resource_unavailable_try_again); - } - }) - .thenError< std::system_error >([blob_id, shard_id](const std::system_error& e) { - auto ec = e.code(); - - if (!ec) { - // if no error code, we come to here, which means the data is valid or no need to read data - // again. - LOGD("blob valid, blob_id={}, shardID=0x{:x}, pg={}", blob_id, shard_id, - (shard_id >> homeobject::shard_width)); - } else { - // if any error happens, we come to here - LOGE("IO error happens when reading data for blob_id={}, shardID=0x{:x}, pg={}, error={}", blob_id, - shard_id, (shard_id >> homeobject::shard_width), e.what()); - } - - return ec; - }); + + auto err = co_await homestore::data_service().async_read(local_blk_id, given_buffer, total_size); + if (!err) { + LOGE("FetchData fails to read blob, lsn={}, blob_id={}, shard_id={}, error={}", lsn, blob_id, shard_id, + err.error()); + co_return std::unexpected(err.error()); + } + + if (home_object_->verify_blob(given_buffer, shard_id, blob_id)) { + LOGD("local_blk_id matches blob data, lsn={}, blob_id={}, shard_id={}", lsn, blob_id, shard_id); + co_return err; + } + + pg_id_t pg_id = shard_id >> homeobject::shard_width; + auto hs_pg = home_object_->get_hs_pg(pg_id); + if (!hs_pg) { + LOGE("pg not found for pg={}, shardID=0x{:x}", pg_id, shard_id); + co_return std::unexpected(std::make_error_condition(std::errc::bad_address)); + } + const auto& index_table = hs_pg->index_table_; + + BlobRouteKey index_key{BlobRoute{shard_id, blob_id}}; + BlobRouteValue index_value; + homestore::BtreeSingleGetRequest get_req{&index_key, &index_value}; + + LOGD("fetch data with blob_id={}, shardID=0x{:x}, pg={} from index table", blob_id, shard_id, pg_id); + + bool should_return_delete_marker{false}; + homestore::multi_blk_id pbas; + + auto rc = index_table->get(get_req); + if (sisl_unlikely(homestore::btree_status_t::success != rc)) { + LOGD("on_fetch_data: failed to get from index table, blob never exists or has been gc, blob_id={}, " + "shardID=0x{:x}, pg={}", + blob_id, shard_id, pg_id); + should_return_delete_marker = true; + } else { + pbas = index_value.pbas(); + if (sisl_unlikely(pbas == HSHomeObject::tombstone_pbas)) { + LOGD("on_fetch_data: got tombstone pba for blob_id={}, shardID=0x{:x}, pg={}", blob_id, shard_id, + pg_id); + should_return_delete_marker = true; + } + } + + if (should_return_delete_marker) { + LOGD("on_fetch_data: return delete marker for blob_id={}, shardID=0x{:x}, pg={}", blob_id, shard_id, pg_id); + std::memset(given_buffer, 0, total_size); + RELEASE_ASSERT(HSHomeObject::delete_marker_blob_data.size() <= total_size, + "delete marker blob size is larger than total_size"); + std::memcpy(given_buffer, HSHomeObject::delete_marker_blob_data.data(), + HSHomeObject::delete_marker_blob_data.size()); + co_return total_size; + } + + RELEASE_ASSERT(pbas.blk_count() * device()->get_blk_size() == total_size, + "pbas blk size does not match total_size"); + + LOGD("on_fetch_data: read data with blob_id={}, shardID=0x{:x}, pg={} from pbas={}", blob_id, shard_id, pg_id, + pbas.to_string()); + + err = co_await homestore::data_service().async_read(pbas, given_buffer, total_size); + if (!err) { + LOGE("IO error reading pbas for blob_id={}, shardID=0x{:x}, pg={}, error={}", blob_id, shard_id, pg_id, + err.error()); + co_return std::unexpected(err.error()); + } + if (home_object_->verify_blob(given_buffer, shard_id, blob_id)) { + LOGD("pba matches blob data, lsn={}, blob_id={}, shardID=0x{:x}, pg={}", lsn, blob_id, shard_id, pg_id); + co_return err; + } + co_return std::unexpected(std::make_error_condition(std::errc::resource_unavailable_try_again)); } default: { LOGW("msg type={}, should not happen in fetch_data rpc", msg_header->msg_type); - return folly::makeFuture< std::error_code >(std::make_error_code(std::errc::operation_not_supported)); + co_return std::unexpected(std::make_error_condition(std::errc::operation_not_supported)); } } } @@ -888,26 +845,25 @@ void ReplicationStateMachine::handle_no_space_left(homestore::repl_lsn_t lsn, ho LOGW("start handling no_space_left error for chunk_id={} , lsn={}", chunk_id, lsn); // 1 drain all the pending requests and refuse later coming new requests for repl_dev, so that no new block can // be allocated from now on. - repl_dev()->quiesce_reqs(); + device()->quiesce_reqs(); // 2 clear all the in-memory rreqs that already allocated blocks on the chunk. - repl_dev()->clear_chunk_req(chunk_id); + device()->clear_chunk_req(chunk_id); // 3 handling this error. for homeobject, we will submit an emergent gc task and wait for the completion. auto gc_mgr = home_object_->gc_manager(); - gc_mgr->submit_gc_task(task_priority::emergent, chunk_id) - .via(&folly::InlineExecutor::instance()) - .thenValue([this, lsn, chunk_id](auto&& res) { + sisl::async::detach_then( + gc_mgr->submit_gc_task(task_priority::emergent, chunk_id), [this, lsn, chunk_id](bool res) { if (!res) { - LOGERROR("failed to submit emergent gc task for chunk_id={} , lsn={}, will retry again if new " - "no_space_left happens", + LOGERROR("failed to submit emergent gc task for chunk_id={} , lsn={}, will retry " + "again if new no_space_left happens", chunk_id, lsn); } else { LOGD("successfully handle no_space_left error for chunk_id={} , lsn={}", chunk_id, lsn); } // start accepting new requests again. - repl_dev()->resume_accepting_reqs(); + device()->resume_accepting_reqs(); }); } diff --git a/src/lib/homestore_backend/replication_state_machine.hpp b/src/lib/homestore_backend/replication_state_machine.hpp index 75d2d4187..b2b8eaa4e 100644 --- a/src/lib/homestore_backend/replication_state_machine.hpp +++ b/src/lib/homestore_backend/replication_state_machine.hpp @@ -1,13 +1,12 @@ #pragma once -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuninitialized" -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#pragma GCC diagnostic pop -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "hs_homeobject.hpp" #include "replication_message.hpp" @@ -22,7 +21,7 @@ struct ho_repl_ctx : public homestore::repl_req_ctx { sisl::io_blob_safe key_buf_; // Data bufs corresponding to data_sgs_. Since data_sgs are raw pointers, we need to keep the data bufs alive - folly::small_vector< sisl::io_blob_safe, 3 > data_bufs_; + boost::container::small_vector< sisl::io_blob_safe, 3 > data_bufs_; sisl::sg_list data_sgs_; ho_repl_ctx(uint32_t hdr_extn_size, uint32_t key_size = 0) : homestore::repl_req_ctx{} { @@ -75,7 +74,7 @@ struct ho_repl_ctx : public homestore::repl_req_ctx { template < typename T > struct repl_result_ctx : public ho_repl_ctx { - folly::Promise< T > promise_; + sisl::async::value_awaitable< T > done_; template < typename... Args > static intrusive< repl_result_ctx< T > > make(Args&&... args) { @@ -83,10 +82,15 @@ struct repl_result_ctx : public ho_repl_ctx { } repl_result_ctx(uint32_t hdr_extn_size, uint32_t key_size = 0) : ho_repl_ctx{hdr_extn_size, key_size} {} - folly::SemiFuture< T > result() { return promise_.getSemiFuture(); } + + sisl::async::task< T > result() { co_return co_await sisl::async::await_value_ref(done_); } + + void set_ok() { done_.complete(T{std::monostate{}}); } // NullResult only + void set_ok(typename T::value_type v) { done_.complete(T{std::move(v)}); } + void set_err(typename T::error_type e) { done_.complete(T{std::unexpected(std::move(e))}); } }; -class ReplicationStateMachine : public homestore::ReplDevListener { +class ReplicationStateMachine : public homestore::repl_dev_listener { public: explicit ReplicationStateMachine(HSHomeObject* home_object) : home_object_(home_object) {} @@ -104,74 +108,26 @@ class ReplicationStateMachine : public homestore::ReplDevListener { /// @param ctx - Context passed as part of the replica_set::write() api /// void on_commit(int64_t lsn, sisl::blob const& header, sisl::blob const& key, - std::vector< homestore::MultiBlkId > const& blkids, + std::vector< homestore::multi_blk_id > const& blkids, cintrusive< homestore::repl_req_ctx >& ctx) override; /// @brief Called when the log entry has been received by the replica dev. - /// - /// On recovery, this is called from a random worker thread before the raft server is started. It is - /// guaranteed to be serialized in log index order. - /// - /// On the leader, this is called from the same thread that replica_set::write() was called. - /// - /// On the follower, this is called when the follower has received the log entry. It is guaranteed to be serialized - /// in log sequence order. - /// - /// NOTE: Listener can choose to ignore this pre commit, however, typical use case of maintaining this is in-case - /// replica set needs to support strong consistent reads and follower needs to ignore any keys which are not being - /// currently in pre-commit, but yet to be committed. - /// - /// @param lsn - The log sequence number - - /// @param header - Header originally passed with replica_set::write() api - /// @param key - Key originally passed with replica_set::write() api - /// @param ctx - User contenxt passed as part of the replica_set::write() api bool on_pre_commit(int64_t lsn, const sisl::blob& header, const sisl::blob& key, cintrusive< homestore::repl_req_ctx >& ctx) override; /// @brief Called when the log entry has been rolled back by the replica set. - /// - /// This function is called on followers only when the log entry is going to be overwritten. This function is called - /// from a random worker thread, but is guaranteed to be serialized. - /// - /// For each log index, it is guaranteed that either on_commit() or on_rollback() is called but not both. - /// - /// NOTE: Listener should do the free any resources created as part of pre-commit. - /// - /// @param lsn - The log sequence number getting rolled back - /// @param header - Header originally passed with replica_set::write() api - /// @param key - Key originally passed with replica_set::write() api - /// @param ctx - User contenxt passed as part of the replica_set::write() api void on_rollback(int64_t lsn, const sisl::blob& header, const sisl::blob& key, cintrusive< homestore::repl_req_ctx >& ctx) override; /// @brief Called when the raft service is created after restart. - /// - /// homeobject should recover all the necessary components to serve log replay/commit requests. void on_restart() override; /// @brief Called when the async_alloc_write call failed to initiate replication - /// - /// Called only on the node which called async_alloc_write - /// - /// - /// NOTE: Listener should do the free any resources created as part of pre-commit. - /// - /// @param header - Header originally passed with ReplDev::async_alloc_write() api - /// @param key - Key originally passed with ReplDev::async_alloc_write() api - /// @param ctx - Context passed as part of the ReplDev::async_alloc_write() api void on_error(ReplServiceError error, const sisl::blob& header, const sisl::blob& key, cintrusive< repl_req_ctx >& ctx); /// @brief Called when replication module is trying to allocate a block to write the value - /// - /// This function can be called both on leader and follower when it is trying to allocate a block to write the - /// value. Caller is expected to provide hints for allocation based on the header supplied as part of original - /// write. In cases where caller don't care about the hints can return default blk_alloc_hints. - /// - /// @param header Header originally passed with repl_dev::write() api on the leader - /// @return Expected to return blk_alloc_hints for this write - homestore::ReplResult< homestore::blk_alloc_hints > + homestore::result< homestore::blk_alloc_hints > get_blk_alloc_hints(sisl::blob const& header, uint32_t data_size, cintrusive< homestore::repl_req_ctx >& hs_ctx) override; @@ -194,7 +150,7 @@ class ReplicationStateMachine : public homestore::ReplDevListener { void on_remove_member(const homestore::replica_id_t& member, trace_id_t tid) override; // Snapshot related functions - homestore::AsyncReplResult<> create_snapshot(std::shared_ptr< homestore::snapshot_context > context) override; + homestore::async_status create_snapshot(std::shared_ptr< homestore::snapshot_context > context) override; bool apply_snapshot(std::shared_ptr< homestore::snapshot_context > context) override; std::shared_ptr< homestore::snapshot_context > last_snapshot() override; int read_snapshot_obj(std::shared_ptr< homestore::snapshot_context > context, @@ -204,48 +160,26 @@ class ReplicationStateMachine : public homestore::ReplDevListener { void free_user_snp_ctx(void*& user_snp_ctx) override; /// @brief ask upper layer to decide which data should be returned. - // @param header - header of the log entry. - // @param blkid - original blkid of the log entry - // @param sgs - sgs to be filled with data - // @param lsn - lsn of the log entry - folly::Future< std::error_code > on_fetch_data(const int64_t lsn, const sisl::blob& header, - const homestore::MultiBlkId& local_blk_id, - sisl::sg_list& sgs) override; + sisl::async::task< iomgr::io_result > on_fetch_data(const int64_t lsn, const sisl::blob& header, + const homestore::multi_blk_id& local_blk_id, + sisl::sg_list& sgs) override; /// @brief ask upper layer to handle no_space_left event - // @param lsn - on which repl_lsn no_space_left happened - // @param header - on which header no_space_left happened when trying to allocate blk void on_no_space_left(homestore::repl_lsn_t lsn, sisl::blob const& header) override; /// @brief Called when the config log entry has been rolled backed. - /// - /// This function is called on followers only when the log entry is going to be overwritten. This function is called - /// from a random worker thread, but is guaranteed to be serialized. - /// - /// For each config log index, it is guaranteed that either on_config_commit() or on_config_rollback() is called but - /// not both. - /// @param lsn - The log sequence number of the rollbacked config log entry void on_config_rollback(int64_t lsn) override; /// @brief periodically called to notify the lastest committed lsn to the listener. - /// NOTE: this callback will block the thread of flushing the latest committed lsn into repl_dev superblk as DC_LSN, - /// pls take care if there is any heavy or blocking operation in this callback. - /// - /// @param lsn - The lasted committed log sequence number so far - /// void notify_committed_lsn(int64_t lsn) override; /// @brief this is called after all the logs are replayed but before joining raft group. - /// @param group_id - the group , where all the logs are replayed but not join raft group - /// void on_log_replay_done(const homestore::group_id_t& group_id) override; /// @brief this is called when this node becomes leader for the group - /// @param group_id - the group , where all the logs are replayed but not join raft group virtual void on_become_leader(const homestore::group_id_t& group_id) override; /// @brief this is called when this node becomes follower for the group - /// @param group_id - the group , where all the logs are replayed but not join raft group virtual void on_become_follower(const homestore::group_id_t& group_id) override; private: @@ -277,7 +211,7 @@ class ReplicationStateMachine : public homestore::ReplDevListener { std::pair< homestore::repl_lsn_t, homestore::chunk_num_t > get_no_space_left_error_info() const; - void handle_no_space_left(homestore ::repl_lsn_t lsn, homestore ::chunk_num_t chunk_id); + void handle_no_space_left(homestore::repl_lsn_t lsn, homestore::chunk_num_t chunk_id); }; } // namespace homeobject diff --git a/src/lib/homestore_backend/scrub_manager.cpp b/src/lib/homestore_backend/scrub_manager.cpp index 58086b70f..5f637a610 100644 --- a/src/lib/homestore_backend/scrub_manager.cpp +++ b/src/lib/homestore_backend/scrub_manager.cpp @@ -23,18 +23,18 @@ class ScrubManager::PGScrubContext { PGScrubContext(uint64_t task_id, const HSHomeObject::HS_PG* hs_pg); ~PGScrubContext() = default; - folly::Future< bool > scrub_meta_batch(std::shared_ptr< ScrubManager::MetaScrubReport > scrub_report, - shard_id_t start_shard_id, shard_id_t end_shard_id, blob_id_t last_blob_id, - int64_t scrub_lsn, - std::map< shard_id_t, uint32_t >& shard_blob_count_in_batch); + sisl::async::task< bool > scrub_meta_batch(std::shared_ptr< ScrubManager::MetaScrubReport > scrub_report, + shard_id_t start_shard_id, shard_id_t end_shard_id, + blob_id_t last_blob_id, int64_t scrub_lsn, + std::map< shard_id_t, uint32_t >& shard_blob_count_in_batch); - folly::Future< bool > scrub_blob_batch(std::shared_ptr< ScrubManager::ShallowScrubReport > scrub_report, - shard_id_t start_shard_id, shard_id_t end_shard_id, blob_id_t last_blob_id, - int64_t scrub_lsn, bool is_deep_scrub); + sisl::async::task< bool > scrub_blob_batch(std::shared_ptr< ScrubManager::ShallowScrubReport > scrub_report, + shard_id_t start_shard_id, shard_id_t end_shard_id, + blob_id_t last_blob_id, int64_t scrub_lsn, bool is_deep_scrub); void reconcile_scrub_report(std::shared_ptr< ScrubManager::ShallowScrubReport > scrub_report); - folly::Future< bool > check_existence_in_peer(peer_id_t peer_id, BlobRoute blob, bool check_blob); + sisl::async::task< bool > check_existence_in_peer(peer_id_t peer_id, BlobRoute blob, bool check_blob); void handle_scrub_req_resp(std::shared_ptr< ScrubManager::scrub_result > result); uint64_t random_req_id() const; @@ -49,9 +49,7 @@ class ScrubManager::PGScrubContext { for (auto& [peer_id, peer_ctx] : m_active_batch) { std::lock_guard lock(peer_ctx->mutex); - if (!peer_ctx->promise.isFulfilled()) { - peer_ctx->promise.setException(folly::make_exception_wrapper< std::runtime_error >("scrub cancelled")); - } + if (peer_ctx->promise && !peer_ctx->promise->await_ready()) { peer_ctx->promise->complete(nullptr); } } SCRUBLOGI(pg_id, task_id, "scrub task is cancelled"); } @@ -67,18 +65,17 @@ class ScrubManager::PGScrubContext { std::shared_ptr< range_scrub_result > batch_scrub_result; uint8_t retry_count{0}; std::chrono::steady_clock::time_point last_sent_at{std::chrono::steady_clock::now()}; - folly::Promise< std::shared_ptr< range_scrub_result > > promise; + std::shared_ptr< sisl::async::value_awaitable< std::shared_ptr< range_scrub_result > > > promise; mutable std::mutex mutex; - // Initialize promise to a fulfilled sentinel so that cancel() and check_scrub_timeouts() - // can safely call isFulfilled() / setException() before the first reset() is called. + // Initialize promise to a completed sentinel so that cancel() and check_scrub_timeouts() + // can safely call await_ready() / complete() before the first reset() is called. PeerBatchScrubCtx() { - auto [p, f] = folly::makePromiseContract< std::shared_ptr< range_scrub_result > >(); - promise = std::move(p); - promise.setValue(nullptr); + promise = std::make_shared< sisl::async::value_awaitable< std::shared_ptr< range_scrub_result > > >(); + promise->complete(nullptr); } - folly::Future< std::shared_ptr< range_scrub_result > > + std::shared_ptr< sisl::async::value_awaitable< std::shared_ptr< range_scrub_result > > > reset(scrub_req req, std::shared_ptr< range_scrub_result > init_batch_scrub_result, const std::atomic_bool& cancelled) { std::lock_guard lock(mutex); @@ -86,12 +83,9 @@ class ScrubManager::PGScrubContext { batch_scrub_result = std::move(init_batch_scrub_result); retry_count = 0; last_sent_at = std::chrono::steady_clock::now(); - auto [p, f] = folly::makePromiseContract< std::shared_ptr< range_scrub_result > >(); - promise = std::move(p); - if (cancelled.load()) { - promise.setException(folly::make_exception_wrapper< std::runtime_error >("scrub cancelled")); - } - return std::move(f).via(folly::getGlobalIOExecutor()); + promise = std::make_shared< sisl::async::value_awaitable< std::shared_ptr< range_scrub_result > > >(); + if (cancelled.load()) { promise->complete(nullptr); } + return promise; } }; @@ -121,16 +115,18 @@ ScrubManager::~ScrubManager() { stop(); } void ScrubManager::check_scrub_timeouts() { // TODO: make timeout and max_retries configurable - static constexpr auto timeout = std::chrono::seconds{10}; - static constexpr uint8_t max_retries = 5; + constexpr auto timeout = std::chrono::seconds{10}; + constexpr uint8_t max_retries = 5; - for (auto const& [pg_id, scrub_ctx] : m_pg_scrub_ctx_map) { - if (scrub_ctx->cancelled.load()) { continue; } + m_pg_scrub_ctx_map.visit_all([&, timeout, max_retries](auto const& entry) { + auto const& pg_id = entry.first; + auto const& scrub_ctx = entry.second; + if (scrub_ctx->cancelled.load()) { return; } const auto task_id = scrub_ctx->task_id; // m_active_batch keys are fixed after construction, iteration without lock is safe. for (auto const& [peer_id, peer_batch_scrub_ctx] : scrub_ctx->m_active_batch) { std::unique_lock lock(peer_batch_scrub_ctx->mutex); - if (peer_batch_scrub_ctx->promise.isFulfilled()) { + if (!peer_batch_scrub_ctx->promise || peer_batch_scrub_ctx->promise->await_ready()) { SCRUBLOGD(pg_id, task_id, "batch scrub for peer {} is not active, skipping timeout check", peer_id); continue; } @@ -155,49 +151,54 @@ void ScrubManager::check_scrub_timeouts() { lock.unlock(); scrub_ctx->send_req_to_peer(next_req, peer_id); } - } + }); } void ScrubManager::scan_pg_for_scrub() { - for (auto const& [pg_id, _] : m_pg_scrub_sb_map) { + bool submitted = false; + m_pg_scrub_sb_map.visit_all([this, &submitted](auto const& entry) { + if (submitted) { return; } + const auto pg_id = entry.first; if (is_eligible_for_deep_scrub(pg_id)) { LOGINFOMOD(scrubmgr, "pg={} is eligible for deep scrub, submit scrub task", pg_id); - submit_scrub_task(pg_id, true) - .via(folly::getGlobalIOExecutor()) - .thenValue([this, pg_id](std::shared_ptr< ShallowScrubReport > report) { - if (!report) { - LOGERRORMOD(scrubmgr, "deep scrub failed for pg={}", pg_id); - return; - } - LOGINFOMOD(scrubmgr, "deep scrub is completed for pg={}", pg_id); - auto deep_report = std::dynamic_pointer_cast< DeepScrubReport >(report); - if (!deep_report) { - LOGERRORMOD(scrubmgr, "report for deep scrub cannot be casted to DeepScrubReport for pg={}", - pg_id); - return; - } - handle_deep_pg_scrub_report(std::move(deep_report)); - }); + sisl::async::detach_then(submit_scrub_task(pg_id, true), + [this, pg_id](std::shared_ptr< ShallowScrubReport > report) { + if (!report) { + LOGERRORMOD(scrubmgr, "deep scrub failed for pg={}", pg_id); + return; + } + LOGINFOMOD(scrubmgr, "deep scrub is completed for pg={}", pg_id); + auto deep_report = std::dynamic_pointer_cast< DeepScrubReport >(report); + if (!deep_report) { + LOGERRORMOD(scrubmgr, + "report for deep scrub cannot be casted to DeepScrubReport " + "for pg={}", + pg_id); + return; + } + handle_deep_pg_scrub_report(std::move(deep_report)); + }); + submitted = true; return; } if (is_eligible_for_shallow_scrub(pg_id)) { LOGINFOMOD(scrubmgr, "pg={} is eligible for shallow scrub, submit scrub task", pg_id); - submit_scrub_task(pg_id, false) - .via(folly::getGlobalIOExecutor()) - .thenValue([this, pg_id](std::shared_ptr< ShallowScrubReport > report) { - if (!report) { - LOGERRORMOD(scrubmgr, "shallow scrub failed for pg={}", pg_id); - return; - } - LOGINFOMOD(scrubmgr, "shallow scrub is completed for pg={}", pg_id); - handle_shallow_pg_scrub_report(std::move(report)); - }); + sisl::async::detach_then(submit_scrub_task(pg_id, false), + [this, pg_id](std::shared_ptr< ShallowScrubReport > report) { + if (!report) { + LOGERRORMOD(scrubmgr, "shallow scrub failed for pg={}", pg_id); + return; + } + LOGINFOMOD(scrubmgr, "shallow scrub is completed for pg={}", pg_id); + handle_shallow_pg_scrub_report(std::move(report)); + }); + submitted = true; return; } LOGDEBUGMOD(scrubmgr, "pg={} is not eligible for any scrubbing", pg_id); - } + }); } void ScrubManager::handle_shallow_pg_scrub_report(std::shared_ptr< ShallowScrubReport > report) { @@ -235,9 +236,9 @@ void ScrubManager::start() { // TODO :: make thread count configurable, thread number is the most concurrent scrub tasks that can be handled // concurrently. Too many concurrent scrub tasks may bring too much pressure to the node const auto most_concurrent_scrub_task_num = 2; - m_scrub_executor = std::make_shared< folly::IOThreadPoolExecutor >(most_concurrent_scrub_task_num); + m_scrub_executor = std::make_shared< boost::asio::thread_pool >(most_concurrent_scrub_task_num); for (int i = 0; i < most_concurrent_scrub_task_num; ++i) { - m_scrub_executor->add([this]() { + boost::asio::post(*m_scrub_executor, [this]() { while (true) { // if no available scrub task, it will be blocked here. auto pop_result = m_scrub_task_queue.pop(); @@ -259,10 +260,11 @@ void ScrubManager::start() { const auto most_concurrent_scrub_req_num = 2; // we don't set priority for req as that of task, only control the concurrency to not bring too much io/cpu pressure // to this node. - m_scrub_req_executor = std::make_shared< folly::IOThreadPoolExecutor >(most_concurrent_scrub_req_num); + m_scrub_req_executor = std::make_shared< boost::asio::thread_pool >(most_concurrent_scrub_req_num); - iomanager.run_on_wait(iomgr::reactor_regex::random_worker, [&]() { - m_scrub_timer_fiber = iomanager.iofiber_self(); + auto* reactor = m_hs_home_object->bg_timer_reactor(); + RELEASE_ASSERT(reactor, "bg timer reactor is not started"); + iomanager.run_on_wait(reactor, [&]() { // TODO: make the interval configurable, for now set it to 60 seconds m_scrub_timer_hdl = iomanager.schedule_thread_timer(60ull * 1000 * 1000 * 1000, true, nullptr /*cookie*/, [this](void*) { scan_pg_for_scrub(); }); @@ -275,43 +277,33 @@ void ScrubManager::start() { void ScrubManager::stop() { LOGINFOMOD(scrubmgr, "stop scrub scheduler timer"); - if (m_scrub_timer_fiber) { - iomanager.run_on_wait(m_scrub_timer_fiber, [&]() { + if (auto* reactor = m_hs_home_object->bg_timer_reactor(); reactor != nullptr) { + iomanager.run_on_wait(reactor, [&]() { if (m_scrub_timer_hdl != iomgr::null_timer_handle) { iomanager.cancel_timer(m_scrub_timer_hdl, true); m_scrub_timer_hdl = iomgr::null_timer_handle; } - if (m_retry_timer_hdl != iomgr::null_timer_handle) { iomanager.cancel_timer(m_retry_timer_hdl, true); m_retry_timer_hdl = iomgr::null_timer_handle; } }); - m_scrub_timer_fiber = nullptr; } // cancel all the running scrub tasks and clear the scrub task queue. // TODO:: add a stopped flag to avoid adding new scrub task if stopped. if (!m_scrub_task_queue.is_closed()) { m_scrub_task_queue.close(); } - for (auto& [_, pg_scrub_ctx] : m_pg_scrub_ctx_map) { - pg_scrub_ctx->cancel(); - } + m_pg_scrub_ctx_map.visit_all([](auto const& entry) { entry.second->cancel(); }); - if (m_scrub_executor) { - m_scrub_executor->stop(); - m_scrub_executor.reset(); - } - if (m_scrub_req_executor) { - m_scrub_req_executor->stop(); - m_scrub_req_executor.reset(); - } + if (m_scrub_executor) { m_scrub_executor.reset(); } + if (m_scrub_req_executor) { m_scrub_req_executor.reset(); } LOGINFOMOD(scrubmgr, "scrub manager stopped!"); } void ScrubManager::add_scrub_req(std::shared_ptr< scrub_req > req) { LOGDEBUGMOD(scrubmgr, "receive scrub req: {}", req->to_string()); if (m_scrub_req_executor) { - m_scrub_req_executor->add([this, req = std::move(req)]() { handle_scrub_req(req); }); + boost::asio::post(*m_scrub_req_executor, [this, req = std::move(req)]() { handle_scrub_req(req); }); return; } @@ -319,13 +311,13 @@ void ScrubManager::add_scrub_req(std::shared_ptr< scrub_req > req) { } void ScrubManager::handle_scrub_req_resp(const pg_id_t pg_id, std::shared_ptr< scrub_result > result) { - auto pg_scrub_ctx_it = m_pg_scrub_ctx_map.find(pg_id); - if (pg_scrub_ctx_it == m_pg_scrub_ctx_map.end()) { + std::shared_ptr< PGScrubContext > scrub_ctx; + if (!m_pg_scrub_ctx_map.cvisit(pg_id, [&](auto const& entry) { scrub_ctx = entry.second; })) { LOGERRORMOD(scrubmgr, "cannot find scrub context for pg_id={}, fail to add scrub result!", pg_id); return; } - pg_scrub_ctx_it->second->handle_scrub_req_resp(std::move(result)); + scrub_ctx->handle_scrub_req_resp(std::move(result)); } void ScrubManager::handle_scrub_req(std::shared_ptr< scrub_req > req) { @@ -388,12 +380,12 @@ void ScrubManager::handle_scrub_req(std::shared_ptr< scrub_req > req) { sisl::io_blob_list_t blob_list; blob_list.emplace_back(flatbuffer.data(), flatbuffer.size(), false); // no need to retry, leader will handle retries - pg_repl_dev->data_request_unidirectional(remote_peer_id, HSHomeObject::PUSH_SCRUB_RESULT, blob_list) - .via(folly::getGlobalIOExecutor()) - .thenValue([pg_id, remote_peer_id, flatbuffer = std::move(flatbuffer), scrub_type](auto&& response) { - if (response.hasError()) { + sisl::async::detach_then( + pg_repl_dev->data_request_unidirectional(remote_peer_id, HSHomeObject::PUSH_SCRUB_RESULT, blob_list), + [pg_id, remote_peer_id, flatbuffer = std::move(flatbuffer), scrub_type](auto&& response) { + if (!response) { LOGERRORMOD(scrubmgr, "failed to send scrub result to peer {} in pg {}, scrub_type:{}, error={}", - remote_peer_id, pg_id, scrub_type, response.error()); + remote_peer_id, pg_id, scrub_type, response.error().message()); return; } @@ -402,7 +394,7 @@ void ScrubManager::handle_scrub_req(std::shared_ptr< scrub_req > req) { }); } -bool ScrubManager::wait_for_scrub_lsn_commit(shared< homestore::ReplDev > repl_dev, int64_t scrub_lsn) { +bool ScrubManager::wait_for_scrub_lsn_commit(shared< homestore::repl_dev > repl_dev, int64_t scrub_lsn) { if (!repl_dev) { LOGERRORMOD(scrubmgr, "repl_dev is null, cannot wait for scrub lsn commit!"); return false; @@ -546,59 +538,58 @@ std::shared_ptr< ScrubManager::scrub_result > ScrubManager::local_scrub_blob(std // deep scrub: read and check blobs. auto& data_service = homestore::data_service(); const auto blk_size = data_service.get_blk_size(); - std::vector< folly::Future< folly::Unit > > futs; + std::vector< sisl::async::task< bool > > futs; - for (const auto& [k, v] : out) { - auto pba = v.pbas(); + auto scrub_one_blob = [this, &data_service, blk_size, + blob_scrub_result](shard_id_t shard_id, blob_id_t blob_id, + homestore::multi_blk_id pba) -> sisl::async::task< bool > { auto total_size = pba.blk_count() * blk_size; sisl::sg_list data_sgs; data_sgs.size = total_size; data_sgs.iovs.emplace_back( iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); - const auto& shard_id = k.key().shard; - const auto& blob_id = k.key().blob; + auto err = co_await data_service.async_read(pba, data_sgs, total_size); + auto blob = data_sgs.iovs[0].iov_base; + struct buffer_free_guard { + uint8_t* buf; + ~buffer_free_guard() { iomanager.iobuf_free(buf); } + } guard{reinterpret_cast< uint8_t* >(blob)}; - futs.emplace_back(std::move( - data_service.async_read(pba, data_sgs, total_size) - .thenValue([this, shard_id, blob_id, data_sgs = std::move(data_sgs), blob_scrub_result](auto&& err) { - auto blob = data_sgs.iovs[0].iov_base; - struct buffer_free_guard { - uint8_t* buf; - ~buffer_free_guard() { iomanager.iobuf_free(buf); } - } guard{reinterpret_cast< uint8_t* >(blob)}; + ScrubManager::scrub_result_entry entry{shard_id, blob_id, ScrubStatus::NONE}; - ScrubManager::scrub_result_entry entry{shard_id, blob_id, ScrubStatus::NONE}; + if (!err) { + LOGERRORMOD(scrubmgr, "Failed to read blob for deep scrub, shard_id={}, blob_id={}, error={}", shard_id, + blob_id, err.error().message()); + entry.status_or_hash = ScrubStatus::IO_ERROR; + } else { + const auto blob_verify_succeed = m_hs_home_object->verify_blob(blob, shard_id, blob_id, true); + if (!blob_verify_succeed) { + // note that, if gc kicks in, the pba might be overwritten and lead to verification + // failure. + + // FIXME:: handle this case by query and read the blob again. + LOGERRORMOD(scrubmgr, "Blob verification failed for deep scrub, shard_id={}, blob_id={}", shard_id, + blob_id); + entry.status_or_hash = ScrubStatus::MISMATCH; + } else { + // we only calculate crc64 for data part. + const auto* header = reinterpret_cast< const HSHomeObject::BlobHeader* >(blob); + const auto* blob_data = reinterpret_cast< const uint8_t* >(blob) + header->data_offset; + entry.status_or_hash = compute_crc64(blob_data, header->blob_size); + } + } - if (err) { - LOGERRORMOD(scrubmgr, "Failed to read blob for deep scrub, shard_id={}, blob_id={}, error={}", - shard_id, blob_id, err.message()); - entry.status_or_hash = ScrubStatus::IO_ERROR; - } else { - const auto blob_verify_succeed = m_hs_home_object->verify_blob(blob, shard_id, blob_id, true); - if (!blob_verify_succeed) { - // note that, if gc kicks in, the pba might be overwritten and lead to verification - // failure. - - // FIXME:: handle this case by query and read the blob again. - LOGERRORMOD(scrubmgr, "Blob verification failed for deep scrub, shard_id={}, blob_id={}", - shard_id, blob_id); - entry.status_or_hash = ScrubStatus::MISMATCH; - } else { - // we only calculate crc64 for data part. - const auto* header = reinterpret_cast< const HSHomeObject::BlobHeader* >(blob); - const auto* blob_data = reinterpret_cast< const uint8_t* >(blob) + header->data_offset; - entry.status_or_hash = compute_crc64(blob_data, header->blob_size); - } - } + LOGDEBUGMOD(scrubmgr, "add entry to blob scrub result: shard_id={}, blob_id={}", entry.shard_id, entry.blob_id); + blob_scrub_result->add_entry(entry); + co_return true; + }; - LOGDEBUGMOD(scrubmgr, "add entry to blob scrub result: shard_id={}, blob_id={}", entry.shard_id, - entry.blob_id); - blob_scrub_result->add_entry(entry); - }))); + for (const auto& [k, v] : out) { + futs.emplace_back(scrub_one_blob(k.key().shard, k.key().blob, v.pbas())); } - folly::collectAllUnsafe(futs).wait(); + sisl::async::sync_get(sisl::async::when_all(std::move(futs))); LOGDEBUGMOD(scrubmgr, "pg_id={}, req_id={}, deep blob scrub completed, found {} blobs in range [{},{}] to [{},{})", pg_id, req_id, out.size(), req->start_shard_id, req->start_blob_id, req->end_shard_id, @@ -754,30 +745,29 @@ std::shared_ptr< ScrubManager::scrub_result > ScrubManager::local_scrub_meta(std return meta_scrub_result; } -folly::SemiFuture< std::shared_ptr< ScrubManager::ShallowScrubReport > > +sisl::async::task< std::shared_ptr< ScrubManager::ShallowScrubReport > > ScrubManager::submit_scrub_task(const pg_id_t& pg_id, const bool is_deep, SCRUB_TRIGGER_TYPE trigger_type) { LOGINFOMOD(scrubmgr, "submit a scrub task for pg={}, deep_scrub={}, trigger_type={}", pg_id, is_deep, trigger_type); // Check if a scrub task is already running for this PG. // Note: There's still a small race window between this check and task execution in handle_pg_scrub_task, // but the in_scrubbing CAS below provides the final guard. This check prevents unnecessary work. - auto it = m_pg_scrub_ctx_map.find(pg_id); - if (it != m_pg_scrub_ctx_map.end()) { + if (m_pg_scrub_ctx_map.cvisit(pg_id, [](auto const&) {})) { LOGWARNMOD(scrubmgr, "a scrub task is already running for pg={}, no need to submit another one!", pg_id); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } - const auto ps_scrub_super_blk_it = m_pg_scrub_sb_map.find(pg_id); - if (ps_scrub_super_blk_it == m_pg_scrub_sb_map.end()) { + std::shared_ptr< homestore::superblk< pg_scrub_superblk > > pg_scrub_sb; + if (!m_pg_scrub_sb_map.cvisit(pg_id, [&](auto const& entry) { pg_scrub_sb = entry.second; })) { LOGERRORMOD(scrubmgr, "cannot find scrub superblk for pg={}, fail to submit scrub task!", pg_id); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } // Get the PG and check its state const auto hs_pg = m_hs_home_object->get_hs_pg(pg_id); if (!hs_pg) { LOGERRORMOD(scrubmgr, "cannot find hs_pg for pg={}, fail to submit scrub task!", pg_id); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } // Check if pg_state is HEALTHY (state must be 0) @@ -785,7 +775,7 @@ ScrubManager::submit_scrub_task(const pg_id_t& pg_id, const bool is_deep, SCRUB_ if (current_state != 0) { LOGWARNMOD(scrubmgr, "pg={} is not in HEALTHY state (current_state={}), cannot submit scrub task!", pg_id, current_state); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } // TODO:: use PGStateMask::SCRUBBING state to replace the in_scrubbing flag after cm supports @@ -794,31 +784,30 @@ ScrubManager::submit_scrub_task(const pg_id_t& pg_id, const bool is_deep, SCRUB_ bool expected = false; if (!hs_pg->in_scrubbing.compare_exchange_strong(expected, true)) { LOGWARNMOD(scrubmgr, "pg={} scrub submission already in-flight, skip!", pg_id); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } - const auto& pg_scrub_sb = *(ps_scrub_super_blk_it->second); const auto last_scrub_time = - is_deep ? pg_scrub_sb->last_deep_scrub_timestamp : pg_scrub_sb->last_shallow_scrub_timestamp; + is_deep ? (*pg_scrub_sb)->last_deep_scrub_timestamp : (*pg_scrub_sb)->last_shallow_scrub_timestamp; - auto [promise, future] = folly::makePromiseContract< std::shared_ptr< ShallowScrubReport > >(); - ScrubManager::scrub_task task(last_scrub_time, pg_id, is_deep, trigger_type, std::move(promise)); + auto promise = std::make_shared< sisl::async::value_awaitable< std::shared_ptr< ShallowScrubReport > > >(); + ScrubManager::scrub_task task(last_scrub_time, pg_id, is_deep, trigger_type, promise); if (!m_scrub_task_queue.push(std::move(task))) { // Queue is closed (scrub manager is stopped); roll back in_scrubbing so future submissions are not blocked. hs_pg->in_scrubbing.store(false); LOGWARNMOD(scrubmgr, "pg={} scrub task queue is closed/stopped, skip!", pg_id); - return folly::makeSemiFuture(std::shared_ptr< ScrubManager::ShallowScrubReport >(nullptr)); + co_return nullptr; } - return std::move(future); + co_return co_await sisl::async::await_value(promise); } void ScrubManager::cancel_scrub_task(const pg_id_t& pg_id) { - auto it = m_pg_scrub_ctx_map.find(pg_id); - if (it == m_pg_scrub_ctx_map.end()) { + std::shared_ptr< PGScrubContext > scrub_ctx; + if (!m_pg_scrub_ctx_map.cvisit(pg_id, [&](auto const& entry) { scrub_ctx = entry.second; })) { LOGWARNMOD(scrubmgr, "no running scrub task for pg={}, no need to cancel!", pg_id); return; } - it->second->cancel(); + scrub_ctx->cancel(); LOGINFOMOD(scrubmgr, "cancel scrub task for pg={}", pg_id); } @@ -836,7 +825,7 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { struct scrub_task_guard { HSHomeObject* home_obj; - folly::ConcurrentHashMap< pg_id_t, std::shared_ptr< PGScrubContext > >& pg_scrub_ctx_map; + boost::concurrent_flat_map< pg_id_t, std::shared_ptr< PGScrubContext > >& pg_scrub_ctx_map; scrub_task& task; std::shared_ptr< ShallowScrubReport >& scrub_report; const pg_id_t& pg_id; @@ -847,7 +836,7 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { // completed scrub that legitimately found no issues (e.g. an empty PG). const bool was_cancelled = scrub_ctx && scrub_ctx->cancelled.load(); pg_scrub_ctx_map.erase(pg_id); - task.scrub_report_promise->setValue(was_cancelled ? nullptr : scrub_report); + task.scrub_report_promise->complete(was_cancelled ? nullptr : scrub_report); auto hs_pg = home_obj->get_hs_pg(pg_id); if (hs_pg) { hs_pg->in_scrubbing.store(false); @@ -865,10 +854,10 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { return; } - auto [ctx_it, happened] = m_pg_scrub_ctx_map.try_emplace(pg_id, std::make_shared< PGScrubContext >(task_id, hs_pg)); + auto scrub_ctx = std::make_shared< PGScrubContext >(task_id, hs_pg); + const bool happened = m_pg_scrub_ctx_map.try_emplace(pg_id, scrub_ctx); RELEASE_ASSERT(happened, "pg={} should not have a running scrub task since we set in_scrubbing in submit_scrub_task", pg_id); - auto& scrub_ctx = ctx_it->second; guard.scrub_ctx = scrub_ctx; // Allow the guard to detect cancellation at teardown // this is the last committed shard_id. we cannot get shard_sequence_num here since some of the shard might be @@ -893,10 +882,9 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { } std::map< shard_id_t, uint32_t > shard_blob_count_in_batch; - if (!scrub_ctx - ->scrub_meta_batch(pg_scrub_report, start_shard_id, last_committed_shard_id, last_committed_blob_id, - scrub_lsn, shard_blob_count_in_batch) - .get()) { + if (!sisl::async::sync_get(scrub_ctx->scrub_meta_batch(pg_scrub_report, start_shard_id, last_committed_shard_id, + last_committed_blob_id, scrub_lsn, + shard_blob_count_in_batch))) { SCRUBLOGE(pg_id, task_id, "meta scrub failed for batch in range: {} to {}, scrub_lsn={}", start_shard_id, last_committed_shard_id, scrub_lsn); return; @@ -932,10 +920,9 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { auto blob_count = it->second; if (total_blob_count_in_batch + blob_count >= max_scrub_batch_size) { // scrub current batch - if (!scrub_ctx - ->scrub_blob_batch(pg_scrub_report, start_shard_id, end_shard_id, last_committed_blob_id, - scrub_lsn, is_deep_scrub) - .get()) { + if (!sisl::async::sync_get(scrub_ctx->scrub_blob_batch(pg_scrub_report, start_shard_id, end_shard_id, + last_committed_blob_id, scrub_lsn, + is_deep_scrub))) { SCRUBLOGE(pg_id, task_id, "{} blob scrub failed for shard range: {} to {}, scrub_lsn={}", is_deep_scrub ? "deep" : "shallow", start_shard_id, end_shard_id, scrub_lsn); return; @@ -951,10 +938,8 @@ void ScrubManager::handle_pg_scrub_task(scrub_task task) { } // scrub last batch - if (!scrub_ctx - ->scrub_blob_batch(pg_scrub_report, start_shard_id, end_shard_id, last_committed_blob_id, scrub_lsn, - is_deep_scrub) - .get()) { + if (!sisl::async::sync_get(scrub_ctx->scrub_blob_batch(pg_scrub_report, start_shard_id, end_shard_id, + last_committed_blob_id, scrub_lsn, is_deep_scrub))) { SCRUBLOGE(pg_id, task_id, "{} blob scrub batch failed for shard range: {} to {}, scrub_lsn={}", is_deep_scrub ? "deep" : "shallow", start_shard_id, end_shard_id, scrub_lsn); return; @@ -998,15 +983,13 @@ void ScrubManager::remove_pg(const pg_id_t pg_id) { cancel_scrub_task(pg_id); m_pg_scrub_ctx_map.erase(pg_id); - auto it = m_pg_scrub_sb_map.find(pg_id); - if (it == m_pg_scrub_sb_map.end()) { + if (!m_pg_scrub_sb_map.visit(pg_id, [](auto& entry) { entry.second->destroy(); })) { LOGINFOMOD(scrubmgr, "no scrub superblock found for pg={}, no need to remove", pg_id); return; } LOGINFOMOD(scrubmgr, "removed pg={} in scrub manager!", pg_id); - it->second->destroy(); - m_pg_scrub_sb_map.erase(it); + m_pg_scrub_sb_map.erase(pg_id); } // this function is called in meta_service thread context @@ -1014,7 +997,7 @@ void ScrubManager::on_pg_scrub_meta_blk_found( sisl::byte_view const& buf, void* meta_cookie, std::vector< homestore::superblk< pg_scrub_superblk > >& stale_pg_scrub_sbs) { auto sb = std::make_shared< homestore::superblk< pg_scrub_superblk > >(); - (*sb).load(buf, meta_cookie); + (*sb).load(buf, r_cast< homestore::meta_blk* >(meta_cookie)); const auto pg_id = (*sb)->pg_id; auto hs_pg = m_hs_home_object->get_hs_pg(pg_id); @@ -1036,40 +1019,40 @@ void ScrubManager::save_scrub_superblk(const pg_id_t pg_id, const bool is_deep_s const auto current_time = std::chrono::duration_cast< std::chrono::seconds >(std::chrono::system_clock::now().time_since_epoch()).count(); - auto it = m_pg_scrub_sb_map.find(pg_id); - if (it == m_pg_scrub_sb_map.end()) { - // Create new superblock for this PG - auto sb = std::make_shared< homestore::superblk< pg_scrub_superblk > >(pg_scrub_meta_name); - (*sb).create(sizeof(pg_scrub_superblk)); - (*sb)->pg_id = pg_id; - (*sb)->last_deep_scrub_timestamp = current_time; - (*sb)->last_shallow_scrub_timestamp = current_time; - (*sb).write(); - m_pg_scrub_sb_map.emplace(pg_id, std::move(sb)); - return; - } - - if (force_update) { - // Update existing superblock - if (is_deep_scrub) { - (*(it->second))->last_deep_scrub_timestamp = current_time; + const bool found = m_pg_scrub_sb_map.visit(pg_id, [&](auto& entry) { + if (force_update) { + // Update existing superblock + if (is_deep_scrub) { + (*(entry.second))->last_deep_scrub_timestamp = current_time; + } else { + (*(entry.second))->last_shallow_scrub_timestamp = current_time; + } + (*(entry.second)).write(); } else { - (*(it->second))->last_shallow_scrub_timestamp = current_time; + LOGINFOMOD(scrubmgr, "skip updating scrub superblock for pg={} since there is no scrub progress update", + pg_id); } - (*(it->second)).write(); - } else { - LOGINFOMOD(scrubmgr, "skip updating scrub superblock for pg={} since there is no scrub progress update", pg_id); - } + }); + if (found) { return; } + + // Create new superblock for this PG + auto sb = std::make_shared< homestore::superblk< pg_scrub_superblk > >(pg_scrub_meta_name); + (*sb).create(sizeof(pg_scrub_superblk)); + (*sb)->pg_id = pg_id; + (*sb)->last_deep_scrub_timestamp = current_time; + (*sb)->last_shallow_scrub_timestamp = current_time; + (*sb).write(); + m_pg_scrub_sb_map.emplace(pg_id, std::move(sb)); } std::optional< ScrubManager::pg_scrub_superblk > ScrubManager::get_scrub_superblk(const pg_id_t pg_id) const { - auto it = m_pg_scrub_sb_map.find(pg_id); - if (it == m_pg_scrub_sb_map.end()) { + std::optional< ScrubManager::pg_scrub_superblk > result; + if (!m_pg_scrub_sb_map.cvisit(pg_id, [&](auto const& entry) { result = *(*(entry.second)); })) { LOGWARNMOD(scrubmgr, "scrub superblk not found for pg {}", pg_id); return std::nullopt; } - return *(*(it->second)); + return result; } ScrubManager::PGScrubContext::PGScrubContext(uint64_t task_id, const HSHomeObject::HS_PG* hs_pg) : @@ -1098,7 +1081,7 @@ void ScrubManager::PGScrubContext::handle_scrub_req_resp(std::shared_ptr< ScrubM std::unique_lock lock(peer_batch_scrub_ctx->mutex); // Drop silently if the promise was already fulfilled by another path (timeout, prior response). - if (peer_batch_scrub_ctx->promise.isFulfilled()) { + if (!peer_batch_scrub_ctx->promise || peer_batch_scrub_ctx->promise->await_ready()) { SCRUBLOGD(pg_id, task_id, "promise already fulfilled for peer {}, dropping result req_id={}", result->issuer_peer_id, result->req_id); return; @@ -1161,7 +1144,7 @@ void ScrubManager::PGScrubContext::handle_scrub_req_resp(std::shared_ptr< ScrubM // This peer has finished its share of the batch range. SCRUBLOGD(pg_id, task_id, "peer {} completed batch, batch_scrub_result entries={}", result->issuer_peer_id, peer_batch_scrub_result->results.size()); - peer_batch_scrub_ctx->promise.setValue(peer_batch_scrub_result); + peer_batch_scrub_ctx->promise->complete(peer_batch_scrub_result); } void ScrubManager::PGScrubContext::send_req_to_peer(const ScrubManager::scrub_req& req, const peer_id_t& peer_id) { @@ -1180,13 +1163,13 @@ void ScrubManager::PGScrubContext::send_req_to_peer(const ScrubManager::scrub_re sisl::io_blob_list_t blob_list; blob_list.emplace_back(flatbuffer.data(), flatbuffer.size(), false); - repl_dev->data_request_unidirectional(peer_id, HSHomeObject::PUSH_SCRUB_REQ, blob_list) - .via(folly::getGlobalIOExecutor()) - .thenValue([pg_id, peer_id, task_id = this->task_id, flatbuffer = std::move(flatbuffer), req_id = req.req_id, - scrub_type = req.scrub_type](auto&& response) { - if (response.hasError()) { + sisl::async::detach_then( + repl_dev->data_request_unidirectional(peer_id, HSHomeObject::PUSH_SCRUB_REQ, blob_list), + [pg_id, peer_id, task_id = this->task_id, flatbuffer = std::move(flatbuffer), req_id = req.req_id, + scrub_type = req.scrub_type](auto&& response) { + if (!response) { SCRUBLOGE(pg_id, task_id, "failed to send scrub req to peer {}, req_id={}, error={}, scrub_type={}", - peer_id, req_id, response.error(), scrub_type); + peer_id, req_id, response.error().message(), scrub_type); } else { SCRUBLOGD(pg_id, task_id, "successfully sent scrub req to peer {}, req_id={}, scrub_type={}", peer_id, req_id, scrub_type); @@ -1194,79 +1177,70 @@ void ScrubManager::PGScrubContext::send_req_to_peer(const ScrubManager::scrub_re }); } -folly::Future< bool > ScrubManager::PGScrubContext::scrub_meta_batch( +sisl::async::task< bool > ScrubManager::PGScrubContext::scrub_meta_batch( std::shared_ptr< ScrubManager::MetaScrubReport > scrub_report, shard_id_t start_shard_id, shard_id_t end_shard_id, blob_id_t last_blob_id, int64_t scrub_lsn, std::map< shard_id_t, uint32_t >& shard_blob_count_in_batch) { const auto pg_id = hs_pg->pg_id(); SCRUBLOGD(pg_id, task_id, "start scrubbing meta for shard range: {} to {}, last_blob_id={}, scrub_lsn={}", start_shard_id, end_shard_id, last_blob_id, scrub_lsn); - // Reset each peer's ctx for this batch round and collect futures. - std::vector< folly::Future< std::shared_ptr< ScrubManager::range_scrub_result > > > futs; + // Reset each peer's ctx for this batch round and collect awaitables. + std::vector< sisl::async::task< std::shared_ptr< ScrubManager::range_scrub_result > > > futs; for (auto& [peer_id, ctx] : m_active_batch) { auto req = scrub_req(pg_id, random_req_id(), scrub_lsn, start_shard_id, 0, end_shard_id, last_blob_id, SCRUB_TYPE::META, hs_pg->home_obj_.our_uuid()); auto batch_scrub_result = std::make_shared< range_scrub_result >(start_shard_id, 0, end_shard_id, last_blob_id, SCRUB_TYPE::META, peer_id); - futs.emplace_back(ctx->reset(req, batch_scrub_result, cancelled)); + futs.emplace_back(sisl::async::await_value(ctx->reset(req, batch_scrub_result, cancelled))); send_req_to_peer(req, peer_id); } - return folly::collectAllUnsafe(futs).thenValue( - [this, pg_id, scrub_report, &shard_blob_count_in_batch]( - std::vector< folly::Try< std::shared_ptr< range_scrub_result > > > results) -> bool { - // Aggregate results into peer_scrub_result_map. - std::map< peer_id_t, std::shared_ptr< range_scrub_result > > peer_scrub_result_map; - for (auto& r : results) { - if (r.hasException()) { - SCRUBLOGE(pg_id, task_id, "scrub meta batch is failed, error={}", r.exception().what()); - return false; - } - auto range_result = r.value(); - if (!range_result) { - SCRUBLOGE(pg_id, task_id, "scrub meta batch is failed, receive nullptr scrub result"); - return false; - } - peer_scrub_result_map[range_result->peer_id] = range_result; - SCRUBLOGD(pg_id, task_id, "complete meta range scrub: {}", range_result->to_string()); - } + auto results = co_await sisl::async::when_all(std::move(futs)); - // Consolidate peer_scrub_result_map into shard_blob_count_in_batch. - std::map< shard_id_t, std::pair< size_t /*occurrence count*/, uint32_t /*max blob count*/ > > - shard_count_map; - for (const auto& [peer_id_key, range_result] : peer_scrub_result_map) { - for (const auto& [route, status_or_hash] : range_result->results) { - shard_count_map[route.shard].first++; - // blob count: route.blob holds the blob count in this shard; take the max across peers. - const auto blob_count = shard_count_map[route.shard].second; - shard_count_map[route.shard].second = std::max(blob_count, static_cast< uint32_t >(route.blob)); - } - } + // Aggregate results into peer_scrub_result_map. + std::map< peer_id_t, std::shared_ptr< range_scrub_result > > peer_scrub_result_map; + for (auto& range_result : results) { + if (!range_result) { + SCRUBLOGE(pg_id, task_id, "scrub meta batch is failed, receive nullptr scrub result"); + co_return false; + } + peer_scrub_result_map[range_result->peer_id] = range_result; + SCRUBLOGD(pg_id, task_id, "complete meta range scrub: {}", range_result->to_string()); + } + + // Consolidate peer_scrub_result_map into shard_blob_count_in_batch. + std::map< shard_id_t, std::pair< size_t /*occurrence count*/, uint32_t /*max blob count*/ > > shard_count_map; + for (const auto& [peer_id_key, range_result] : peer_scrub_result_map) { + for (const auto& [route, status_or_hash] : range_result->results) { + shard_count_map[route.shard].first++; + // blob count: route.blob holds the blob count in this shard; take the max across peers. + const auto blob_count = shard_count_map[route.shard].second; + shard_count_map[route.shard].second = std::max(blob_count, static_cast< uint32_t >(route.blob)); + } + } - for (const auto& [shard_id, count_pair] : shard_count_map) { - // shard_id=0 is a pg meta result, not a real shard — skip in blob scrub phase. - if (!shard_id) continue; + for (const auto& [shard_id, count_pair] : shard_count_map) { + // shard_id=0 is a pg meta result, not a real shard — skip in blob scrub phase. + if (!shard_id) continue; - if (count_pair.first == peer_scrub_result_map.size()) { - // all peers have this shard; empty shards get a minimum blob count of 1. - shard_blob_count_in_batch[shard_id] = std::max(count_pair.second, uint32_t{1}); - } else { - RELEASE_ASSERT( - count_pair.first < peer_scrub_result_map.size(), - "the occurrence count of shard_id {} should not be larger than peer count, but it is {}", - shard_id, count_pair.first); - // Not all peers have this shard: mark as missing; force single-blob-batch in blob scrub phase. - shard_blob_count_in_batch[shard_id] = UINT32_MAX; - } - } + if (count_pair.first == peer_scrub_result_map.size()) { + // all peers have this shard; empty shards get a minimum blob count of 1. + shard_blob_count_in_batch[shard_id] = std::max(count_pair.second, uint32_t{1}); + } else { + RELEASE_ASSERT(count_pair.first < peer_scrub_result_map.size(), + "the occurrence count of shard_id {} should not be larger than peer count, but it is {}", + shard_id, count_pair.first); + // Not all peers have this shard: mark as missing; force single-blob-batch in blob scrub phase. + shard_blob_count_in_batch[shard_id] = UINT32_MAX; + } + } - // Consolidate peer_scrub_result_map into scrub_report. - scrub_report->merge(peer_scrub_result_map); - return true; - }); + // Consolidate peer_scrub_result_map into scrub_report. + scrub_report->merge(peer_scrub_result_map); + co_return true; } -folly::Future< bool > +sisl::async::task< bool > ScrubManager::PGScrubContext::scrub_blob_batch(std::shared_ptr< ScrubManager::ShallowScrubReport > scrub_report, shard_id_t start_shard_id, shard_id_t end_shard_id, blob_id_t last_blob_id, int64_t scrub_lsn, bool is_deep_scrub) { @@ -1277,40 +1251,33 @@ ScrubManager::PGScrubContext::scrub_blob_batch(std::shared_ptr< ScrubManager::Sh "start scrubbing blob for shard range: {} to {}, last_blob_id={}, scrub_lsn={}, scrub_type={}", start_shard_id, end_shard_id, last_blob_id, scrub_lsn, scrub_type); - // Reset each peer's ctx for this batch round and collect futures. - std::vector< folly::Future< std::shared_ptr< ScrubManager::range_scrub_result > > > futs; + // Reset each peer's ctx for this batch round and collect awaitables. + std::vector< sisl::async::task< std::shared_ptr< ScrubManager::range_scrub_result > > > futs; for (auto& [peer_id, ctx] : m_active_batch) { auto req = scrub_req(pg_id, random_req_id(), scrub_lsn, start_shard_id, 0, end_shard_id, last_blob_id, scrub_type, hs_pg->home_obj_.our_uuid()); auto batch_scrub_result = std::make_shared< range_scrub_result >(start_shard_id, 0, end_shard_id, last_blob_id, scrub_type, peer_id); - futs.emplace_back(ctx->reset(req, batch_scrub_result, cancelled)); + futs.emplace_back(sisl::async::await_value(ctx->reset(req, batch_scrub_result, cancelled))); send_req_to_peer(req, peer_id); } - return folly::collectAllUnsafe(futs).thenValue( - [this, pg_id, - scrub_report](std::vector< folly::Try< std::shared_ptr< range_scrub_result > > > results) -> bool { - // Aggregate results into peer_scrub_result_map. - std::map< peer_id_t, std::shared_ptr< range_scrub_result > > peer_scrub_result_map; - for (auto& r : results) { - if (r.hasException()) { - SCRUBLOGE(pg_id, task_id, "scrub blob batch is failed, error={}", r.exception().what()); - return false; - } - auto range_result = r.value(); - if (!range_result) { - SCRUBLOGE(pg_id, task_id, "scrub blob batch is failed, receive nullptr scrub result"); - return false; - } - peer_scrub_result_map.emplace(range_result->peer_id, range_result); - SCRUBLOGD(pg_id, task_id, "complete blob range scrub: {}", range_result->to_string()); - } + auto results = co_await sisl::async::when_all(std::move(futs)); - // Consolidate peer_scrub_result_map into scrub_report. - scrub_report->merge(peer_scrub_result_map); - return true; - }); + // Aggregate results into peer_scrub_result_map. + std::map< peer_id_t, std::shared_ptr< range_scrub_result > > peer_scrub_result_map; + for (auto& range_result : results) { + if (!range_result) { + SCRUBLOGE(pg_id, task_id, "scrub blob batch is failed, receive nullptr scrub result"); + co_return false; + } + peer_scrub_result_map.emplace(range_result->peer_id, range_result); + SCRUBLOGD(pg_id, task_id, "complete blob range scrub: {}", range_result->to_string()); + } + + // Consolidate peer_scrub_result_map into scrub_report. + scrub_report->merge(peer_scrub_result_map); + co_return true; } void ScrubManager::PGScrubContext::reconcile_scrub_report(std::shared_ptr< ShallowScrubReport > scrub_report) { @@ -1336,67 +1303,60 @@ void ScrubManager::PGScrubContext::reconcile_scrub_report(std::shared_ptr< Shall return; } - std::vector< folly::Future< folly::Unit > > reconcile_futs; + std::vector< sisl::async::task< std::monostate > > reconcile_futs; + + auto reconcile_one = [this, pg_id, &scrub_report](peer_id_t peer_id, BlobRoute blob, + bool check_blob) -> sisl::async::task< std::monostate > { + try { + const bool exists = co_await check_existence_in_peer(peer_id, blob, check_blob); + if (!exists) { + if (check_blob) { + SCRUBLOGD(pg_id, task_id, + "reconcile check: shard_id={}, blob_id={} confirmed absent on peer {}, " + "removing from existence-tracking set", + blob.shard, blob.blob, peer_id); + scrub_report->remove_blob_existence_from_peer(blob, peer_id); + } else { + SCRUBLOGD(pg_id, task_id, + "reconcile check: shard {} confirmed absent on peer {}, removing from " + "existence-tracking set", + blob.shard, peer_id); + scrub_report->remove_shard_existence_from_peer(blob.shard, peer_id); + } + } else if (check_blob) { + SCRUBLOGD(pg_id, task_id, + "reconcile check: shard_id={}, blob_id={} still present on peer {}, no change", + blob.shard, blob.blob, peer_id); + } else { + SCRUBLOGD(pg_id, task_id, "reconcile check: shard {} still present on peer {}, no change", + blob.shard, peer_id); + } + } catch (const std::exception& e) { + if (check_blob) { + SCRUBLOGE(pg_id, task_id, + "failed to check blob existence in peer {}, shard_id={}, blob_id={}, error: {}", peer_id, + blob.shard, blob.blob, e.what()); + } else { + SCRUBLOGE(pg_id, task_id, "failed to check shard existence in peer {}, shard {}, error: {}", + peer_id, blob.shard, e.what()); + } + } + co_return std::monostate{}; + }; for (const auto& [shard_id, peer_set] : missing_shards) { for (const auto& peer_id : peer_set) { - reconcile_futs.emplace_back(std::move( - check_existence_in_peer(peer_id, {shard_id, 0}, false /* check blob */) - .thenTry([this, pg_id, peer_id, shard_id, &scrub_report](folly::Try< bool > result) { - if (result.hasException()) { - SCRUBLOGE(pg_id, task_id, - "failed to check shard existence in peer {}, shard {}, error: {}", peer_id, - shard_id, result.exception().what()); - return; - } - - const auto& exists = result.value(); - if (!exists) { - SCRUBLOGD(pg_id, task_id, - "reconcile check: shard {} confirmed absent on peer {}, removing from " - "existence-tracking set", - shard_id, peer_id); - scrub_report->remove_shard_existence_from_peer(shard_id, peer_id); - } else { - SCRUBLOGD(pg_id, task_id, - "reconcile check: shard {} still present on peer {}, no change", shard_id, - peer_id); - } - }))); + reconcile_futs.emplace_back(reconcile_one(peer_id, {shard_id, 0}, false /* check blob */)); } } for (const auto& [blob_route, peer_set] : missing_blobs) { for (const auto& peer_id : peer_set) { - reconcile_futs.emplace_back(std::move( - check_existence_in_peer(peer_id, blob_route, true /* check blob */) - .thenTry([this, pg_id, peer_id, blob_route, &scrub_report](folly::Try< bool > result) { - if (result.hasException()) { - SCRUBLOGE( - pg_id, task_id, - "failed to check blob existence in peer {}, shard_id={}, blob_id={}, error: {}", - peer_id, blob_route.shard, blob_route.blob, result.exception().what()); - return; - } - - const auto& exists = result.value(); - if (!exists) { - SCRUBLOGD(pg_id, task_id, - "reconcile check: shard_id={}, blob_id={} confirmed absent on peer {}, " - "removing from existence-tracking set", - blob_route.shard, blob_route.blob, peer_id); - scrub_report->remove_blob_existence_from_peer(blob_route, peer_id); - } else { - SCRUBLOGD( - pg_id, task_id, - "reconcile check: shard_id={}, blob_id={} still present on peer {}, no change", - blob_route.shard, blob_route.blob, peer_id); - } - }))); + reconcile_futs.emplace_back(reconcile_one(peer_id, blob_route, true /* check blob */)); } } - folly::collectAllUnsafe(reconcile_futs).wait(); + sisl::async::sync_get(sisl::async::when_all(std::move(reconcile_futs))); } const auto remaining_missing_shards = scrub_report->get_missing_shard_ids().size(); @@ -1411,16 +1371,12 @@ void ScrubManager::PGScrubContext::reconcile_scrub_report(std::shared_ptr< Shall } } -folly::Future< bool > ScrubManager::PGScrubContext::check_existence_in_peer(peer_id_t peer_id, BlobRoute blob, - bool check_blob) { - auto [promise, future] = folly::makePromiseContract< bool >(); +sisl::async::task< bool > ScrubManager::PGScrubContext::check_existence_in_peer(peer_id_t peer_id, BlobRoute blob, + bool check_blob) { const auto pg_id = hs_pg->pg_id(); auto repl_dev = hs_pg->repl_dev_; - if (!repl_dev) { - promise.setException(folly::make_exception_wrapper< std::runtime_error >("repl dev is not available")); - return std::move(future).via(folly::getGlobalIOExecutor()); - } + if (!repl_dev) { throw std::runtime_error("repl dev is not available"); } ScrubManager::scrub_req check_blob_req; check_blob_req.start_shard_id = blob.shard; @@ -1434,34 +1390,25 @@ folly::Future< bool > ScrubManager::PGScrubContext::check_existence_in_peer(peer const auto check_type_str = check_blob ? "blob" : "shard"; // this is a bidirectional request, no need to add a req_id. - repl_dev->data_request_bidirectional(peer_id, HSHomeObject::PUSH_SCRUB_REQ, blob_list) - .via(folly::getGlobalIOExecutor()) - .thenValue([pg_id, peer_id, task_id = this->task_id, blob, check_type_str, flatbuffer = std::move(flatbuffer), - promise = std::move(promise)](auto&& response) mutable { - if (response.hasError()) { - SCRUBLOGE(pg_id, task_id, "failed to check {} existence in peer {}, blob {}, error code: {}", - check_type_str, peer_id, blob, static_cast< int >(response.error())); - promise.setException( - folly::make_exception_wrapper< std::runtime_error >("rpc bidirectional request failed")); - } else { - const auto& resp_blob = response.value().response_blob(); - if (resp_blob.size() != sizeof(bool)) { - SCRUBLOGE(pg_id, task_id, - "invalid response for {} existence check from peer {}, blob {}, response size={}", - check_type_str, peer_id, blob, resp_blob.size()); - promise.setException( - folly::make_exception_wrapper< std::runtime_error >("invalid response for existence check")); - } else { - const bool exists = *reinterpret_cast< const bool* >(resp_blob.cbytes()); - SCRUBLOGD(pg_id, task_id, - "successfully checked {} existence in peer {}, shard_id={}, blob_id={}, exists={}", - check_type_str, peer_id, blob.shard, blob.blob, exists); - promise.setValue(exists); - } - } - }); + auto response = co_await repl_dev->data_request_bidirectional(peer_id, HSHomeObject::PUSH_SCRUB_REQ, blob_list); + + if (!response) { + SCRUBLOGE(pg_id, task_id, "failed to check {} existence in peer {}, blob {}, error code: {}", check_type_str, + peer_id, blob, response.error().message()); + throw std::runtime_error("rpc bidirectional request failed"); + } + + const auto& resp_blob = response.value().response_blob(); + if (resp_blob.size() != sizeof(bool)) { + SCRUBLOGE(pg_id, task_id, "invalid response for {} existence check from peer {}, blob {}, response size={}", + check_type_str, peer_id, blob, resp_blob.size()); + throw std::runtime_error("invalid response for existence check"); + } - return std::move(future).via(folly::getGlobalIOExecutor()); + const bool exists = *reinterpret_cast< const bool* >(resp_blob.cbytes()); + SCRUBLOGD(pg_id, task_id, "successfully checked {} existence in peer {}, shard_id={}, blob_id={}, exists={}", + check_type_str, peer_id, blob.shard, blob.blob, exists); + co_return exists; } uint64_t ScrubManager::PGScrubContext::random_req_id() const { diff --git a/src/lib/homestore_backend/scrub_manager.hpp b/src/lib/homestore_backend/scrub_manager.hpp index 8c4fb591c..8127c1741 100644 --- a/src/lib/homestore_backend/scrub_manager.hpp +++ b/src/lib/homestore_backend/scrub_manager.hpp @@ -3,21 +3,22 @@ #include #include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuninitialized" -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#include -#include -#include -#pragma GCC diagnostic pop +#include +#include + +#include +#include +#include +#include +#include #include -#include "homeobject/common.hpp" -#include + +#include #include + +#include "homeobject/common.hpp" #include "lib/blob_route.hpp" -#include "MPMCPriorityQueue.hpp" #include "generated/scrub_common_generated.h" #include "generated/scrub_req_generated.h" #include "generated/scrub_result_generated.h" @@ -265,7 +266,7 @@ class ScrubManager { void start(); void stop(); - folly::SemiFuture< std::shared_ptr< ShallowScrubReport > > + sisl::async::task< std::shared_ptr< ShallowScrubReport > > submit_scrub_task(const pg_id_t& pg_id, const bool is_deep, SCRUB_TRIGGER_TYPE trigger_type = SCRUB_TRIGGER_TYPE::PERIODICALLY); @@ -297,18 +298,19 @@ class ScrubManager { struct scrub_task { scrub_task(uint64_t last_scrub_time, pg_id_t pg_id, bool is_deep_scrub, SCRUB_TRIGGER_TYPE trigger_type, - folly::Promise< std::shared_ptr< ShallowScrubReport > > promise) : + std::shared_ptr< sisl::async::value_awaitable< std::shared_ptr< ShallowScrubReport > > > promise) : task_id{scrub_task_id.fetch_add(1)}, last_scrub_time{last_scrub_time}, pg_id{pg_id}, is_deep_scrub{is_deep_scrub}, triggered{trigger_type}, - scrub_report_promise{ - std::make_shared< folly::Promise< std::shared_ptr< ShallowScrubReport > > >(std::move(promise))} {} + scrub_report_promise{promise} {} ~scrub_task() { - if (scrub_report_promise && !scrub_report_promise->isFulfilled()) { - scrub_report_promise->setValue(nullptr); + // Safety net for tasks that never reach scrub_task_guard (e.g. queue closed on push). + // Successful paths complete via scrub_task_guard; await_ready() makes this a no-op then. + if (scrub_report_promise && !scrub_report_promise->await_ready()) { + scrub_report_promise->complete(nullptr); } } @@ -322,7 +324,7 @@ class ScrubManager { pg_id_t pg_id; bool is_deep_scrub; SCRUB_TRIGGER_TYPE triggered; - std::shared_ptr< folly::Promise< std::shared_ptr< ShallowScrubReport > > > scrub_report_promise; + std::shared_ptr< sisl::async::value_awaitable< std::shared_ptr< ShallowScrubReport > > > scrub_report_promise; bool operator==(const scrub_task& other) const noexcept { return task_id == other.task_id; } @@ -360,20 +362,19 @@ class ScrubManager { void handle_deep_pg_scrub_report(std::shared_ptr< DeepScrubReport > report); void handle_shallow_pg_scrub_report(std::shared_ptr< ShallowScrubReport > report); void handle_scrub_req(std::shared_ptr< scrub_req > req); - bool wait_for_scrub_lsn_commit(shared< homestore::ReplDev > repl_dev, int64_t scrub_lsn); + bool wait_for_scrub_lsn_commit(shared< homestore::repl_dev > repl_dev, int64_t scrub_lsn); uint64_t compute_crc64(const void* data, size_t len, uint64_t crc = 0) const; void check_scrub_timeouts(); iomgr::timer_handle_t m_scrub_timer_hdl{iomgr::null_timer_handle}; iomgr::timer_handle_t m_retry_timer_hdl{iomgr::null_timer_handle}; - iomgr::io_fiber_t m_scrub_timer_fiber{nullptr}; HSHomeObject* m_hs_home_object{nullptr}; - MPMCPriorityQueue< scrub_task > m_scrub_task_queue; - std::shared_ptr< folly::IOThreadPoolExecutor > m_scrub_executor; - folly::ConcurrentHashMap< pg_id_t, std::shared_ptr< PGScrubContext > > m_pg_scrub_ctx_map; - folly::ConcurrentHashMap< pg_id_t, std::shared_ptr< homestore::superblk< pg_scrub_superblk > > > m_pg_scrub_sb_map; - - std::shared_ptr< folly::IOThreadPoolExecutor > m_scrub_req_executor; + sisl::MPMCPriorityQueue< scrub_task > m_scrub_task_queue; + std::shared_ptr< boost::asio::thread_pool > m_scrub_executor; + std::shared_ptr< boost::asio::thread_pool > m_scrub_req_executor; + boost::concurrent_flat_map< pg_id_t, std::shared_ptr< PGScrubContext > > m_pg_scrub_ctx_map; + boost::concurrent_flat_map< pg_id_t, std::shared_ptr< homestore::superblk< pg_scrub_superblk > > > + m_pg_scrub_sb_map; }; } // namespace homeobject diff --git a/src/lib/homestore_backend/snapshot_receive_handler.cpp b/src/lib/homestore_backend/snapshot_receive_handler.cpp index 4ea5cf0da..afbb9a4e0 100644 --- a/src/lib/homestore_backend/snapshot_receive_handler.cpp +++ b/src/lib/homestore_backend/snapshot_receive_handler.cpp @@ -1,3 +1,8 @@ +#include +#include +#include +#include +#include #include #include "hs_homeobject.hpp" @@ -8,9 +13,28 @@ #include namespace homeobject { + +namespace { +sisl::async::task< bool > ready_true() { co_return true; } + +#ifdef _PRERELEASE +sisl::async::task< iomgr::io_result > ready_io_err(std::errc e) { + co_return std::unexpected(std::make_error_condition(e)); +} +#endif + +sisl::async::task< iomgr::io_result > collect_all_futures(std::vector< sisl::async::task< iomgr::io_result > > futs) { + auto vf = co_await sisl::async::when_all(std::move(futs)); + for (auto const& r : vf) { + if (sisl_unlikely(!r)) { co_return r; } + } + co_return iomgr::io_result{0}; +} +} // namespace + HSHomeObject::SnapshotReceiveHandler::SnapshotReceiveHandler(HSHomeObject& home_obj, - shared< homestore::ReplDev > repl_dev) : - home_obj_(home_obj), repl_dev_(std::move(repl_dev)), cp_fut(folly::makeFuture< bool >(true)) {} + shared< homestore::repl_dev > repl_dev) : + home_obj_(home_obj), repl_dev_(std::move(repl_dev)), cp_fut{ready_true()} {} int HSHomeObject::SnapshotReceiveHandler::process_pg_snapshot_data(ResyncPGMetaData const& pg_meta) { // Init shard list @@ -35,8 +59,8 @@ int HSHomeObject::SnapshotReceiveHandler::process_pg_snapshot_data(ResyncPGMetaD pg_member.priority = member->priority(); pg_info.members.insert(pg_member); } - LOGD("Resync PG membership: pg={}, expected_members={}, members={}", pg_meta.pg_id(), - pg_info.expected_member_num, pg_meta.members()->size()) + LOGD("Resync PG membership: pg={}, expected_members={}, members={}", pg_meta.pg_id(), pg_info.expected_member_num, + pg_meta.members()->size()) #ifdef _PRERELEASE if (iomgr_flip::instance()->test_flip("snapshot_receiver_pg_error")) { @@ -45,7 +69,7 @@ int HSHomeObject::SnapshotReceiveHandler::process_pg_snapshot_data(ResyncPGMetaD } #endif auto ret = home_obj_.local_create_pg(repl_dev_, pg_info); - if (ret.hasError()) { + if (!ret) { LOGE("Failed to process resync PG metadata: pg={}, error={}", pg_meta.pg_id(), ret.error()); return CREATE_PG_ERR; } @@ -105,23 +129,11 @@ int HSHomeObject::SnapshotReceiveHandler::process_shard_snapshot_data(ResyncShar } ctx_->shard_cursor = shard_meta.shard_id(); ctx_->cur_batch_num = 0; - LOGI("Processed resync shard metadata: pg={}, shard_id=0x{:x}, state={}", shard_meta.pg_id(), - shard_meta.shard_id(), shard_meta.state()); + LOGI("Processed resync shard metadata: pg={}, shard_id=0x{:x}, state={}", shard_meta.pg_id(), shard_meta.shard_id(), + shard_meta.state()); return 0; } -static auto collect_all_futures(std::vector< folly::Future< std::error_code > >& futs) { - return folly::collectAllUnsafe(futs).thenValue([](auto&& vf) { - for (auto const& err_c : vf) { - if (sisl_unlikely(err_c.value())) { - auto ec = err_c.value(); - return folly::makeFuture< std::error_code >(std::move(ec)); - } - } - return folly::makeFuture< std::error_code >(std::error_code{}); - }); -} - int HSHomeObject::SnapshotReceiveHandler::process_blobs_snapshot_data(ResyncBlobDataBatch const& data_blobs, const snp_batch_id_t batch_num, bool is_last_batch) { @@ -152,7 +164,7 @@ int HSHomeObject::SnapshotReceiveHandler::process_blobs_snapshot_data(ResyncBlob uint64_t total_bytes = 0; - std::vector< folly::Future< std::error_code > > futs; + std::vector< sisl::async::task< iomgr::io_result > > futs; std::vector< std::shared_ptr< sisl::io_blob_safe > > data_bufs; auto skipped_blobs = 0; @@ -233,22 +245,23 @@ int HSHomeObject::SnapshotReceiveHandler::process_blobs_snapshot_data(ResyncBlob std::memcpy(aligned_buf->bytes(), blob_data, data_size); data_bufs.emplace_back(aligned_buf); - homestore::MultiBlkId blk_id; - homestore::BlkAllocStatus status; + homestore::multi_blk_id blk_id; + bool alloc_ok = false; #ifdef _PRERELEASE if (iomgr_flip::instance()->test_flip("snapshot_receiver_blk_allocation_error")) { LOGD("Simulating resync blob allocation failure: pg={}, shard_id=0x{:x}, blob={}", ctx_->pg_id, ctx_->shard_cursor, blob->blob_id()); - status = homestore::BlkAllocStatus::SPACE_FULL; - } else { - status = homestore::data_service().alloc_blks( - sisl::round_up(aligned_buf->size(), homestore::data_service().get_blk_size()), hints, blk_id); - } -#else - status = homestore::data_service().alloc_blks( - sisl::round_up(aligned_buf->size(), homestore::data_service().get_blk_size()), hints, blk_id); + } else #endif - if (status != homestore::BlkAllocStatus::SUCCESS) { + { + auto alloc_res = homestore::data_service().alloc_blks( + sisl::round_up(aligned_buf->size(), homestore::data_service().get_blk_size()), hints); + if (alloc_res) { + blk_id = std::move(*alloc_res); + alloc_ok = true; + } + } + if (!alloc_ok) { LOGE("Failed to allocate resync blob blocks: pg={}, shard_id=0x{:x}, blob={}", ctx_->pg_id, ctx_->shard_cursor, blob->blob_id()); std::unique_lock< std::shared_mutex > lock(ctx_->progress_lock); @@ -262,65 +275,68 @@ int HSHomeObject::SnapshotReceiveHandler::process_blobs_snapshot_data(ResyncBlob ctx_->shard_cursor, blob->blob_id()); std::unique_lock< std::shared_mutex > lock(ctx_->progress_lock); ctx_->progress.error_count++; - futs.emplace_back(folly::makeFuture< std::error_code >(std::make_error_code(std::errc::invalid_argument))); + futs.emplace_back(ready_io_err(std::errc::invalid_argument)); continue; } #endif auto blob_id = blob->blob_id(); - LOGT("Writing resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", ctx_->pg_id, ctx_->shard_cursor, - blob_id, blk_id.to_string()); + LOGT("Writing resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", ctx_->pg_id, ctx_->shard_cursor, blob_id, + blk_id.to_string()); // ToDo: limit the max concurrent? + // Pass values as coroutine parameters — a temporary IIFE lambda's captures dangle after }(). futs.emplace_back( - homestore::data_service() - .async_write(r_cast< char const* >(aligned_buf->cbytes()), aligned_buf->size(), blk_id) - .thenValue([this, blk_id, start, blob_id](auto&& err) -> folly::Future< std::error_code > { - // TODO: do we need to update repl_dev metrics? - if (err) { - LOGE("Failed to write resync blob; freeing block: pg={}, shard_id=0x{:x}, blob={}, blkid={}", - ctx_->pg_id, ctx_->shard_cursor, blob_id, blk_id.to_string()); - homestore::data_service().async_free_blk(blk_id).get(); - return err; - } - LOGT("Wrote resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", ctx_->pg_id, - ctx_->shard_cursor, blob_id, blk_id.to_string()); - - if (homestore::data_service().commit_blk(blk_id) != homestore::BlkAllocStatus::SUCCESS) { - LOGE("Failed to commit resync blob block: pg={}, shard_id=0x{:x}, blob={}, blkid={}", - ctx_->pg_id, ctx_->shard_cursor, blob_id, blk_id.to_string()); - homestore::data_service().async_free_blk(blk_id).get(); - return std::make_error_code(std::errc::io_error); - } - // Add local blob info to index & PG - bool success = - home_obj_.local_add_blob_info(ctx_->pg_id, BlobInfo{ctx_->shard_cursor, blob_id, blk_id}); - if (!success) { - LOGE("Failed to index resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", ctx_->pg_id, - ctx_->shard_cursor, blob_id, blk_id.to_string()); - homestore::data_service().async_free_blk(blk_id).get(); - return std::make_error_code(std::errc::io_error); - } - - auto duration = get_elapsed_time_us(start); - HISTOGRAM_OBSERVE(*metrics_, snp_rcvr_blob_process_time, duration); - LOGT("Persisted resync blob: pg={}, shard_id=0x{:x}, blob={}, duration_us={}", ctx_->pg_id, - ctx_->shard_cursor, blob_id, duration); - return std::error_code{}; - })); + [](SnapshotReceiveHandler* self, auto blk_id, auto start, auto blob_id, + std::shared_ptr< sisl::io_blob_safe > aligned_buf) -> sisl::async::task< iomgr::io_result > { + auto err = co_await homestore::data_service().async_write(r_cast< char const* >(aligned_buf->cbytes()), + aligned_buf->size(), blk_id); + // TODO: do we need to update repl_dev metrics? + if (!err) { + LOGE("Failed to write resync blob; freeing block: pg={}, shard_id=0x{:x}, blob={}, blkid={}", + self->ctx_->pg_id, self->ctx_->shard_cursor, blob_id, blk_id.to_string()); + co_await homestore::data_service().async_free_blk(blk_id); + co_return err; + } + LOGT("Wrote resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", self->ctx_->pg_id, + self->ctx_->shard_cursor, blob_id, blk_id.to_string()); + + if (!homestore::data_service().commit_blk(blk_id)) { + LOGE("Failed to commit resync blob block: pg={}, shard_id=0x{:x}, blob={}, blkid={}", + self->ctx_->pg_id, self->ctx_->shard_cursor, blob_id, blk_id.to_string()); + co_await homestore::data_service().async_free_blk(blk_id); + co_return std::unexpected(std::make_error_condition(std::errc::io_error)); + } + // Add local blob info to index & PG + bool success = self->home_obj_.local_add_blob_info(self->ctx_->pg_id, + BlobInfo{self->ctx_->shard_cursor, blob_id, blk_id}); + if (!success) { + LOGE("Failed to index resync blob: pg={}, shard_id=0x{:x}, blob={}, blkid={}", self->ctx_->pg_id, + self->ctx_->shard_cursor, blob_id, blk_id.to_string()); + co_await homestore::data_service().async_free_blk(blk_id); + co_return std::unexpected(std::make_error_condition(std::errc::io_error)); + } + + auto duration = get_elapsed_time_us(start); + HISTOGRAM_OBSERVE(*self->metrics_, snp_rcvr_blob_process_time, duration); + LOGT("Persisted resync blob: pg={}, shard_id=0x{:x}, blob={}, duration_us={}", self->ctx_->pg_id, + self->ctx_->shard_cursor, blob_id, duration); + co_return err; + }(this, blk_id, start, blob_id, std::move(aligned_buf))); } - auto ec = collect_all_futures(futs).get(); + auto submitted = futs.size(); + auto ec = sisl::async::sync_get(collect_all_futures(std::move(futs))); if (found_unexpected_corruption) { return BLOB_DATA_CORRUPTED; } // when there is a allocation failure it breaks the while loop earlier. - auto all_io_submitted = (futs.size() + skipped_blobs == data_blobs.blob_list()->size()); + auto all_io_submitted = (submitted + skipped_blobs == data_blobs.blob_list()->size()); - if (!all_io_submitted || ec != std::error_code{}) { + if (!all_io_submitted || !ec) { if (!all_io_submitted) { - LOGE( - "Failed to submit complete resync shard batch: pg={}, shard_id=0x{:x}, batch={}, expected_blobs={}, submitted_blobs={}", - ctx_->pg_id, ctx_->shard_cursor, batch_num, data_blobs.blob_list()->size(), futs.size()); + LOGE("Failed to submit complete resync shard batch: pg={}, shard_id=0x{:x}, batch={}, expected_blobs={}, " + "submitted_blobs={}", + ctx_->pg_id, ctx_->shard_cursor, batch_num, data_blobs.blob_list()->size(), submitted); } else { - LOGE("Failed to write resync shard batch: pg={}, shard_id=0x{:x}, batch={}, error_code={}, error={}", - ctx_->pg_id, ctx_->shard_cursor, batch_num, ec.value(), ec.message()); + LOGE("Failed to write resync shard batch: pg={}, shard_id=0x{:x}, batch={}, error={}", ctx_->pg_id, + ctx_->shard_cursor, batch_num, ec.error().message()); } std::unique_lock< std::shared_mutex > lock(ctx_->progress_lock); ctx_->progress.error_count++; @@ -459,7 +475,9 @@ shard_id_t HSHomeObject::SnapshotReceiveHandler::get_next_shard() const { void HSHomeObject::SnapshotReceiveHandler::update_snp_info_sb(bool init) { RELEASE_ASSERT(home_obj_.get_hs_pg(ctx_->pg_id) != nullptr, "PG not found, pg={}", ctx_->pg_id); // ensure previous cp finished. - std::move(cp_fut).get(); + RELEASE_ASSERT(cp_fut.has_value(), "cp_fut not initialized"); + sisl::async::sync_get(std::move(*cp_fut)); + cp_fut.reset(); // Copy current value of mutable field in context auto shard_cursor = get_next_shard(); @@ -478,41 +496,46 @@ void HSHomeObject::SnapshotReceiveHandler::update_snp_info_sb(bool init) { // Ensure all the superblk & corresponding index/data update have been written to disk // then update the superblock. - cp_fut = homestore::hs() - ->cp_mgr() - .trigger_cp_flush(true /* force */) - .thenValue([this, init, shard_cursor, progress](auto success) -> bool { - RELEASE_ASSERT(success, "CP flush failure"); - LOGINFO("Update snp_info sb, CP Flush {}", success ? "success" : "failed"); - auto hs_pg = home_obj_.get_hs_pg(ctx_->pg_id); - auto* sb = hs_pg->snp_rcvr_info_sb_.get(); - if (init) { - if (!hs_pg->snp_rcvr_info_sb_.is_empty()) { hs_pg->snp_rcvr_info_sb_.destroy(); } - if (!hs_pg->snp_rcvr_shard_list_sb_.is_empty()) { hs_pg->snp_rcvr_shard_list_sb_.destroy(); } - sb = hs_pg->snp_rcvr_info_sb_.create(sizeof(snapshot_rcvr_info_superblk)); - - auto lst_sb = - hs_pg->snp_rcvr_shard_list_sb_.create(sizeof(snapshot_rcvr_shard_list_superblk) + - (ctx_->shard_list.size() - 1) * sizeof(shard_id_t)); - lst_sb->pg_id = ctx_->pg_id; - lst_sb->snp_lsn = ctx_->snp_lsn; - lst_sb->shard_cnt = ctx_->shard_list.size(); - std::copy(ctx_->shard_list.begin(), ctx_->shard_list.end(), lst_sb->shard_list); - hs_pg->snp_rcvr_shard_list_sb_.write(); - } - RELEASE_ASSERT(sb != nullptr, "Snapshot info superblk not found"); - sb->snp_lsn = ctx_->snp_lsn; - sb->pg_id = ctx_->pg_id; - sb->shard_cursor = shard_cursor; - sb->progress = progress; - hs_pg->snp_rcvr_info_sb_.write(); - return success; - }); + // exec::task is move-constructible but not assignable, so use emplace (not optional=). + // Pass values as coroutine parameters — a temporary IIFE lambda's captures dangle after }(). + { + auto t = [](SnapshotReceiveHandler* self, bool init, auto shard_cursor, + auto progress) -> sisl::async::task< bool > { + auto success = co_await homestore::hs()->cp_mgr().trigger_cp_flush(true /* force */); + RELEASE_ASSERT(success, "CP flush failure"); + LOGINFO("Update snp_info sb, CP Flush {}", success ? "success" : "failed"); + auto hs_pg = self->home_obj_.get_hs_pg(self->ctx_->pg_id); + auto* sb = hs_pg->snp_rcvr_info_sb_.get(); + if (init) { + if (!hs_pg->snp_rcvr_info_sb_.is_empty()) { hs_pg->snp_rcvr_info_sb_.destroy(); } + if (!hs_pg->snp_rcvr_shard_list_sb_.is_empty()) { hs_pg->snp_rcvr_shard_list_sb_.destroy(); } + sb = hs_pg->snp_rcvr_info_sb_.create(sizeof(snapshot_rcvr_info_superblk)); + + auto lst_sb = + hs_pg->snp_rcvr_shard_list_sb_.create(sizeof(snapshot_rcvr_shard_list_superblk) + + (self->ctx_->shard_list.size() - 1) * sizeof(shard_id_t)); + lst_sb->pg_id = self->ctx_->pg_id; + lst_sb->snp_lsn = self->ctx_->snp_lsn; + lst_sb->shard_cnt = self->ctx_->shard_list.size(); + std::copy(self->ctx_->shard_list.begin(), self->ctx_->shard_list.end(), lst_sb->shard_list); + hs_pg->snp_rcvr_shard_list_sb_.write(); + } + RELEASE_ASSERT(sb != nullptr, "Snapshot info superblk not found"); + sb->snp_lsn = self->ctx_->snp_lsn; + sb->pg_id = self->ctx_->pg_id; + sb->shard_cursor = shard_cursor; + sb->progress = progress; + hs_pg->snp_rcvr_info_sb_.write(); + co_return success; + }(this, init, shard_cursor, progress); + cp_fut.emplace(std::move(t)); + } // sync wait for last shard before returning LAST_OBJ_ID. if (get_next_shard() == shard_list_end_marker) { - std::move(cp_fut).get(); - cp_fut = folly::makeFuture< bool >(true); + sisl::async::sync_get(std::move(*cp_fut)); + cp_fut.reset(); + cp_fut.emplace(ready_true()); } } diff --git a/src/lib/homestore_backend/tests/CMakeLists.txt b/src/lib/homestore_backend/tests/CMakeLists.txt index 8eceb3d1f..fc5701024 100644 --- a/src/lib/homestore_backend/tests/CMakeLists.txt +++ b/src/lib/homestore_backend/tests/CMakeLists.txt @@ -35,7 +35,3 @@ add_library(homestore_tests_scrubber OBJECT) target_sources(homestore_tests_scrubber PRIVATE test_homestore_backend.cpp hs_scrubber_tests.cpp) target_link_libraries(homestore_tests_scrubber homeobject_homestore ${COMMON_TEST_DEPS}) -add_executable(test_mpmc_priority_queue) -target_sources(test_mpmc_priority_queue PRIVATE test_mpmc_priority_queue.cpp) -target_link_libraries(test_mpmc_priority_queue homeobject_homestore ${COMMON_TEST_DEPS}) -add_test(NAME MPMCPriorityQueueTest COMMAND test_mpmc_priority_queue) diff --git a/src/lib/homestore_backend/tests/homeobj_fixture.hpp b/src/lib/homestore_backend/tests/homeobj_fixture.hpp index a66bbeeb0..05cf74584 100644 --- a/src/lib/homestore_backend/tests/homeobj_fixture.hpp +++ b/src/lib/homestore_backend/tests/homeobj_fixture.hpp @@ -1,4 +1,7 @@ +#include +#include #pragma once +#include #include #include #include @@ -15,7 +18,6 @@ #include "bits_generator.hpp" #include "hs_repl_test_helper.hpp" #include -#include #include SETTINGS_INIT(iomgrcfg::IomgrSettings, iomgr_config); @@ -85,7 +87,9 @@ class HomeObjectFixture : public ::testing::Test { LOGINFO("Stopping homeobject replica={}", g_helper->my_replica_id()); LOGINFO("Metrics={}", sisl::MetricsFarm::getInstance().get_result_in_json().dump(2)); _obj_inst.reset(); - g_helper->homeobj_.reset(); + // Must shutdown HomeStore (CPManager etc.) — resetting the shared_ptr alone leaves + // home_store::s_instance alive until process exit and trips ~CPManager after loggers die. + g_helper->shutdown_homeobject(); sleep(10); } @@ -136,7 +140,7 @@ class HomeObjectFixture : public ::testing::Test { info.members.insert(homeobject::PGMember{member.first, name + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); ASSERT_TRUE(p); LOGINFO("pg={} is created at leader", pg_id); } else { @@ -164,7 +168,7 @@ class HomeObjectFixture : public ::testing::Test { return id != INVALID_UINT64_ID && shard_exist(id, tid); }, [&]() -> bool { - auto s = _obj_inst->shard_manager()->create_shard(pg_id, size_bytes, meta, tid).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->create_shard(pg_id, size_bytes, meta, tid)); if (!s) { if (is_not_leader_error(s.error())) return false; RELEASE_ASSERT(false, "failed to create shard"); @@ -192,7 +196,7 @@ class HomeObjectFixture : public ::testing::Test { RELEASE_ASSERT(local_v_chunkID.has_value(), "failed to get shard v_chunk_id"); RELEASE_ASSERT(leader_v_chunk_id == local_v_chunkID, "v_chunk_id supposed to be identical"); - auto r = _obj_inst->shard_manager()->get_shard(shard_id, tid).get(); + auto r = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id, tid)); RELEASE_ASSERT(!!r, "failed to get shard {}", shard_id); return r.value(); } @@ -200,13 +204,13 @@ class HomeObjectFixture : public ::testing::Test { ShardInfo seal_shard(shard_id_t shard_id) { g_helper->sync(); auto tid = generateRandomTraceId(); - auto r = _obj_inst->shard_manager()->get_shard(shard_id, tid).get(); + auto r = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id, tid)); if (!r) return {}; auto pg_id = r.value().placement_group; std::optional< ShardInfo > sealed_opt; auto is_sealed = [&]() -> bool { - auto res = _obj_inst->shard_manager()->get_shard(shard_id, tid).get(); + auto res = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id, tid)); if (res && res.value().state == ShardInfo::State::SEALED) { sealed_opt = res.value(); return true; @@ -216,7 +220,7 @@ class HomeObjectFixture : public ::testing::Test { run_on_pg_leader_with_retry(pg_id, is_sealed, [&]() -> bool { if (is_sealed()) return true; // idempotent: already sealed, sealed_opt captured inside is_sealed - auto s = _obj_inst->shard_manager()->seal_shard(shard_id, tid).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->seal_shard(shard_id, tid)); if (!s) { if (is_not_leader_error(s.error())) return false; RELEASE_ASSERT(false, "failed to seal shard"); @@ -232,7 +236,7 @@ class HomeObjectFixture : public ::testing::Test { void put_blob(shard_id_t shard_id, Blob&& blob) { g_helper->sync(); auto tid = generateRandomTraceId(); - auto r = _obj_inst->shard_manager()->get_shard(shard_id, tid).get(); + auto r = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id, tid)); if (!r) return; auto pg_id = r.value().placement_group; @@ -247,7 +251,7 @@ class HomeObjectFixture : public ::testing::Test { return blob_id != INVALID_UINT64_ID && blob_exist(shard_id, blob_id); }, [&]() -> bool { - auto b = _obj_inst->blob_manager()->put(shard_id, blob_to_put.clone(), tid).get(); + auto b = sisl::async::sync_get(_obj_inst->blob_manager()->put(shard_id, blob_to_put.clone(), tid)); if (!b) { if (is_not_leader_error(b.error())) return false; RELEASE_ASSERT(false, "failed to put blob"); @@ -309,7 +313,8 @@ class HomeObjectFixture : public ::testing::Test { LOGDEBUG("Put blob pg={} shard {} blob {} size {} data {} trace_id={}", pg_id, shard_id, blob_id, put_blob.body.size(), hex_bytes(put_blob.body.cbytes(), std::min(10u, put_blob.body.size())), tid); - auto b = _obj_inst->blob_manager()->put(shard_id, std::move(put_blob), tid).get(); + auto b = + sisl::async::sync_get(_obj_inst->blob_manager()->put(shard_id, std::move(put_blob), tid)); if (!b) { if (is_not_leader_error(b.error())) return false; RELEASE_ASSERT(false, "Failed to put blob pg={} shard {} error={}", pg_id, shard_id, @@ -337,7 +342,7 @@ class HomeObjectFixture : public ::testing::Test { pg_id, [&] { return !blob_exist(shard_id, blob_id); }, [&]() -> bool { if (!blob_exist(shard_id, blob_id)) return true; // idempotent: already deleted - auto g = _obj_inst->blob_manager()->del(shard_id, blob_id, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->del(shard_id, blob_id, tid)); if (!g) { if (is_not_leader_error(g.error())) return false; RELEASE_ASSERT(false, "failed to del blob"); @@ -364,7 +369,7 @@ class HomeObjectFixture : public ::testing::Test { for (const auto& blob_id : blob_ids) { if (!blob_exist(shard_id, blob_id)) continue; // already deleted auto tid = generateRandomTraceId(); - auto g = _obj_inst->blob_manager()->del(shard_id, blob_id, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->del(shard_id, blob_id, tid)); if (!g) { if (is_not_leader_error(g.error())) return false; RELEASE_ASSERT(false, "failed to del blob"); @@ -404,7 +409,7 @@ class HomeObjectFixture : public ::testing::Test { for (auto& [shard_id, blob_id] : blob_order) { if (!blob_exist(shard_id, blob_id)) continue; // already deleted auto tid = generateRandomTraceId(); - auto g = _obj_inst->blob_manager()->del(shard_id, blob_id, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->del(shard_id, blob_id, tid)); if (!g) { if (is_not_leader_error(g.error())) return false; RELEASE_ASSERT(false, "failed to del blob"); @@ -495,16 +500,14 @@ class HomeObjectFixture : public ::testing::Test { allow_skip_verify = (bool)bool_dist(rnd_engine); } - auto g = _obj_inst->blob_manager() - ->get(shard_id, current_blob_id, off, len, allow_skip_verify, tid) - .get(); - while (wait_when_not_exist && g.hasError() && g.error().code == BlobErrorCode::UNKNOWN_BLOB) { + auto g = sisl::async::sync_get( + _obj_inst->blob_manager()->get(shard_id, current_blob_id, off, len, allow_skip_verify, tid)); + while (wait_when_not_exist && !g && g.error().code == BlobErrorCode::UNKNOWN_BLOB) { LOGDEBUG("blob not exist at the moment, waiting for sync, shard {} blob {}", shard_id, current_blob_id); wait_for_blob(shard_id, current_blob_id); - g = _obj_inst->blob_manager() - ->get(shard_id, current_blob_id, off, len, allow_skip_verify, tid) - .get(); + g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, current_blob_id, off, len, + allow_skip_verify, tid)); } ASSERT_TRUE(!!g) << "get blob fail, shard_id " << shard_id << " blob_id " << current_blob_id << " replica number " << g_helper->replica_num(); @@ -544,7 +547,7 @@ class HomeObjectFixture : public ::testing::Test { if ((off + len) >= blob.body.size()) { len = blob.body.size() - off; } } - auto g = _obj_inst->blob_manager()->get(shard_id, blob_id, off, len, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id, off, len, tid)); ASSERT_TRUE(!!g) << "get blob fail, shard_id " << shard_id << " blob_id " << blob_id << " replica number " << g_helper->replica_num(); auto result = std::move(g.value()); @@ -588,7 +591,7 @@ class HomeObjectFixture : public ::testing::Test { ASSERT_FALSE(pg_exist(pg_id)); ASSERT_EQ(_obj_inst->index_table_pg_map_.find(index_table_uuid_str), _obj_inst->index_table_pg_map_.end()); // check shards - auto e = _obj_inst->shard_manager()->list_shards(pg_id).get(); + auto e = sisl::async::sync_get(_obj_inst->shard_manager()->list_shards(pg_id)); ASSERT_EQ(e.error().getCode(), ShardErrorCode::UNKNOWN_PG); for (const auto& shard_id : shard_id_vec) { ASSERT_FALSE(shard_exist(shard_id)); @@ -675,7 +678,7 @@ class HomeObjectFixture : public ::testing::Test { } run_on_pg_leader(pg_id, [this, pg_id, &task_id, &out_member, &in_member]() { std::vector< PGMember > others; - for (auto m : g_helper->members_) { + for (auto m : g_helper->members()) { if (m.first != out_member.id && m.first != in_member.id) { others.emplace_back(PGMember(m.first, "")); } } auto result = _obj_inst->get_replace_member_status(pg_id, task_id, out_member, in_member, others, 0); @@ -721,7 +724,7 @@ class HomeObjectFixture : public ::testing::Test { run_on_pg_leader(pg_id, [this, pg_id, &task_id, &out_member, &in_member]() { std::vector< PGMember > others; - for (auto m : g_helper->members_) { + for (auto m : g_helper->members()) { if (m.first != out_member.id && m.first != in_member.id) { others.emplace_back(PGMember(m.first, "")); } } auto result = _obj_inst->get_replace_member_status(pg_id, task_id, out_member, in_member, others, 0); @@ -755,7 +758,7 @@ class HomeObjectFixture : public ::testing::Test { // verify task auto r = _obj_inst->pg_manager()->list_all_replace_member_tasks(0); - RELEASE_ASSERT(r.hasValue(), "Failed to list_all_replace_member_tasks"); + RELEASE_ASSERT((bool)r, "Failed to list_all_replace_member_tasks"); const auto& tasks = r.value(); bool found = std::any_of(tasks.cbegin(), tasks.cend(), [&task_id](const homeobject::replace_member_task& task) { return task.task_id == task_id; @@ -788,7 +791,8 @@ class HomeObjectFixture : public ::testing::Test { if (!_obj_inst->pg_manager()->get_stats(pg_id, pg_stats)) return nullptr; if (g_helper->my_replica_id() != pg_stats.leader_id) return nullptr; - auto report = scrub_mgr->submit_scrub_task(pg_id, is_deep, SCRUB_TRIGGER_TYPE::MANUALLY).get(); + auto report = + sisl::async::sync_get(scrub_mgr->submit_scrub_task(pg_id, is_deep, SCRUB_TRIGGER_TYPE::MANUALLY)); if (report) return report; // null means the task was cancelled (leader switch); re-check leadership and retry. @@ -957,12 +961,12 @@ class HomeObjectFixture : public ::testing::Test { } bool shard_exist(shard_id_t id, trace_id_t tid = 0) { - auto r = _obj_inst->shard_manager()->get_shard(id, tid).get(); + auto r = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(id, tid)); return !!r; } bool blob_exist(shard_id_t shard_id, blob_id_t blob_id) { - auto r = _obj_inst->blob_manager()->get(shard_id, blob_id).get(); + auto r = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id)); return !!r; } @@ -974,9 +978,9 @@ class HomeObjectFixture : public ::testing::Test { }; if (wait) { - on_complete(std::move(fut).get()); + on_complete(sisl::async::sync_get(std::move(fut))); } else { - std::move(fut).thenValue(on_complete); + sisl::async::detach_then(std::move(fut), on_complete); } } @@ -1023,7 +1027,7 @@ class HomeObjectFixture : public ::testing::Test { flip::FlipFrequency freq; freq.set_count(count); freq.set_percent(percent); - m_fc.inject_noreturn_flip(flip_name, {null_cond}, freq); + m_fc.inject_noreturn_flip(flip_name, std::array< flip::FlipCondition, 1 >{null_cond}, freq); LOGINFO("Flip {} set", flip_name); } @@ -1033,7 +1037,7 @@ class HomeObjectFixture : public ::testing::Test { flip::FlipFrequency freq; freq.set_count(count); freq.set_percent(percent); - ASSERT_TRUE(m_fc.inject_retval_flip(flip_name, {cond}, freq, retval)); + ASSERT_TRUE(m_fc.inject_retval_flip(flip_name, std::array< flip::FlipCondition, 1 >{cond}, freq, retval)); LOGINFO("Flip {} with returned value set, value={}", flip_name, retval); } @@ -1042,7 +1046,7 @@ class HomeObjectFixture : public ::testing::Test { flip::FlipFrequency freq; freq.set_count(count); freq.set_percent(percent); - m_fc.inject_delay_flip(flip_name, {null_cond}, freq, delay_usec); + m_fc.inject_delay_flip(flip_name, std::array< flip::FlipCondition, 1 >{null_cond}, freq, delay_usec); LOGINFO("Flip {} set", flip_name); } @@ -1052,7 +1056,7 @@ class HomeObjectFixture : public ::testing::Test { flip::FlipFrequency freq; freq.set_count(count); freq.set_percent(percent); - m_fc.inject_callback_flip(flip_name, {null_cond}, freq, callback); + m_fc.inject_callback_flip(flip_name, std::array< flip::FlipCondition, 1 >{null_cond}, freq, callback); LOGINFO("Flip {} with callback set", flip_name); } @@ -1063,7 +1067,8 @@ class HomeObjectFixture : public ::testing::Test { flip::FlipFrequency freq; freq.set_count(count); freq.set_percent(percent); - ASSERT_TRUE(m_fc.inject_callback_retval_flip(flip_name, {null_cond}, freq, callback)); + ASSERT_TRUE(m_fc.inject_callback_retval_flip(flip_name, std::array< flip::FlipCondition, 1 >{null_cond}, freq, + callback)); LOGINFO("Flip {} with callback retval set", flip_name); } diff --git a/src/lib/homestore_backend/tests/homeobj_fixture_http.hpp b/src/lib/homestore_backend/tests/homeobj_fixture_http.hpp index 1a9725502..8e43bc9e9 100644 --- a/src/lib/homestore_backend/tests/homeobj_fixture_http.hpp +++ b/src/lib/homestore_backend/tests/homeobj_fixture_http.hpp @@ -1,47 +1,61 @@ #pragma once -#include -#include -#include +#include +#include +#include +namespace homeobject { + +// Thin synchronous httplib client (formerly Pistache Experimental::Client). class HttpHelper { public: - HttpHelper(const std::string& host, uint16_t port) { - auto opts = Pistache::Http::Experimental::Client::options().threads(1).maxConnectionsPerHost(8); - client_.init(opts); - server_addr_ = "http://" + host + ":" + std::to_string(port); + struct Response { + int status{0}; + std::string body; + + int code() const { return status; } + std::string const& body_str() const { return body; } + }; + + HttpHelper(const std::string& host, uint16_t port) : client_{host, static_cast< int >(port)} { + client_.set_connection_timeout(5, 0); + client_.set_read_timeout(30, 0); } - ~HttpHelper() { client_.shutdown(); } + ~HttpHelper() = default; - Pistache::Http::Response get(const std::string& resource) { - auto resp = client_.get(server_addr_ + resource).send(); - Pistache::Http::Response response; - resp.then([&](Pistache::Http::Response r) { response = r; }, Pistache::Async::Throw); - Pistache::Async::Barrier< Pistache::Http::Response > barrier(resp); - barrier.wait(); + Response get(const std::string& resource) { + Response response; + auto res = client_.Get(resource); + if (res) { + response.status = res->status; + response.body = res->body; + } return response; } - Pistache::Http::Response post(const std::string& resource, const std::string& body) { - auto resp = client_.post(server_addr_ + resource).body(body).send(); - Pistache::Http::Response response; - resp.then([&](Pistache::Http::Response r) { response = r; }, Pistache::Async::Throw); - Pistache::Async::Barrier< Pistache::Http::Response > barrier(resp); - barrier.wait(); + Response post(const std::string& resource, const std::string& body) { + Response response; + auto res = client_.Post(resource, body, "application/json"); + if (res) { + response.status = res->status; + response.body = res->body; + } return response; } - Pistache::Http::Response del(const std::string& resource, const std::string& body) { - auto resp = client_.del(server_addr_ + resource).body(body).send(); - Pistache::Http::Response response; - resp.then([&](Pistache::Http::Response r) { response = r; }, Pistache::Async::Throw); - Pistache::Async::Barrier< Pistache::Http::Response > barrier(resp); - barrier.wait(); + Response del(const std::string& resource, const std::string& body) { + Response response; + auto res = client_.Delete(resource, body, "application/json"); + if (res) { + response.status = res->status; + response.body = res->body; + } return response; } private: - Pistache::Http::Experimental::Client client_; - std::string server_addr_; + httplib::Client client_; }; + +} // namespace homeobject diff --git a/src/lib/homestore_backend/tests/homeobj_misc_tests.cpp b/src/lib/homestore_backend/tests/homeobj_misc_tests.cpp index 6848063dc..0d9e46a42 100644 --- a/src/lib/homestore_backend/tests/homeobj_misc_tests.cpp +++ b/src/lib/homestore_backend/tests/homeobj_misc_tests.cpp @@ -178,7 +178,7 @@ TEST_F(HomeObjectFixture, PGBlobIterator) { auto blob_data = b->data()->Data(); auto blob_header = r_cast< HSHomeObject::BlobHeader const* >(blob_data); ASSERT_TRUE(blob_header->valid()); - auto g = _obj_inst->blob_manager()->get(shard->info.id, b->blob_id(), 0, 0).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard->info.id, b->blob_id(), 0, 0)); ASSERT_TRUE(!!g); auto result = std::move(g.value()); EXPECT_EQ(result.body.size(), blob_header->blob_size); @@ -313,7 +313,7 @@ TEST_F(HomeObjectFixture, PGBlobIteratorGCMoveDetection) { // Get shard_2's blob_1 pbas — its blob header has shard_2_id, so verify_blob against shard_1_id will fail. auto index_table = _obj_inst->get_index_table(pg_id); auto shard_2_blob_pbas = _obj_inst->get_blob_from_index_table(index_table, shard_2_id, 1 /* blob_id */); - ASSERT_TRUE(shard_2_blob_pbas.hasValue()); + ASSERT_TRUE((bool)shard_2_blob_pbas); ASSERT_NE(shard_2_blob_pbas.value(), correct_pbas); // Inject stale pbas (simulating GC moved blob_0 to a new location already reflected in the @@ -375,7 +375,7 @@ TEST_F(HomeObjectFixture, PGBlobIteratorGCTombstoneDetection) { // Get shard_2's blob_1 pbas to inject as a stale blkid for blob_0 in shard_1. auto index_table = _obj_inst->get_index_table(pg_id); auto shard_2_blob_pbas = _obj_inst->get_blob_from_index_table(index_table, shard_2_id, 1 /* blob_id */); - ASSERT_TRUE(shard_2_blob_pbas.hasValue()); + ASSERT_TRUE((bool)shard_2_blob_pbas); // Inject stale pbas and tombstone blob_0 so the index lookup returns UNKNOWN_BLOB. // Flow: read from shard_2's location → verify_blob fails (shard_id mismatch) → @@ -484,8 +484,8 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandler) { ASSERT_TRUE(pg != nullptr); PGStats stats; ASSERT_TRUE(_obj_inst->pg_manager()->get_stats(pg_id, stats)); - auto r_dev = homestore::HomeStore::instance()->repl_service().get_repl_dev(stats.replica_set_uuid); - ASSERT_TRUE(r_dev.hasValue()); + auto r_dev = homestore::hs()->repl_service().get_repl_dev(stats.replica_set_uuid); + ASSERT_TRUE((bool)r_dev); auto handler = std::make_unique< homeobject::HSHomeObject::SnapshotReceiveHandler >(*_obj_inst, r_dev.value()); handler->reset_context_and_metrics(snp_lsn, pg_id); @@ -508,10 +508,9 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandler) { for (uint64_t i = 1; i <= num_shards_per_pg; i++) { shard_ids.push_back(i); } - auto pg_entry = - CreateResyncPGMetaDataDirect(builder, pg_id, &uuid, pg->pg_info_.size, pg->pg_info_.expected_member_num, - pg->pg_info_.chunk_size, blob_seq_num, num_shards_per_pg, &members, &shard_ids, - blob_seq_num /* total_blobs_to_transfer */); + auto pg_entry = CreateResyncPGMetaDataDirect( + builder, pg_id, &uuid, pg->pg_info_.size, pg->pg_info_.expected_member_num, pg->pg_info_.chunk_size, + blob_seq_num, num_shards_per_pg, &members, &shard_ids, blob_seq_num /* total_blobs_to_transfer */); builder.Finish(pg_entry); auto pg_meta = GetResyncPGMetaData(builder.GetBufferPointer()); auto ret = handler->process_pg_snapshot_data(*pg_meta); @@ -562,7 +561,7 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandler) { ASSERT_EQ(handler->get_next_shard(), i == num_shards_per_pg ? HSHomeObject::SnapshotReceiveHandler::shard_list_end_marker : i + 1); - auto res = _obj_inst->shard_manager()->get_shard(shard.id).get(); + auto res = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard.id)); ASSERT_TRUE(!!res); auto shard_res = std::move(res.value()); ASSERT_EQ(shard_res.id, shard.id); @@ -672,7 +671,8 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandler) { builder, retry_blob_id, static_cast< uint8_t >(ResyncBlobState::NORMAL), &retry_data)); blob_map[retry_blob_id] = std::make_tuple< Blob, bool >(std::move(retry_blob), false); } - builder.Finish(CreateResyncBlobDataBatchDirect(builder, &retry_blob_entries, j == num_batches_per_shard)); + builder.Finish( + CreateResyncBlobDataBatchDirect(builder, &retry_blob_entries, j == num_batches_per_shard)); auto retry_blob_batch = GetResyncBlobDataBatch(builder.GetBufferPointer()); ASSERT_EQ(handler->process_blobs_snapshot_data(*retry_blob_batch, j, j == num_batches_per_shard), 0); } else { @@ -692,7 +692,7 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandler) { auto& blob = std::get< 0 >(b.second); auto is_corrupted = std::get< 1 >(b.second); - auto result = _obj_inst->blob_manager()->get(shard.id, blob_id, 0, blob.body.size()).get(); + auto result = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard.id, blob_id, 0, blob.body.size())); if (is_corrupted) { ASSERT_FALSE(!!result); } else { @@ -743,8 +743,8 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { ASSERT_NE(pg, nullptr); PGStats stats; ASSERT_TRUE(_obj_inst->pg_manager()->get_stats(pg_id, stats)); - auto r_dev = homestore::HomeStore::instance()->repl_service().get_repl_dev(stats.replica_set_uuid); - ASSERT_TRUE(r_dev.hasValue()); + auto r_dev = homestore::hs()->repl_service().get_repl_dev(stats.replica_set_uuid); + ASSERT_TRUE((bool)r_dev); auto handler = std::make_unique< homeobject::HSHomeObject::SnapshotReceiveHandler >(*_obj_inst, r_dev.value()); handler->reset_context_and_metrics(snp_lsn, pg_id); @@ -759,10 +759,9 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { members.push_back(CreateMemberDirect(builder, &id, member.name.c_str(), priority)); } std::vector< uint64_t > shard_ids = {1}; - auto pg_entry = CreateResyncPGMetaDataDirect(builder, pg_id, &uuid, pg->pg_info_.size, - pg->pg_info_.expected_member_num, pg->pg_info_.chunk_size, - num_blobs_total /*blob_seq_num*/, 1 /*shard_seq_num*/, &members, - &shard_ids); + auto pg_entry = CreateResyncPGMetaDataDirect( + builder, pg_id, &uuid, pg->pg_info_.size, pg->pg_info_.expected_member_num, pg->pg_info_.chunk_size, + num_blobs_total /*blob_seq_num*/, 1 /*shard_seq_num*/, &members, &shard_ids); builder.Finish(pg_entry); ASSERT_EQ(handler->process_pg_snapshot_data(*GetResyncPGMetaData(builder.GetBufferPointer())), 0); builder.Reset(); @@ -816,7 +815,8 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { uint64_t total_bytes{0}; for (blob_id_t blob_id = start_blob_id; blob_id < end_blob_id; blob_id++) { auto blob = build_blob(blob_id); - total_bytes += sisl::round_up(sizeof(HSHomeObject::BlobHeader), _obj_inst->_data_block_size) + blob.body.size(); + total_bytes += + sisl::round_up(sizeof(HSHomeObject::BlobHeader), _obj_inst->_data_block_size) + blob.body.size(); } return total_bytes; }; @@ -836,11 +836,11 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { // Confirm only the partial set is readable and indexed for (blob_id_t blob_id = 0; blob_id < num_blobs_partial; blob_id++) { - auto result = _obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0).get(); + auto result = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0)); ASSERT_TRUE(!!result) << "blob_id=" << blob_id << " should be readable after partial delivery"; } for (blob_id_t blob_id = num_blobs_partial; blob_id < num_blobs_total; blob_id++) { - auto result = _obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0).get(); + auto result = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0)); ASSERT_FALSE(!!result) << "blob_id=" << blob_id << " should NOT exist before full delivery"; } @@ -848,10 +848,10 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { // protect during re-delivery so new blobs don't overwrite them. auto index_table = _obj_inst->get_hs_pg(pg_id)->index_table_; ASSERT_NE(index_table, nullptr); - std::vector< homestore::MultiBlkId > partial_blk_ids; + std::vector< homestore::multi_blk_id > partial_blk_ids; for (blob_id_t blob_id = 0; blob_id < num_blobs_partial; blob_id++) { auto res = _obj_inst->get_blob_from_index_table(index_table, shard_id, blob_id); - ASSERT_TRUE(res.hasValue()) << "blob_id=" << blob_id << " must be in index after partial delivery"; + ASSERT_TRUE((bool)res) << "blob_id=" << blob_id << " must be in index after partial delivery"; partial_blk_ids.push_back(res.value()); } @@ -911,12 +911,12 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { // ---- Verify no overlap: a fresh alloc must land ABOVE all blobs (partial + new) ---- // Core safety check: without the fix, alloc_blks() returns blk 0 (watermark stayed 0), // overlapping the data written for blobs 0..num_blobs_partial-1 in the first delivery. - homestore::MultiBlkId new_blk_id; + homestore::multi_blk_id new_blk_id; homestore::blk_alloc_hints hints; hints.chunk_id_hint = *p_chunk_id; - auto alloc_status = - homestore::data_service().alloc_blks(homestore::data_service().get_blk_size(), hints, new_blk_id); - ASSERT_EQ(alloc_status, homestore::BlkAllocStatus::SUCCESS); + auto alloc_res = homestore::data_service().alloc_blks(homestore::data_service().get_blk_size(), hints); + ASSERT_TRUE(alloc_res.has_value()); + new_blk_id = std::move(*alloc_res); for (const auto& existing_blk : partial_blk_ids) { auto existing_end = existing_blk.blk_num() + existing_blk.blk_count(); @@ -924,14 +924,14 @@ TEST_F(HomeObjectFixture, SnapshotReceiveHandlerAllocatorResyncAfterCrash) { << "new allocation at blk " << new_blk_id.blk_num() << " must not overlap partial blob at blks [" << existing_blk.blk_num() << ", " << existing_end << ")"; } - homestore::data_service().async_free_blk(new_blk_id).get(); + sisl::async::sync_get(homestore::data_service().async_free_blk(new_blk_id)); // ---- Verify all blobs are readable with correct content ---- // Blobs 0..num_blobs_partial-1: must retain the data from the first delivery (not overwritten // by the new-blob allocations in the second delivery). // Blobs num_blobs_partial..num_blobs_total-1: must be readable as newly written. for (blob_id_t blob_id = 0; blob_id < num_blobs_total; blob_id++) { - auto result = _obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0).get(); + auto result = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id, 0, 0)); ASSERT_TRUE(!!result) << "blob_id=" << blob_id << " must be readable after full delivery"; auto expected = build_blob(blob_id); ASSERT_EQ(result->body.size(), expected.body.size()); diff --git a/src/lib/homestore_backend/tests/hs_blob_tests.cpp b/src/lib/homestore_backend/tests/hs_blob_tests.cpp index 3429a8719..a3bd7cb5d 100644 --- a/src/lib/homestore_backend/tests/hs_blob_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_blob_tests.cpp @@ -76,7 +76,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithRestart) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { auto tid = generateRandomTraceId(); - auto g = _obj_inst->blob_manager()->del(shard_id, blob_id, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->del(shard_id, blob_id, tid)); ASSERT_TRUE(g); LOGINFO("delete blob shard {} blob {}, trace_id={}", shard_id, blob_id, tid); blob_id++; @@ -92,7 +92,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithRestart) { for (; blob_id != pg_blob_id[pg_id];) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { - auto g = _obj_inst->blob_manager()->get(shard_id, blob_id).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id)); ASSERT_TRUE(!g); blob_id++; } @@ -125,7 +125,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithRestart) { for (; blob_id != pg_blob_id[pg_id];) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { - auto g = _obj_inst->blob_manager()->get(shard_id, blob_id).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id)); ASSERT_TRUE(!g); blob_id++; } @@ -275,7 +275,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithDiskBack) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { auto tid = generateRandomTraceId(); - auto g = _obj_inst->blob_manager()->del(shard_id, blob_id, tid).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->del(shard_id, blob_id, tid)); ASSERT_TRUE(g); LOGINFO("delete blob shard {} blob {}, trace_id={}", shard_id, blob_id, tid); blob_id++; @@ -291,7 +291,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithDiskBack) { for (; blob_id != pg_blob_id[pg_id];) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { - auto g = _obj_inst->blob_manager()->get(shard_id, blob_id).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id)); ASSERT_TRUE(!g); blob_id++; } @@ -326,7 +326,7 @@ TEST_F(HomeObjectFixture, BasicPutGetDelBlobWithDiskBack) { for (; blob_id != pg_blob_id[pg_id];) { for (const auto& shard_id : shard_vec) { for (uint64_t k = 0; k < num_blobs_per_shard; k++) { - auto g = _obj_inst->blob_manager()->get(shard_id, blob_id).get(); + auto g = sisl::async::sync_get(_obj_inst->blob_manager()->get(shard_id, blob_id)); ASSERT_TRUE(!g); blob_id++; } @@ -389,10 +389,10 @@ TEST_F(HomeObjectFixture, BasicPutGetDelOnAllPGWithDiskLost) { current_blob_id, tid); auto blob = build_blob(current_blob_id); len = blob.body.size(); - auto g = _obj_inst->blob_manager()->get(shard_id, current_blob_id, off, len, false, tid).get(); - ASSERT_TRUE(g.hasError()) - << "degraded pg on error member should return get blob fail, shard_id " << shard_id << " blob_id " - << current_blob_id << " replica number " << g_helper->replica_num(); + auto g = sisl::async::sync_get( + _obj_inst->blob_manager()->get(shard_id, current_blob_id, off, len, false, tid)); + ASSERT_TRUE(!g) << "degraded pg on error member should return get blob fail, shard_id " << shard_id + << " blob_id " << current_blob_id << " replica number " << g_helper->replica_num(); current_blob_id++; } } @@ -406,10 +406,9 @@ TEST_F(HomeObjectFixture, BasicPutGetDelOnAllPGWithDiskLost) { put_shard_id, current_blob_id, put_blob.body.size(), hex_bytes(put_blob.body.cbytes(), std::min(10u, put_blob.body.size())), tid); - auto b = _obj_inst->blob_manager()->put(put_shard_id, std::move(put_blob), tid).get(); - ASSERT_TRUE(b.hasError()) << "degraded pg on error member should return put blob fail, shard_id " - << put_shard_id << " blob_id " << current_blob_id << " replica number " - << g_helper->replica_num(); + auto b = sisl::async::sync_get(_obj_inst->blob_manager()->put(put_shard_id, std::move(put_blob), tid)); + ASSERT_TRUE(!b) << "degraded pg on error member should return put blob fail, shard_id " << put_shard_id + << " blob_id " << current_blob_id << " replica number " << g_helper->replica_num(); blob_id_t delete_blob_id{0}; auto delete_shard_id = pg_shard_id_vec[degrade_pg_id].back(); @@ -419,10 +418,9 @@ TEST_F(HomeObjectFixture, BasicPutGetDelOnAllPGWithDiskLost) { delete_shard_id, delete_blob_id, delete_blob.body.size(), hex_bytes(delete_blob.body.cbytes(), std::min(10u, delete_blob.body.size())), tid); - auto d = _obj_inst->blob_manager()->del(delete_shard_id, delete_blob_id, tid).get(); - ASSERT_TRUE(d.hasError()) << "degraded pg on error member should return delete blob fail, shard_id " - << delete_shard_id << " blob_id " << delete_blob_id << " replica number " - << g_helper->replica_num(); + auto d = sisl::async::sync_get(_obj_inst->blob_manager()->del(delete_shard_id, delete_blob_id, tid)); + ASSERT_TRUE(!d) << "degraded pg on error member should return delete blob fail, shard_id " << delete_shard_id + << " blob_id " << delete_blob_id << " replica number " << g_helper->replica_num(); } else { restart(); sleep(10); diff --git a/src/lib/homestore_backend/tests/hs_gc_tests.cpp b/src/lib/homestore_backend/tests/hs_gc_tests.cpp index 72d3c36a4..64a950adc 100644 --- a/src/lib/homestore_backend/tests/hs_gc_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_gc_tests.cpp @@ -1,3 +1,5 @@ +#include +#include #include "homeobj_fixture.hpp" TEST_F(HomeObjectFixture, BasicGC) { @@ -136,7 +138,8 @@ TEST_F(HomeObjectFixture, BasicGC) { if (0 == EXVchunk->get_defrag_nblks()) { // some unexpect async write or free happens, increase defrag num to trigger gc again. - homestore::data_service().async_free_blk(homestore::MultiBlkId(0, 1, chunk_id)); + sisl::async::detach( + homestore::data_service().async_free_blk(homestore::multi_blk_id(0, 1, chunk_id))); } all_deleted_blobs_have_been_gc = false; @@ -268,7 +271,8 @@ TEST_F(HomeObjectFixture, BasicGC) { if (0 == EXVchunk->get_defrag_nblks()) { // some unexpect async write or free happens, increase defrag num to trigger gc again. - homestore::data_service().async_free_blk(homestore::MultiBlkId(0, 1, chunk_id)); + sisl::async::detach( + homestore::data_service().async_free_blk(homestore::multi_blk_id(0, 1, chunk_id))); } LOGINFO("pg_id={}, chunk_id={}, available_blk={}, total_blk={}, not empty, waiting for gc", pg_id, @@ -385,16 +389,17 @@ TEST_F(HomeObjectFixture, HandlingNoSpaceLeft) { ASSERT_TRUE(vchunk); auto available_blk_num = vchunk->available_blks(); - homestore::MultiBlkId all_remaining_blk; + homestore::multi_blk_id all_remaining_blk; homestore::blk_alloc_hints hints; hints.chunk_id_hint = chunk; // allocate all the remaining blocks, so that there is no space left on this chunk - const auto status = data_service.alloc_blks(available_blk_num * blk_size, hints, all_remaining_blk); + auto alloc_res = data_service.alloc_blks(available_blk_num * blk_size, hints); LOGINFO("Set chunk {} to no_space_left, total_blks={}, available_blks={}, used_blks={}", chunk, vchunk->get_total_blks(), vchunk->available_blks(), vchunk->get_used_blks()); - ASSERT_TRUE(status == homestore::BlkAllocStatus::SUCCESS); + ASSERT_TRUE(alloc_res.has_value()); + all_remaining_blk = std::move(*alloc_res); ASSERT_TRUE(vchunk->available_blks() == 0); } }); @@ -505,7 +510,7 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { // do not seal the last shard and trigger gc mannually to simulate emergent gc auto gc_mgr = _obj_inst->gc_manager(); - std::vector< folly::SemiFuture< bool > > futs; + std::vector< sisl::async::task< bool > > futs; if (with_crash_recovery) { const auto egc_thread_count_per_pdev = HS_BACKEND_DYNAMIC_CONFIG(reserved_chunk_num_per_pdev_for_egc); @@ -533,15 +538,10 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { } // wait for all egc completed - folly::collectAllUnsafe(futs) - .thenValue([](auto&& results) { - for (auto const& ok : results) { - ASSERT_TRUE(ok.hasValue()); - // all egc task should be completed. - ASSERT_TRUE(ok.value()); - } - }) - .get(); + auto __results = sisl::async::sync_get(sisl::async::when_all(std::move(futs))); + for (auto const& ok : __results) { + ASSERT_TRUE(ok); + } futs.clear(); @@ -570,15 +570,10 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { } // wait for all egc completed - folly::collectAllUnsafe(futs) - .thenValue([](auto&& results) { - for (auto const& ok : results) { - ASSERT_TRUE(ok.hasValue()); - // all egc task should be completed. - ASSERT_TRUE(ok.value()); - } - }) - .get(); + auto recovery_results = sisl::async::sync_get(sisl::async::when_all(std::move(futs))); + for (auto const& ok : recovery_results) { + ASSERT_TRUE(ok); + } futs.clear(); } @@ -706,15 +701,10 @@ void HomeObjectFixture::EmergentGC(bool with_crash_recovery) { } // wait for all egc completed - folly::collectAllUnsafe(futs) - .thenValue([](auto&& results) { - for (auto const& ok : results) { - ASSERT_TRUE(ok.hasValue()); - // all egc task should be completed - ASSERT_TRUE(ok.value()); - } - }) - .get(); + auto final_results = sisl::async::sync_get(sisl::async::when_all(std::move(futs))); + for (auto const& ok : final_results) { + ASSERT_TRUE(ok); + } futs.clear(); @@ -794,7 +784,7 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { chunk_id_t cur_chunk = get_current_chunk(); // Case 1: gc succeeds when all blob pbas match move_from_chunk. - ASSERT_TRUE(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk).get()) + ASSERT_TRUE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk))) << "emergent gc should succeed when all blob pbas match move_from_chunk"; cur_chunk = get_current_chunk(); @@ -804,13 +794,13 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { BlobRouteValue existing_value; BlobRouteKey index_key{BlobRoute{shard.id, 0 /* blob_id */}}; - BlobRouteValue wrong_value{homestore::MultiBlkId{0, 1, std::numeric_limits< chunk_id_t >::max()}}; + BlobRouteValue wrong_value{homestore::multi_blk_id{0, 1, std::numeric_limits< chunk_id_t >::max()}}; homestore::BtreeSinglePutRequest inject_req{&index_key, &wrong_value, homestore::btree_put_type::UPDATE, &existing_value}; ASSERT_EQ(hs_pg->index_table_->put(inject_req), homestore::btree_status_t::success) << "failed to inject wrong pba into pg index table"; - ASSERT_FALSE(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk).get()) + ASSERT_FALSE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk))) << "emergent gc should fail when a blob's pba chunk_id does not match move_from_chunk"; // Case 3: gc succeeds again after restoring the correct pba. @@ -819,7 +809,7 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { ASSERT_EQ(hs_pg->index_table_->put(restore_req), homestore::btree_status_t::success) << "failed to restore correct pba into pg index table"; - ASSERT_TRUE(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk).get()) + ASSERT_TRUE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::emergent, cur_chunk))) << "emergent gc should succeed after restoring correct blob pba"; seal_shard(shard.id); @@ -828,7 +818,7 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { del_blob(pg_id, shard.id, 1); // the same check for normal gc task. - ASSERT_TRUE(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk).get()) + ASSERT_TRUE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk))) << "normal gc should succeed when all blob pbas match move_from_chunk"; cur_chunk = get_current_chunk(); @@ -837,7 +827,7 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { del_blob(pg_id, shard.id, 2); - ASSERT_FALSE(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk).get()) + ASSERT_FALSE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk))) << "normal gc should fail when a blob's pba chunk_id does not match move_from_chunk"; homestore::BtreeSinglePutRequest new_restore_req{&index_key, &existing_value, homestore::btree_put_type::UPDATE, @@ -845,7 +835,7 @@ TEST_F(HomeObjectFixture, GCTaskPbaChunkCheck) { ASSERT_EQ(hs_pg->index_table_->put(new_restore_req), homestore::btree_status_t::success) << "failed to restore correct pba into pg index table"; - ASSERT_TRUE(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk).get()) + ASSERT_TRUE(sisl::async::sync_get(gc_mgr->submit_gc_task(task_priority::normal, cur_chunk))) << "normal gc should succeed after restoring correct blob pba"; } @@ -936,7 +926,7 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { // Flip 1: in SEAL_SHARD1 commit — spin until CREATE_SHARD2 log is in the log store before // release_chunk runs. Explicit guarantee that the race window actually exists. m_fc.inject_callback_flip< void, int64_t >( - "wait_create_shard_in_log", {dont_care}, freq, + "wait_create_shard_in_log", std::array< flip::FlipCondition, 1 >{dont_care}, freq, std::function< void(int64_t) >([&, repl_dev](int64_t seal_lsn) { while (repl_dev->get_last_append_lsn() <= seal_lsn) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); @@ -948,8 +938,8 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { // Flip 2: in CREATE_SHARD2 commit — pause before local_create_shard so GC can run in the race window. // NOTE: do NOT spin inside a commit callback (commit_ext runs on the iomgr I/O thread; // spinning there blocks log-append processing and causes deadlock in single-threaded executors). - m_fc.inject_callback_flip< void >("pause_create_shard_commit", {dont_care}, freq, - std::function< void() >([&]() { + m_fc.inject_callback_flip< void >("pause_create_shard_commit", std::array< flip::FlipCondition, 1 >{dont_care}, + freq, std::function< void() >([&]() { LOGI("[StalePChunkRouteAfterGC] pausing CREATE_SHARD commit"); std::unique_lock< std::mutex > lk(repro1_mtx); repro1_blocked.store(true); @@ -967,9 +957,9 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { shard_id_t shard2_id = INVALID_UINT64_ID; run_on_pg_leader(pg_id, [&]() { auto tid = generateRandomTraceId(); - auto sealed = _obj_inst->shard_manager()->seal_shard(shard1.id, tid).get(); + auto sealed = sisl::async::sync_get(_obj_inst->shard_manager()->seal_shard(shard1.id, tid)); RELEASE_ASSERT(!!sealed, "failed to seal shard1"); - auto created = _obj_inst->shard_manager()->create_shard(pg_id, 64 * Mi, "shard2", tid).get(); + auto created = sisl::async::sync_get(_obj_inst->shard_manager()->create_shard(pg_id, 64 * Mi, "shard2", tid)); RELEASE_ASSERT(!!created, "failed to create shard2"); g_helper->set_uint64_id(created.value().id); LOGINFO("[StalePChunkRouteAfterGC] leader sealed shard1=0x{:x} and created shard2=0x{:x}", shard1.id, @@ -999,7 +989,7 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { // Normal GC: chunk has garbage from deleted blobs (gc_garbage_rate_threshold=0 in the CTest entry). auto fut = _obj_inst->gc_manager()->submit_gc_task(task_priority::normal, pchunk_A.value()); - bool gc_ok = std::move(fut).get(); + bool gc_ok = sisl::async::sync_get(std::move(fut)); ASSERT_TRUE(gc_ok) << "normal GC on pchunk=" << pchunk_A.value() << " failed"; // release the gate: alloc_blks runs and resolves live pchunk B. @@ -1011,7 +1001,7 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { } // wait for shard2 to be created locally on every member. - while (!_obj_inst->shard_manager()->get_shard(shard2_id, 0).get()) { + while (!sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard2_id, 0))) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); } @@ -1073,13 +1063,14 @@ TEST_F(HomeObjectFixture, StalePChunkRouteAfterGC) { g_helper->sync(); run_on_pg_leader(pg_id, [&]() { - auto sealed2 = _obj_inst->shard_manager()->seal_shard(shard2_id, generateRandomTraceId()).get(); + auto sealed2 = + sisl::async::sync_get(_obj_inst->shard_manager()->seal_shard(shard2_id, generateRandomTraceId())); RELEASE_ASSERT(!!sealed2, "failed to seal shard2"); LOGINFO("[StalePChunkRouteAfterGC] leader sealed shard2=0x{:x}", shard2_id); }); while (true) { - auto s2 = _obj_inst->shard_manager()->get_shard(shard2_id, 0).get(); + auto s2 = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard2_id, 0)); if (s2 && s2.value().state == ShardInfo::State::SEALED) break; std::this_thread::sleep_for(std::chrono::milliseconds(200)); } @@ -1148,7 +1139,7 @@ TEST_F(HomeObjectFixture, StaleBlobRouteAfterSealAndGC) { freq.set_count(3); // 3 replicas all call callback_flip; count must be >= num_replicas freq.set_percent(100); m_fc.inject_callback_flip< void >( - "pause_seal_commit", {dont_care}, freq, std::function< void() >([&]() { + "pause_seal_commit", std::array< flip::FlipCondition, 1 >{dont_care}, freq, std::function< void() >([&]() { LOGI("[StaleBlobRouteAfterSealAndGC] pausing SEAL commit BEFORE state update"); std::unique_lock< std::mutex > lk(repro2_mtx); repro2_blocked.store(true); @@ -1169,8 +1160,8 @@ TEST_F(HomeObjectFixture, StaleBlobRouteAfterSealAndGC) { auto tid = generateRandomTraceId(); bool seal_ok = false; std::thread seal_thread([&]() { - auto r = std::move(_obj_inst->shard_manager()->seal_shard(shard1.id, tid)).get(); - seal_ok = r.hasValue(); + auto r = sisl::async::sync_get(std::move(_obj_inst->shard_manager()->seal_shard(shard1.id, tid))); + seal_ok = (bool)r; }); // 2. Wait until commit is paused (shard state is still OPEN). @@ -1192,10 +1183,9 @@ TEST_F(HomeObjectFixture, StaleBlobRouteAfterSealAndGC) { bool blob_rejected = false; std::thread blob_thread([&]() { auto blob = build_blob(num_blobs_per_shard); - auto b = std::move(_obj_inst->_put_blob(shard1, std::move(blob), tid)).get(); - blob_rejected = !b.hasValue(); - LOGINFO("[StaleBlobRouteAfterSealAndGC] leader: _put_blob result: {}", - b.hasValue() ? "admitted" : "rejected"); + auto b = sisl::async::sync_get(std::move(_obj_inst->_put_blob(shard1, std::move(blob), tid))); + blob_rejected = !(bool)b; + LOGINFO("[StaleBlobRouteAfterSealAndGC] leader: _put_blob result: {}", (bool)b ? "admitted" : "rejected"); }); // 4. Release the gate: state = SEALED, seal commit returns with sealed_lsn = lsn_seal. @@ -1243,7 +1233,7 @@ TEST_F(HomeObjectFixture, StaleBlobRouteAfterSealAndGC) { auto gc_mgr = _obj_inst->gc_manager(); auto gc_fut = gc_mgr->submit_gc_task(task_priority::normal, chunk_id); - bool gc_ok = std::move(gc_fut).get(); + bool gc_ok = sisl::async::sync_get(std::move(gc_fut)); ASSERT_TRUE(gc_ok) << "GC task failed on pchunk=" << chunk_id; g_helper->sync(); @@ -1278,7 +1268,7 @@ TEST_F(HomeObjectFixture, StaleBlobRouteAfterSealAndGC) { ASSERT_NE(index_table, nullptr) << "Failed to get index table for pg=" << pg_id; for (blob_id_t blob_id = remaining_start; blob_id < num_blobs_per_shard; ++blob_id) { auto pbas_result = _obj_inst->get_blob_from_index_table(index_table, shard1.id, blob_id); - ASSERT_TRUE(pbas_result.hasValue()) << "Failed to get blob pchunk for blob_id=" << blob_id << " after GC"; + ASSERT_TRUE((bool)pbas_result) << "Failed to get blob pchunk for blob_id=" << blob_id << " after GC"; ASSERT_EQ(pbas_result.value().chunk_num(), p_chunk_id_after_gc) << "Blob pchunk mismatch: blob_id=" << blob_id << " expected pchunk=" << p_chunk_id_after_gc << " actual=" << pbas_result.value().chunk_num(); diff --git a/src/lib/homestore_backend/tests/hs_pg_tests.cpp b/src/lib/homestore_backend/tests/hs_pg_tests.cpp index 37a9543f5..34d9c8ded 100644 --- a/src/lib/homestore_backend/tests/hs_pg_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_pg_tests.cpp @@ -8,7 +8,7 @@ TEST_F(HomeObjectFixture, PGStatsTest) { create_pg(pg_id); auto shard_info = create_shard(pg_id, 64 * Mi, "shard meta"); auto shard_id = shard_info.id; - auto s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); ASSERT_TRUE(!!s); LOGINFO("Got shard {}", shard_info.id); shard_info = s.value(); @@ -28,7 +28,7 @@ TEST_F(HomeObjectFixture, PGStatsTest) { // create a 2nd shard auto shard_info2 = create_shard(pg_id, 64 * Mi, "shard meta"); auto shard_id2 = shard_info2.id; - auto s2 = _obj_inst->shard_manager()->get_shard(shard_id2).get(); + auto s2 = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id2)); ASSERT_TRUE(!!s2); LOGINFO("Got shard {}", shard_id2); @@ -63,8 +63,8 @@ TEST_F(HomeObjectFixture, PGExceedSpaceTest) { info.members.insert(homeobject::PGMember{member.first, name + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); - ASSERT_TRUE(p.hasError()); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); + ASSERT_TRUE(!p); PGError error = p.error(); ASSERT_EQ(PGError::NO_SPACE_LEFT, error); } else { @@ -102,8 +102,8 @@ TEST_F(HomeObjectFixture, PGSizeLessThanChunkTest) { info.members.insert(homeobject::PGMember{member.first, name + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); - ASSERT_TRUE(p.hasError()); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); + ASSERT_TRUE(!p); PGError error = p.error(); ASSERT_EQ(PGError::INVALID_ARG, error); } else { @@ -247,7 +247,7 @@ TEST_F(HomeObjectFixture, PGRecoveryWithDiskLostTest) { // pg_blob_id[i] = 0; // auto shard_info = create_shard(i, 64 * Mi); // auto shard_id = shard_info.id; - // auto s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + // auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); // ASSERT_TRUE(!!s); // pg_shard_id_vec[i].emplace_back(shard_id); // LOGINFO("Created shard {} on pg={}", shard_info.id, i); @@ -313,7 +313,7 @@ TEST_F(HomeObjectFixture, DuplicateCreatePG) { homeobject::PGMember{member.first, g_helper->name() + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); ASSERT_FALSE(p); ASSERT_EQ(PGError::INVALID_ARG, p.error()); } @@ -332,7 +332,7 @@ TEST_F(HomeObjectFixture, DuplicateCreatePG) { info.members.insert(homeobject::PGMember{uuid, g_helper->name() + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); ASSERT_FALSE(p); ASSERT_EQ(PGError::INVALID_ARG, p.error()); } @@ -352,7 +352,7 @@ TEST_F(HomeObjectFixture, DuplicateCreatePG) { homeobject::PGMember{member.first, g_helper->name() + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); ASSERT_FALSE(p); ASSERT_EQ(PGError::INVALID_ARG, p.error()); } @@ -384,7 +384,7 @@ TEST_F(HomeObjectFixture, CreatePGFailed) { info.members.insert(homeobject::PGMember{member.first, name + std::to_string(member.second), 0}); } } - auto p = _obj_inst->pg_manager()->create_pg(std::move(info)).get(); + auto p = sisl::async::sync_get(_obj_inst->pg_manager()->create_pg(std::move(info))); ASSERT_FALSE(p); ASSERT_EQ(PGError::UNKNOWN, p.error()); @@ -395,7 +395,8 @@ TEST_F(HomeObjectFixture, CreatePGFailed) { // wait for repl gc. std::this_thread::sleep_for(std::chrono::seconds(70)); int num_repl = 0; - _obj_inst->hs_repl_service().iterate_repl_devs([&num_repl](cshared< homestore::ReplDev >&) { num_repl++; }); + _obj_inst->hs_repl_service().iterate_repl_devs( + [&num_repl](cshared< homestore::repl_dev >&) { num_repl++; }); LOGINFO("Failed to create pg={} at leader, times {}, num_repl {}", pg_id, i, num_repl); ASSERT_EQ(0, num_repl); @@ -437,7 +438,7 @@ TEST_F(HomeObjectFixture, PGRefreshStatisticsTest) { create_pg(pg_id); auto shard_info = create_shard(pg_id, 64 * Mi, "shard meta"); auto shard_id = shard_info.id; - auto s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); ASSERT_TRUE(!!s); LOGINFO("Created shard {}", shard_info.id); @@ -557,7 +558,7 @@ TEST_F(HomeObjectFixture, DestroyPgResourceCleansUpTest) { // Each replica destroys its local repl_dev. This marks destroy_pending=1 on the repl_dev // superblk auto ret = HSHomeObject::hs_repl_service().destroy_repl_dev(group_id); - ASSERT_TRUE(ret == homestore::ReplServiceError::OK || ret == homestore::ReplServiceError::SERVER_NOT_FOUND); + ASSERT_TRUE(ret || ret.error() == homestore::ReplServiceError::SERVER_NOT_FOUND); // for a destroyed repl_dev, the related pg resource should be reclaimed. restart(); diff --git a/src/lib/homestore_backend/tests/hs_repl_test_helper.hpp b/src/lib/homestore_backend/tests/hs_repl_test_helper.hpp index 7986e643b..3aa081eff 100644 --- a/src/lib/homestore_backend/tests/hs_repl_test_helper.hpp +++ b/src/lib/homestore_backend/tests/hs_repl_test_helper.hpp @@ -35,9 +35,9 @@ #include #include #include -#include - -#include +#include +#include +#include #include "homeobject/common.hpp" @@ -160,15 +160,16 @@ class HSReplTestHelper { return SISL_OPTIONS["base_port"].as< uint16_t >() + helper_.replica_num_; } - void get_prometheus_metrics(const Pistache::Rest::Request&, Pistache::Http::ResponseWriter response) { - response.send(Pistache::Http::Code::Ok, - sisl::MetricsFarm::getInstance().report(sisl::ReportFormat::kTextFormat)); + void get_prometheus_metrics(httplib::Request const&, httplib::Response& response) { + response.status = 200; + response.set_content(sisl::MetricsFarm::getInstance().report(sisl::ReportFormat::TEXT_FORMAT), + "text/plain"); } void start_http_server() { - std::vector< iomgr::http_route > routes = { - {Pistache::Http::Method::Get, "/metrics", - Pistache::Rest::Routes::bind(&TestReplApplication::get_prometheus_metrics, this)}, + std::vector< sisl::http_route > routes = { + {sisl::http_method::Get, "/metrics", + [this](httplib::Request const& req, httplib::Response& res) { get_prometheus_metrics(req, res); }}, }; auto http_server = ioenvironment.get_http_server(); @@ -268,9 +269,6 @@ class HSReplTestHelper { } } - int tmp_argc = 1; - folly_ = std::make_unique< folly::Init >(&tmp_argc, &argv_, true); - LOGINFO("Starting HomeObject replica={}", replica_num_); app = std::make_shared< TestReplApplication >(*this); } @@ -323,10 +321,25 @@ class HSReplTestHelper { return homeobj_; } + // Single source of truth for tearing down homeobj_/the HomeStore singleton. Every caller that + // drops its shared_ptr to the HomeObject (fixture's _obj_inst, homeobj_ here, etc.) must route + // through this so home_store::s_instance never survives, half torn-down, into the next + // build_new_homeobject()/restart() call. The safe_instance() check still runs after reset() in + // case homeobj_ was null or HSHomeObject::shutdown() never reached homestore cleanup. + void shutdown_homeobject() { + if (homeobj_) { + homeobj_->shutdown(); + homeobj_.reset(); + } + if (homestore::home_store::safe_instance()) { + homestore::home_store::instance()->shutdown(); + homestore::home_store::reset_instance(); + } + } + void delete_homeobject() { LOGINFO("Clearing Homeobject replica={}", replica_num_); - homeobj_->shutdown(); - homeobj_.reset(); + shutdown_homeobject(); remove_test_files(); } @@ -336,8 +349,7 @@ class HSReplTestHelper { if (shutdown_delay_secs > 0) { std::this_thread::sleep_for(std::chrono::seconds(shutdown_delay_secs)); } LOGINFO("Stopping homeobject after {} secs, replica={}", shutdown_delay_secs, replica_num_); - homeobj_->shutdown(); - homeobj_.reset(); + shutdown_homeobject(); if (restart_delay_secs > 0) { std::this_thread::sleep_for(std::chrono::seconds(restart_delay_secs)); } LOGINFO("Starting homeobject after {} secs, replica={}", restart_delay_secs, replica_num_); auto const ndevices = SISL_OPTIONS["num_devs"].as< uint32_t >(); @@ -383,7 +395,10 @@ class HSReplTestHelper { std::string name() const { return name_; } std::string test_name() const { return test_name_; } - void teardown() { sisl::GrpcAsyncClientWorker::shutdown_all(); } + void teardown() { + sisl::GrpcAsyncClientWorker::shutdown_all(); + shutdown_homeobject(); + } void sync() { ipc_data_->sync(sync_point_num++, total_replicas_nums_); } @@ -519,7 +534,6 @@ class HSReplTestHelper { boost::process::group proc_grp_; std::unique_ptr< bip::shared_memory_object > shm_; std::unique_ptr< bip::mapped_region > region_; - std::unique_ptr< folly::Init > folly_; std::map< peer_id_t, uint32_t > members_; peer_id_t my_replica_id_; IPCData* ipc_data_; diff --git a/src/lib/homestore_backend/tests/hs_scrubber_tests.cpp b/src/lib/homestore_backend/tests/hs_scrubber_tests.cpp index eb06b1d44..035705da6 100644 --- a/src/lib/homestore_backend/tests/hs_scrubber_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_scrubber_tests.cpp @@ -1,5 +1,7 @@ +#include +#include #include "homeobj_fixture.hpp" -#include +#include #include #include #include @@ -57,28 +59,24 @@ static void corrupt_blob_data(shared< homestore::IndexTable< BlobRouteKey, BlobR data_sgs.size = total_size; data_sgs.iovs.emplace_back(iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); - data_service.async_read(pbas, data_sgs, total_size) - .thenValue([&](auto&& err) { - if (err) { - LOGE("Failed to read blob data, blob_id={}, err={}", blob_id, err.message()); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - throw std::runtime_error(fmt::format("Failed to read blob data: {}", err.message())); - } + auto err = sisl::async::sync_get(data_service.async_read(pbas, data_sgs, total_size)); + if (!err) { + LOGE("Failed to read blob data, blob_id={}, err={}", blob_id, err.error().message()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + throw std::runtime_error(fmt::format("Failed to read blob data: {}", err.error().message())); + } - auto* data_ptr = reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base); - for (size_t i = 0; i < data_sgs.iovs[0].iov_len / 2; i++) { - data_ptr[i] ^= 0xFF; // Flip first half of data - } + auto* data_ptr = reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base); + for (size_t i = 0; i < data_sgs.iovs[0].iov_len / 2; i++) { + data_ptr[i] ^= 0xFF; // Flip first half of data + } - return data_service.async_write(data_sgs, pbas).thenValue([data_sgs = std::move(data_sgs)](auto&& err) { - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - ASSERT_FALSE(err) << "Failed to write corrupted blob data"; - }); - }) - .get(); + err = sisl::async::sync_get(data_service.async_write(data_sgs, pbas)); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + ASSERT_TRUE(err) << "Failed to write corrupted blob data"; } -// Helper function to make a blob inconsistent (valid but different hash) +// Keep the blob locally valid but change its payload hash vs peers (header-only flips look corrupted). static void make_blob_inconsistent(shared< homestore::IndexTable< BlobRouteKey, BlobRouteValue > > pg_index_table, shard_id_t shard_id, blob_id_t blob_id, HSHomeObject* obj_inst) { auto& data_service = homestore::data_service(); @@ -97,47 +95,33 @@ static void make_blob_inconsistent(shared< homestore::IndexTable< BlobRouteKey, data_sgs.size = total_size; data_sgs.iovs.emplace_back(iovec{.iov_base = iomanager.iobuf_alloc(blk_size, total_size), .iov_len = total_size}); - data_service.async_read(pbas, data_sgs, total_size) - .thenValue([&](auto&& err) { - if (err) { - LOGE("Failed to read blob data, blob_id={}, err={}", blob_id, err.message()); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - throw std::runtime_error(fmt::format("Failed to read blob data: {}", err.message())); - } - - // Modify blob data and recompute valid hash - uint8_t* read_buf = r_cast< uint8_t* >(data_sgs.iovs[0].iov_base); - auto header = r_cast< BlobHeader* >(read_buf); - uint8_t* blob_bytes = read_buf + header->data_offset; - - std::mt19937 rng{std::random_device{}()}; - std::uniform_int_distribution< int > dist(0, 255); - - for (size_t i = 0; i < header->blob_size / 2; i++) { - blob_bytes[i] ^= static_cast< uint8_t >(dist(rng)); - } + auto err = sisl::async::sync_get(data_service.async_read(pbas, data_sgs, total_size)); + if (!err) { + LOGE("Failed to read blob data, blob_id={}, err={}", blob_id, err.error().message()); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + throw std::runtime_error(fmt::format("Failed to read blob data: {}", err.error().message())); + } - uint8_t computed_hash[BlobHeader::blob_max_hash_len]{}; - obj_inst->compute_blob_payload_hash(header->hash_algorithm, blob_bytes, header->blob_size, computed_hash, - BlobHeader::blob_max_hash_len); + auto* data_ptr = reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base); + auto* header = reinterpret_cast< BlobHeader* >(data_ptr); + ASSERT_TRUE(header->valid()) << "Blob header must be valid before making it inconsistent"; + ASSERT_GE(header->blob_size, 16u) << "Blob payload too small to flip bytes"; + ASSERT_EQ(header->data_offset, HSHomeObject::_data_block_size) + << "Unexpected blob data_offset; payload would overlap header"; - std::memcpy(header->hash, computed_hash, BlobHeader::blob_max_hash_len); - std::memset(header->header_hash, 0, BlobHeader::blob_max_hash_len); - uint32_t computed_header_hash = crc32_ieee(0, (uint8_t*)header, sizeof(BlobHeader)); - std::memcpy(header->header_hash, &computed_header_hash, sizeof(uint32_t)); + auto* payload = data_ptr + header->data_offset; + for (size_t i = 0; i < 16; ++i) { + payload[i] ^= 0xAA; + } - if (!obj_inst->verify_blob(data_sgs.iovs[0].iov_base, header->shard_id, header->blob_id)) { - LOGE("Blob verification failed after modification, blob_id={}", blob_id); - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - throw std::runtime_error(fmt::format("Blob verification failed for blob_id={}", blob_id)); - } + obj_inst->compute_blob_payload_hash(header->hash_algorithm, payload, header->blob_size, header->hash, + BlobHeader::blob_max_hash_len); + header->seal(); + ASSERT_TRUE(header->valid()) << "Blob header must remain valid after resealing"; - return data_service.async_write(data_sgs, pbas).thenValue([data_sgs = std::move(data_sgs)](auto&& err) { - iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); - ASSERT_FALSE(err) << "Failed to write inconsistent blob data"; - }); - }) - .get(); + err = sisl::async::sync_get(data_service.async_write(data_sgs, pbas)); + iomanager.iobuf_free(reinterpret_cast< uint8_t* >(data_sgs.iovs[0].iov_base)); + ASSERT_TRUE(err) << "Failed to write inconsistent blob data"; } // Helper function to verify missing blobs in scrub report @@ -391,7 +375,8 @@ TEST_F(HomeObjectFixture, BasicScrubTest) { EXPECT_TRUE(inconsistent_blobs.size() == 1) << "Inconsistent blob should be reported in deep scrub report for one of the followers"; const auto it = inconsistent_blobs.find(BlobRoute{blob_op_shard_id, inconsistent_blob_id}); - EXPECT_TRUE(it != inconsistent_blobs.end()) + // ASSERT: EXPECT would continue and dereference end() iterator → UB / segfault in map::count. + ASSERT_TRUE(it != inconsistent_blobs.end()) << "The inconsistent blob should be reported in deep scrub report for blob_id=" << inconsistent_blob_id; auto& inconsistent_blob_peers = it->second; @@ -793,12 +778,13 @@ TEST_F(HomeObjectFixture, CancelScrubTaskTest) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); scrub_mgr->cancel_scrub_task(pg_id); LOGINFO("Cancelled scrub task for pg={}", pg_id); - auto scrub_report = std::move(scrub_future).get(); + auto scrub_report = sisl::async::sync_get(std::move(scrub_future)); LOGINFO("Scrub task cancelled, report: {}", scrub_report ? "present" : "null"); // The critical invariant: cancel must clear in_scrubbing so that a subsequent // submit_scrub_task is accepted. A null return here means the state was not cleaned up. - auto followup_report = scrub_mgr->submit_scrub_task(pg_id, true, SCRUB_TRIGGER_TYPE::MANUALLY).get(); + auto followup_report = + sisl::async::sync_get(scrub_mgr->submit_scrub_task(pg_id, true, SCRUB_TRIGGER_TYPE::MANUALLY)); EXPECT_NE(followup_report, nullptr) << "A new scrub task should be accepted after cancellation; " "null means in_scrubbing was not cleared"; scrub_mgr->cancel_scrub_task(pg_id); @@ -831,7 +817,7 @@ TEST_F(HomeObjectFixture, ConcurrentScrubsOnMultiplePGsTest) { auto scrub_mgr = _obj_inst->scrub_manager(); // Submit scrub tasks for all PGs concurrently - std::vector< std::pair< pg_id_t, folly::SemiFuture< std::shared_ptr< ScrubManager::ShallowScrubReport > > > > + std::vector< std::pair< pg_id_t, sisl::async::task< std::shared_ptr< ScrubManager::ShallowScrubReport > > > > scrub_futures; for (const auto& pg_id : pg_ids) { @@ -844,7 +830,7 @@ TEST_F(HomeObjectFixture, ConcurrentScrubsOnMultiplePGsTest) { // Wait for all scrub tasks to complete and verify each report is clean for (auto& [pg_id, future] : scrub_futures) { - auto report = std::move(future).get(); + auto report = sisl::async::sync_get(std::move(future)); ASSERT_NE(report, nullptr) << "Scrub report should not be null for pg=" << pg_id; auto deep_report = std::dynamic_pointer_cast< ScrubManager::DeepScrubReport >(report); @@ -932,8 +918,8 @@ TEST_F(HomeObjectFixture, ReconcileScrubReportTest) { #ifdef _PRERELEASE set_callback_flip( "delete_missing_blob_through_raft", std::function< void() >([this, missing_blob_to_delete, shard_id]() { - auto ret = - _obj_inst->blob_manager()->del(shard_id, missing_blob_to_delete, generateRandomTraceId()).get(); + auto ret = sisl::async::sync_get( + _obj_inst->blob_manager()->del(shard_id, missing_blob_to_delete, generateRandomTraceId())); if (!ret) { FAIL() << "Blob deletion via raft failed for shard=" << shard_id << " blob=" << missing_blob_to_delete << ", error=" << fmt::format("{}", ret.error()); @@ -1011,7 +997,7 @@ TEST_F(HomeObjectFixture, AddRemovePGScrubTest) { // Now delete the PG - this should cancel any running scrub and remove superblock _obj_inst->pg_manager()->destroy_pg(pg_id); - auto report = scrub_mgr->submit_scrub_task(pg_id, false, SCRUB_TRIGGER_TYPE::MANUALLY).get(); + auto report = sisl::async::sync_get(scrub_mgr->submit_scrub_task(pg_id, false, SCRUB_TRIGGER_TYPE::MANUALLY)); ASSERT_EQ(report, nullptr) << "Scrub report should be null after PG deletion"; LOGINFO("Scrub task for deleted pg={} returned null report as expected", pg_id); diff --git a/src/lib/homestore_backend/tests/hs_shard_tests.cpp b/src/lib/homestore_backend/tests/hs_shard_tests.cpp index 4c45ce77c..9a9a391d3 100644 --- a/src/lib/homestore_backend/tests/hs_shard_tests.cpp +++ b/src/lib/homestore_backend/tests/hs_shard_tests.cpp @@ -87,8 +87,8 @@ TEST_F(HomeObjectFixture, SealShard) { // expect to create shard failed run_on_pg_leader(pg_id, [&]() { - auto s = _obj_inst->shard_manager()->create_shard(pg_id, 64 * Mi, "shard meta").get(); - ASSERT_TRUE(s.hasError()); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->create_shard(pg_id, 64 * Mi, "shard meta")); + ASSERT_TRUE(!s); ASSERT_EQ(ShardErrorCode::NO_SPACE_LEFT, s.error().getCode()); }); @@ -150,7 +150,7 @@ TEST_F(HomeObjectFixture, ShardManagerRecovery) { // restart again to verify the shards has expected states. restart(); - auto s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); ASSERT_TRUE(!!s); EXPECT_EQ(ShardInfo::State::SEALED, s.value().state); @@ -211,7 +211,7 @@ TEST_F(HomeObjectFixture, SealShardWithRestart) { auto shard_info = create_shard(pg_id, 64 * Mi, "shard meta"); auto shard_id = shard_info.id; - auto s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); ASSERT_TRUE(!!s); LOGINFO("Got shard {}", shard_id); @@ -228,7 +228,8 @@ TEST_F(HomeObjectFixture, SealShardWithRestart) { LOGINFO("Sealed shard {}", shard_id); run_on_pg_leader(pg_id, [this, shard_id]() { - auto b = _obj_inst->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul}).get(); + auto b = sisl::async::sync_get( + _obj_inst->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul})); ASSERT_TRUE(!b); ASSERT_EQ(b.error().getCode(), BlobErrorCode::SEALED_SHARD); LOGINFO("Put blob {}", b.error()); @@ -238,7 +239,7 @@ TEST_F(HomeObjectFixture, SealShardWithRestart) { restart(); // Verify shard is sealed. - s = _obj_inst->shard_manager()->get_shard(shard_id).get(); + s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_id)); ASSERT_TRUE(!!s); LOGINFO("After restart shard {}", shard_id); @@ -248,7 +249,8 @@ TEST_F(HomeObjectFixture, SealShardWithRestart) { EXPECT_EQ(shard_info.state, ShardInfo::State::SEALED); run_on_pg_leader(pg_id, [this, shard_id]() { - auto b = _obj_inst->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul}).get(); + auto b = sisl::async::sync_get( + _obj_inst->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul})); ASSERT_TRUE(!b); ASSERT_EQ(b.error().getCode(), BlobErrorCode::SEALED_SHARD); LOGINFO("Put blob {}", b.error()); @@ -277,14 +279,14 @@ TEST_F(HomeObjectFixture, CreateShardOnDiskLostMemeber) { restart(0, 0, 1); auto tid = generateRandomTraceId(); - auto s = _obj_inst->shard_manager()->seal_shard(pg_shard_id_map[degrade_pg_id], tid).get(); - ASSERT_TRUE(s.hasError()) << "degraded pg on error member should return seal shard fail, pg_id " - << degrade_pg_id << "shard_id " << pg_shard_id_map[degrade_pg_id] - << " replica number " << g_helper->replica_num(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->seal_shard(pg_shard_id_map[degrade_pg_id], tid)); + ASSERT_TRUE(!s) << "degraded pg on error member should return seal shard fail, pg_id " << degrade_pg_id + << "shard_id " << pg_shard_id_map[degrade_pg_id] << " replica number " + << g_helper->replica_num(); tid = generateRandomTraceId(); - s = _obj_inst->shard_manager()->create_shard(degrade_pg_id, 64 * Mi, "shard meta", tid).get(); - ASSERT_TRUE(s.hasError()) << "degraded pg on error member should return create shard fail, pg_id " - << degrade_pg_id << " replica number " << g_helper->replica_num(); + s = sisl::async::sync_get(_obj_inst->shard_manager()->create_shard(degrade_pg_id, 64 * Mi, "shard meta", tid)); + ASSERT_TRUE(!s) << "degraded pg on error member should return create shard fail, pg_id " << degrade_pg_id + << " replica number " << g_helper->replica_num(); } else { restart(); sleep(10); @@ -434,7 +436,7 @@ TEST_F(HomeObjectFixture, ShardVersionMigrationRecovery) { // Verify all shards are still functional LOGINFO("Verifying shard functionality..."); for (size_t i = 0; i < shard_ids.size(); ++i) { - auto s = _obj_inst->shard_manager()->get_shard(shard_ids[i]).get(); + auto s = sisl::async::sync_get(_obj_inst->shard_manager()->get_shard(shard_ids[i])); ASSERT_TRUE(!!s) << "Shard " << i << " should be accessible"; EXPECT_EQ(shard_ids[i], s.value().id); EXPECT_EQ(ShardInfo::State::OPEN, s.value().state); diff --git a/src/lib/homestore_backend/tests/test_heap_chunk_selector.cpp b/src/lib/homestore_backend/tests/test_heap_chunk_selector.cpp index 053d5aa5d..34cf74642 100644 --- a/src/lib/homestore_backend/tests/test_heap_chunk_selector.cpp +++ b/src/lib/homestore_backend/tests/test_heap_chunk_selector.cpp @@ -2,8 +2,6 @@ #include #include -#include - #include #include "homeobject/common.hpp" @@ -379,6 +377,5 @@ int main(int argc, char* argv[]) { sisl::logging::SetLogger(std::string(argv[0])); spdlog::set_pattern("[%D %T.%e] [%n] [%^%l%$] [%t] %v"); parsed_argc = 1; - auto f = ::folly::Init(&parsed_argc, &argv, true); return RUN_ALL_TESTS(); } diff --git a/src/lib/homestore_backend/tests/test_homestore_backend.cpp b/src/lib/homestore_backend/tests/test_homestore_backend.cpp index 3ec52cbb4..0b29924cf 100644 --- a/src/lib/homestore_backend/tests/test_homestore_backend.cpp +++ b/src/lib/homestore_backend/tests/test_homestore_backend.cpp @@ -47,7 +47,7 @@ SISL_OPTION_GROUP( "true or false")); SISL_LOGGING_INIT(homeobject) -#define test_options logging, config, homeobject, test_homeobject_repl_common +#define test_options logging, config, test_homeobject_repl_common SISL_OPTIONS_ENABLE(test_options) std::unique_ptr< test_common::HSReplTestHelper > g_helper; diff --git a/src/lib/homestore_backend/tests/test_homestore_backend_dynamic.cpp b/src/lib/homestore_backend/tests/test_homestore_backend_dynamic.cpp index 4938f9040..aedd7c389 100644 --- a/src/lib/homestore_backend/tests/test_homestore_backend_dynamic.cpp +++ b/src/lib/homestore_backend/tests/test_homestore_backend_dynamic.cpp @@ -1,3 +1,5 @@ +#include +#include /********************************************************************************* * Modifications Copyright 2017-2019 eBay Inc. * @@ -163,9 +165,8 @@ void HomeObjectFixture::RestartFollowerDuringBaselineResyncUsingSigKill(uint64_t // ======== Stage 2: replace a member ======== LOGINFO("start replace member, pg={}, task_id={}", pg_id, task_id); run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -315,9 +316,8 @@ TEST_F(HomeObjectFixture, RestartFollowerDuringBaselineResyncUsingGracefulShutdo // ======== Stage 2: replace a member ======== std::string task_id = "task_id"; run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -420,7 +420,7 @@ void HomeObjectFixture::ReplaceMember(bool withGC) { if (withGC) { run_if_in_pg(pg_id, [&]() { auto gc_mgr = _obj_inst->gc_manager(); - std::vector< folly::SemiFuture< bool > > futs; + std::vector< sisl::async::task< bool > > futs; for (const auto& [pg_id, shards] : pg_shard_id_vec) { for (const auto& shard_id : shards) { auto chunk_id_opt = _obj_inst->get_shard_p_chunk_id(shard_id); @@ -429,15 +429,10 @@ void HomeObjectFixture::ReplaceMember(bool withGC) { } } // wait for all egc completed - folly::collectAllUnsafe(futs) - .thenValue([](auto&& results) { - for (auto const& ok : results) { - ASSERT_TRUE(ok.hasValue()); - // all egc task should be completed - ASSERT_TRUE(ok.value()); - } - }) - .get(); + auto results = sisl::async::sync_get(sisl::async::when_all(std::move(futs))); + for (auto const& ok : results) { + ASSERT_TRUE(ok); + } futs.clear(); }); @@ -473,9 +468,8 @@ void HomeObjectFixture::ReplaceMember(bool withGC) { std::string task_id = "task_id"; LOGINFO("start replace member, pg={}, task_id={}", pg_id, task_id); run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -648,9 +642,8 @@ void HomeObjectFixture::RestartLeaderDuringBaselineResyncUsingSigKill(uint64_t f // ========Stage 2: replace a member======== run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); initial_leader_replica_id = get_leader_id(pg_id); @@ -818,9 +811,8 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { // ======== Stage 2: replace a member ======== std::string task_id = "task_id"; run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -860,19 +852,19 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { j["member_id"] = boost::uuids::to_string(in_member_id); j["commit_quorum"] = "0"; auto r = http_helper.del("/api/v1/member", j.dump()); - if (r.code() == Pistache::Http::Code::Ok) { + if (r.code() == 200) { retry = false; - } else if (r.code() == Pistache::Http::Code::Service_Unavailable) { + } else if (r.code() == 503) { LOGINFO("remove_member get RETRY_REQUEST error, will retry"); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } else { ASSERT_FALSE(true); } } else { - auto r = _obj_inst->pg_manager()->remove_member(pg_id, in_member_id, 0, 0).get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->remove_member(pg_id, in_member_id, 0, 0)); // new member can not respond to remove_member request because it is stuck at snapshot, so we may // get RETRY_REQUEST error here, but the remove_member takes effective after force removal timeout. - if (!r.hasError()) { + if (!!r) { retry = false; } else if (r.error() == PGError::RETRY_REQUEST) { LOGINFO("remove_member get RETRY_REQUEST error, will retry"); @@ -895,10 +887,11 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { j["learner"] = "false"; j["commit_quorum"] = "0"; auto r = http_helper.post("/api/v1/flip_learner", j.dump()); - ASSERT_EQ(r.code(), Pistache::Http::Code::Ok); + ASSERT_EQ(r.code(), 200); } else { - auto r = _obj_inst->pg_manager()->flip_learner_flag(pg_id, out_member_id, false, 0, 0).get(); - ASSERT_FALSE(r.hasError()); + auto r = sisl::async::sync_get( + _obj_inst->pg_manager()->flip_learner_flag(pg_id, out_member_id, false, 0, 0)); + ASSERT_FALSE(!r); } }); // SyncPoint5 clean task @@ -906,8 +899,9 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { LOGINFO("about to clean task") run_on_pg_leader(pg_id, [&]() { if (!http_enabled) { - auto r = _obj_inst->pg_manager()->clean_replace_member_task(pg_id, task_id, 0, 0).get(); - ASSERT_FALSE(r.hasError()); + auto r = + sisl::async::sync_get(_obj_inst->pg_manager()->clean_replace_member_task(pg_id, task_id, 0, 0)); + ASSERT_FALSE(!r); } else { HttpHelper http_helper("127.0.0.1", 5000 + g_helper->replica_num()); nlohmann::json j; @@ -915,7 +909,7 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { j["task_id"] = task_id; j["commit_quorum"] = "0"; auto r = http_helper.del("/api/v1/pg_replacemember_task", j.dump()); - ASSERT_EQ(r.code(), Pistache::Http::Code::Ok); + ASSERT_EQ(r.code(), 200); } }); // wait for replication settled @@ -941,7 +935,7 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { auto resource = "/api/v1/pg?group_id=" + boost::uuids::to_string(group_id) + "&replica_id=" + boost::uuids::to_string(g_helper->my_replica_id_); auto r = http_helper.del(resource, ""); - ASSERT_EQ(r.code(), Pistache::Http::Code::Ok); + ASSERT_EQ(r.code(), 200); } else { _obj_inst->pg_manager()->exit_pg(group_id, g_helper->my_replica_id(), 0); } @@ -950,7 +944,7 @@ TEST_F(HomeObjectFixture, RollbackReplaceMember) { // Test idempotence of exit_pg LOGINFO("Try to call exit_pg again pg={}", pg_id); auto ret = _obj_inst->pg_manager()->exit_pg(group_id, g_helper->my_replica_id(), 0); - ASSERT_FALSE(ret.hasError()); + ASSERT_FALSE(!ret); } g_helper->sync(); } @@ -998,9 +992,8 @@ TEST_F(HomeObjectFixture, BaselineResyncEmptyPG) { LOGINFO("start replace member, pg={}, task_id={}", pg_id, task_id); run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -1087,9 +1080,8 @@ TEST_F(HomeObjectFixture, BaselineResyncEmptyShard) { LOGINFO("start replace member, pg={}, task_id={}", pg_id, task_id); run_on_pg_leader(pg_id, [&]() { - auto r = _obj_inst->pg_manager() - ->replace_member(pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0}) - .get(); + auto r = sisl::async::sync_get(_obj_inst->pg_manager()->replace_member( + pg_id, task_id, out_member_id, PGMember{in_member_id, "new_member", 0})); ASSERT_TRUE(r); }); @@ -1169,7 +1161,7 @@ SISL_OPTION_GROUP( "true or false")); SISL_LOGGING_INIT(homeobject) -#define test_options logging, config, homeobject, test_homeobject_repl_common +#define test_options logging, config, test_homeobject_repl_common SISL_OPTIONS_ENABLE(test_options) std::unique_ptr< test_common::HSReplTestHelper > g_helper; diff --git a/src/lib/homestore_backend/tests/test_mpmc_priority_queue.cpp b/src/lib/homestore_backend/tests/test_mpmc_priority_queue.cpp deleted file mode 100644 index c90ad7d1b..000000000 --- a/src/lib/homestore_backend/tests/test_mpmc_priority_queue.cpp +++ /dev/null @@ -1,413 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "../MPMCPriorityQueue.hpp" - -using namespace homeobject; -using namespace std::chrono_literals; - -// ============================================================================ -// Basic Functionality Tests -// ============================================================================ - -TEST(MPMCPriorityQueueTest, BasicPushPop) { - MPMCPriorityQueue< int > queue; - - // Push elements - queue.push(5); - queue.push(2); - queue.push(8); - queue.push(1); - - EXPECT_EQ(queue.size(), 4); - EXPECT_FALSE(queue.empty()); - - // Pop in priority order (max heap by default) - auto r1 = queue.pop(); - EXPECT_TRUE(r1.is_ok()); - EXPECT_EQ(r1.value.value(), 8); - - auto r2 = queue.pop(); - EXPECT_TRUE(r2.is_ok()); - EXPECT_EQ(r2.value.value(), 5); - - auto r3 = queue.pop(); - EXPECT_TRUE(r3.is_ok()); - EXPECT_EQ(r3.value.value(), 2); - - auto r4 = queue.pop(); - EXPECT_TRUE(r4.is_ok()); - EXPECT_EQ(r4.value.value(), 1); - - EXPECT_EQ(queue.size(), 0); - EXPECT_TRUE(queue.empty()); -} - -TEST(MPMCPriorityQueueTest, CustomComparator) { - // Min-heap using std::greater - MPMCPriorityQueue< int, std::greater< int > > queue; - - queue.push(5); - queue.push(2); - queue.push(8); - queue.push(1); - - // Pop in ascending order - EXPECT_EQ(queue.pop().value.value(), 1); - EXPECT_EQ(queue.pop().value.value(), 2); - EXPECT_EQ(queue.pop().value.value(), 5); - EXPECT_EQ(queue.pop().value.value(), 8); -} - -TEST(MPMCPriorityQueueTest, MoveSemantics) { - struct MoveOnly { - int value; - - explicit MoveOnly(int v) : value(v) {} - MoveOnly(const MoveOnly&) = delete; - MoveOnly& operator=(const MoveOnly&) = delete; - MoveOnly(MoveOnly&&) = default; - MoveOnly& operator=(MoveOnly&&) = default; - - bool operator<(const MoveOnly& other) const { return value < other.value; } - }; - - MPMCPriorityQueue< MoveOnly > queue; - - queue.push(MoveOnly(5)); - queue.push(MoveOnly(2)); - queue.push(MoveOnly(8)); - - EXPECT_EQ(queue.pop().value.value().value, 8); - EXPECT_EQ(queue.pop().value.value().value, 5); - EXPECT_EQ(queue.pop().value.value().value, 2); -} - -// ============================================================================ -// Close Operation Tests -// ============================================================================ - -TEST(MPMCPriorityQueueTest, Close) { - MPMCPriorityQueue< int > queue; - - queue.push(1); - queue.push(2); - queue.push(3); - - EXPECT_FALSE(queue.is_closed()); - queue.close(); - EXPECT_TRUE(queue.is_closed()); - - // Can still pop existing elements - EXPECT_EQ(queue.pop().value.value(), 3); - EXPECT_EQ(queue.pop().value.value(), 2); - EXPECT_EQ(queue.pop().value.value(), 1); - - // Now should return Closed status - auto result = queue.pop(); - EXPECT_TRUE(result.is_closed()); - EXPECT_FALSE(result.value.has_value()); -} - -TEST(MPMCPriorityQueueTest, PushAfterClose) { - MPMCPriorityQueue< int > queue; - - queue.push(1); - queue.close(); - - // Pushes after close are ignored - queue.push(2); - queue.push(3); - - EXPECT_EQ(queue.size(), 1); - - auto r1 = queue.pop(); - EXPECT_TRUE(r1.is_ok()); - EXPECT_EQ(r1.value.value(), 1); - - auto r2 = queue.pop(); - EXPECT_TRUE(r2.is_closed()); -} - -TEST(MPMCPriorityQueueTest, CloseIdempotent) { - MPMCPriorityQueue< int > queue; - - queue.push(1); - queue.close(); - queue.close(); // Should be safe - queue.close(); - - EXPECT_TRUE(queue.is_closed()); - EXPECT_EQ(queue.size(), 1); -} - -// ============================================================================ -// Blocking Behavior Tests -// ============================================================================ - -TEST(MPMCPriorityQueueTest, BlockingPop) { - MPMCPriorityQueue< int > queue; - std::atomic< bool > pop_started{false}; - std::atomic< bool > pop_completed{false}; - - // Consumer thread that will block - std::thread consumer([&]() { - pop_started = true; - auto result = queue.pop(); - pop_completed = true; - - EXPECT_TRUE(result.is_ok()); - EXPECT_EQ(result.value.value(), 42); - }); - - // Wait for consumer to start - while (!pop_started) { - std::this_thread::yield(); - } - - std::this_thread::sleep_for(50ms); - EXPECT_FALSE(pop_completed); - - // Unblock consumer by pushing - queue.push(42); - - consumer.join(); - EXPECT_TRUE(pop_completed); -} - -TEST(MPMCPriorityQueueTest, CloseUnblocksWaiters) { - MPMCPriorityQueue< int > queue; - std::atomic< int > closed_count{0}; - - // Start multiple waiting consumers - std::vector< std::thread > consumers; - for (int i = 0; i < 5; ++i) { - consumers.emplace_back([&]() { - auto result = queue.pop(); - if (result.is_closed()) { closed_count.fetch_add(1, std::memory_order_relaxed); } - }); - } - - std::this_thread::sleep_for(100ms); - - // Close should wake all waiters - queue.close(); - - for (auto& t : consumers) { - t.join(); - } - - EXPECT_EQ(closed_count.load(), 5); -} - -// ============================================================================ -// Multi-threaded Producer Tests -// ============================================================================ - -TEST(MPMCPriorityQueueTest, MultipleProducers) { - MPMCPriorityQueue< int > queue; - constexpr int num_producers = 4; - constexpr int items_per_producer = 250; - - std::vector< std::thread > producers; - for (int i = 0; i < num_producers; ++i) { - producers.emplace_back([&, i]() { - for (int j = 0; j < items_per_producer; ++j) { - queue.push(i * items_per_producer + j); - } - }); - } - - for (auto& t : producers) { - t.join(); - } - - EXPECT_EQ(queue.size(), num_producers * items_per_producer); - - // Verify all elements come out in descending order - std::vector< int > popped; - for (int i = 0; i < num_producers * items_per_producer; ++i) { - auto result = queue.pop(); - ASSERT_TRUE(result.is_ok()); - popped.push_back(result.value.value()); - } - - EXPECT_TRUE(std::is_sorted(popped.rbegin(), popped.rend())); -} - -// ============================================================================ -// Multi-threaded Consumer Tests -// ============================================================================ - -TEST(MPMCPriorityQueueTest, MultipleConsumers) { - MPMCPriorityQueue< int > queue; - constexpr int num_items = 1000; - - // Fill queue - for (int i = 0; i < num_items; ++i) { - queue.push(i); - } - - constexpr int num_consumers = 4; - std::vector< std::thread > consumers; - std::atomic< int > total_consumed{0}; - - for (int i = 0; i < num_consumers; ++i) { - consumers.emplace_back([&]() { - int count = 0; - while (true) { - auto result = queue.pop(); - if (result.is_closed()) { break; } - ++count; - } - total_consumed.fetch_add(count, std::memory_order_relaxed); - }); - } - - // Give consumers time to start - std::this_thread::sleep_for(50ms); - - // Close to signal completion - queue.close(); - - for (auto& t : consumers) { - t.join(); - } - - EXPECT_EQ(total_consumed.load(), num_items); -} - -// ============================================================================ -// Concurrent Producers and Consumers -// ============================================================================ - -TEST(MPMCPriorityQueueTest, ConcurrentProducersConsumers) { - MPMCPriorityQueue< int > queue; - constexpr int num_producers = 3; - constexpr int num_consumers = 3; - constexpr int items_per_producer = 200; - - std::atomic< int > total_consumed{0}; - std::vector< std::thread > threads; - - // Start consumers - for (int i = 0; i < num_consumers; ++i) { - threads.emplace_back([&]() { - int count = 0; - while (true) { - auto result = queue.pop(); - if (result.is_closed()) { break; } - ++count; - } - total_consumed.fetch_add(count, std::memory_order_relaxed); - }); - } - - // Start producers - for (int i = 0; i < num_producers; ++i) { - threads.emplace_back([&, i]() { - for (int j = 0; j < items_per_producer; ++j) { - queue.push(i * items_per_producer + j); - std::this_thread::sleep_for(10us); // Simulate work - } - }); - } - - // Wait for producers - for (int i = num_consumers; i < num_consumers + num_producers; ++i) { - threads[i].join(); - } - - // Close and wait for consumers - queue.close(); - for (int i = 0; i < num_consumers; ++i) { - threads[i].join(); - } - - EXPECT_EQ(total_consumed.load(), num_producers * items_per_producer); -} - -// ============================================================================ -// Stress Test -// ============================================================================ - -TEST(MPMCPriorityQueueTest, StressTest) { - MPMCPriorityQueue< int > queue; - constexpr int num_threads = 8; - constexpr int operations_per_thread = 1000; - - std::atomic< int > push_count{0}; - std::atomic< int > pop_count{0}; - std::vector< std::thread > threads; - - // Half producers, half consumers - for (int i = 0; i < num_threads / 2; ++i) { - threads.emplace_back([&]() { - for (int j = 0; j < operations_per_thread; ++j) { - queue.push(j); - push_count.fetch_add(1, std::memory_order_relaxed); - } - }); - } - - for (int i = 0; i < num_threads / 2; ++i) { - threads.emplace_back([&]() { - for (int j = 0; j < operations_per_thread; ++j) { - auto result = queue.pop(); - if (result.is_ok()) { pop_count.fetch_add(1, std::memory_order_relaxed); } - } - }); - } - - for (auto& t : threads) { - t.join(); - } - - EXPECT_EQ(push_count.load(), (num_threads / 2) * operations_per_thread); - - // Pop remaining elements - while (!queue.empty()) { - auto result = queue.pop(); - if (result.is_ok()) { pop_count.fetch_add(1, std::memory_order_relaxed); } - } - - EXPECT_EQ(pop_count.load(), push_count.load()); -} - -// ============================================================================ -// Destructor Test -// ============================================================================ - -TEST(MPMCPriorityQueueTest, DestructorClosesQueue) { - std::atomic< bool > consumer_unblocked{false}; - - std::thread consumer([&]() { - auto queue = std::make_unique< MPMCPriorityQueue< int > >(); - queue->push(1); - - std::thread waiter([&, q = queue.get()]() { - auto first_result = q->pop(); // Pop the 1 - (void)first_result; // Explicitly ignore the result - auto result = q->pop(); // This will block until destructor closes queue - if (result.is_closed()) { consumer_unblocked = true; } - }); - - std::this_thread::sleep_for(100ms); - // Destructor will be called here - queue.reset(); - - waiter.join(); - }); - - consumer.join(); - EXPECT_TRUE(consumer_unblocked); -} - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/lib/memory_backend/CMakeLists.txt b/src/lib/memory_backend/CMakeLists.txt index 8a35bf231..5156e6bd9 100644 --- a/src/lib/memory_backend/CMakeLists.txt +++ b/src/lib/memory_backend/CMakeLists.txt @@ -21,5 +21,4 @@ target_link_libraries(memory_test ${COMMON_TEST_DEPS} -rdynamic ) -add_test(NAME MemoryTestCPU COMMAND memory_test -csv error --executor cpu --num_iters 20000) -add_test(NAME MemoryTestIO COMMAND memory_test -csv error --executor io --num_iters 20000) +add_test(NAME MemoryTest COMMAND memory_test -csv error --num_iters 20000) diff --git a/src/lib/memory_backend/mem_blob_manager.cpp b/src/lib/memory_backend/mem_blob_manager.cpp index 096b070af..75bcf194e 100644 --- a/src/lib/memory_backend/mem_blob_manager.cpp +++ b/src/lib/memory_backend/mem_blob_manager.cpp @@ -2,25 +2,10 @@ namespace homeobject { -#define WITH_SHARD \ - auto index_it = index_.find(_shard.id); \ - RELEASE_ASSERT(index_.end() != index_it, "Missing BTree!!"); \ - auto& shard = *index_it->second; - -#define WITH_ROUTE(blob) \ - auto const route = BlobRoute{_shard.id, (blob)}; \ - LOGT("[route={}]", route); - -#define IF_BLOB_ALIVE \ - if (auto blob_it = shard.btree_.find(route); shard.btree_.end() == blob_it || !blob_it->second) { \ - LOGD("[route={}] missing", route); \ - } else - // Write (move) Blob to new BlobExt on heap and Insert BlobExt to Index BlobManager::AsyncResult< blob_id_t > MemoryHomeObject::_put_blob(ShardInfo const& _shard, Blob&& _blob, trace_id_t tid) { (void)tid; - WITH_SHARD blob_id_t new_blob_id; { auto lg = std::shared_lock(_pg_lock); @@ -29,12 +14,17 @@ BlobManager::AsyncResult< blob_id_t > MemoryHomeObject::_put_blob(ShardInfo cons iter->second->durable_entities_update( [&new_blob_id](auto& de) { new_blob_id = de.blob_sequence_num.fetch_add(1, std::memory_order_relaxed); }); } - WITH_ROUTE(new_blob_id); + auto const route = BlobRoute{_shard.id, new_blob_id}; + LOGT("[route={}]", route); - auto [_, happened] = - shard.btree_.try_emplace(route, BlobExt{.state_ = BlobState::ALIVE, .blob_ = new Blob(std::move(_blob))}); + bool happened = false; + bool found = index_.visit(_shard.id, [&](auto& p) { + happened = p.second->btree_.try_emplace( + route, BlobExt{.state_ = BlobState::ALIVE, .blob_ = new Blob(std::move(_blob))}); + }); + RELEASE_ASSERT(found, "Missing BTree!!"); RELEASE_ASSERT(happened, "Generated duplicate BlobRoute!"); - return route.blob; + co_return route.blob; } // Lookup BlobExt and duplicate underyling Blob for user; only *safe* because we defer GC. @@ -45,22 +35,38 @@ BlobManager::AsyncResult< Blob > MemoryHomeObject::_get_blob(ShardInfo const& _s (void)len; (void)allow_skip_verify; (void)tid; - WITH_SHARD - WITH_ROUTE(_blob) - IF_BLOB_ALIVE { return blob_it->second.blob_->clone(); } - return folly::makeUnexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); + Blob result; + bool alive = false; + auto const route = BlobRoute{_shard.id, _blob}; + LOGT("[route={}]", route); + + index_.cvisit(_shard.id, [&](auto const& p) { + p.second->btree_.cvisit(route, [&](auto const& bp) { + if (bp.second) { + result = bp.second.blob_->clone(); + alive = true; + } else { + LOGD("[route={}] missing", route); + } + }); + }); + if (alive) co_return result; + LOGD("[route={}] missing", route); + co_return std::unexpected(BlobError(BlobErrorCode::UNKNOWN_BLOB)); } // Tombstone BlobExt entry BlobManager::NullAsyncResult MemoryHomeObject::_del_blob(ShardInfo const& _shard, blob_id_t _blob, trace_id_t tid) { (void)tid; - WITH_SHARD - WITH_ROUTE(_blob) - IF_BLOB_ALIVE { - shard.btree_.assign_if_equal(route, blob_it->second, - BlobExt{.state_ = BlobState::DELETED, .blob_ = blob_it->second.blob_}); - } - return folly::Unit(); + auto const route = BlobRoute{_shard.id, _blob}; + LOGT("[route={}]", route); + + index_.cvisit(_shard.id, [&](auto const& p) { + p.second->btree_.visit(route, [&](auto& bp) { + if (bp.second) { bp.second.state_ = BlobState::DELETED; } + }); + }); + co_return std::monostate{}; } } // namespace homeobject diff --git a/src/lib/memory_backend/mem_homeobject.cpp b/src/lib/memory_backend/mem_homeobject.cpp index 80d89d4af..decc85b42 100644 --- a/src/lib/memory_backend/mem_homeobject.cpp +++ b/src/lib/memory_backend/mem_homeobject.cpp @@ -15,9 +15,7 @@ MemoryHomeObject::MemoryHomeObject(std::weak_ptr< HomeObjectApplication >&& appl void MemoryHomeObject::shutdown() { LOGI("MemoryHomeObject: Executing shutdown procedure"); } ShardIndex::~ShardIndex() { - for (auto it = btree_.begin(); it != btree_.end(); ++it) { - delete it->second.blob_; - } + btree_.visit_all([](auto& p) { delete p.second.blob_; }); } } // namespace homeobject diff --git a/src/lib/memory_backend/mem_homeobject.hpp b/src/lib/memory_backend/mem_homeobject.hpp index 5fbd6a53f..3524423fa 100644 --- a/src/lib/memory_backend/mem_homeobject.hpp +++ b/src/lib/memory_backend/mem_homeobject.hpp @@ -3,10 +3,8 @@ #include #include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include -#pragma GCC diagnostic pop +#include + #include "lib/homeobject_impl.hpp" #include "lib/blob_route.hpp" @@ -25,13 +23,13 @@ struct BlobExt { }; struct ShardIndex { - folly::ConcurrentHashMap< BlobRoute, BlobExt > btree_; + boost::concurrent_flat_map< BlobRoute, BlobExt, std::hash< BlobRoute > > btree_; ~ShardIndex(); }; class MemoryHomeObject : public HomeObjectImpl { /// Simulates the Shard=>Chunk mapping in IndexSvc - using index_svc = folly::ConcurrentHashMap< shard_id_t, std::unique_ptr< ShardIndex > >; + using index_svc = boost::concurrent_flat_map< shard_id_t, std::unique_ptr< ShardIndex > >; index_svc index_; /// @@ -74,8 +72,6 @@ class MemoryHomeObject : public HomeObjectImpl { PGManager::Result< std::vector< replace_member_task > > _list_all_replace_member_tasks(trace_id_t trace_id) override; - ShardIndex& _find_index(shard_id_t) const; - public: MemoryHomeObject(std::weak_ptr< HomeObjectApplication >&& application); ~MemoryHomeObject() override = default; diff --git a/src/lib/memory_backend/mem_pg_manager.cpp b/src/lib/memory_backend/mem_pg_manager.cpp index 943d208fd..efc64db37 100644 --- a/src/lib/memory_backend/mem_pg_manager.cpp +++ b/src/lib/memory_backend/mem_pg_manager.cpp @@ -7,7 +7,7 @@ PGManager::NullAsyncResult MemoryHomeObject::_create_pg(PGInfo&& pg_info, std::s auto lg = std::scoped_lock(_pg_lock); auto [it1, _] = _pg_map.try_emplace(pg_info.id, std::make_unique< PG >(pg_info)); RELEASE_ASSERT(_pg_map.end() != it1, "Unknown map insert error!"); - return folly::makeSemiFuture< PGManager::NullResult >(folly::Unit()); + co_return std::monostate{}; } PGManager::NullAsyncResult MemoryHomeObject::_replace_member(pg_id_t id, std::string& task_id, @@ -17,12 +17,11 @@ PGManager::NullAsyncResult MemoryHomeObject::_replace_member(pg_id_t id, std::st (void)new_member; (void)commit_quorum; (void)tid; + (void)task_id; auto lg = std::shared_lock(_pg_lock); auto it = _pg_map.find(id); - if (_pg_map.end() == it) { - return folly::makeSemiFuture< PGManager::NullResult >(folly::makeUnexpected(PGError::UNKNOWN_PG)); - } - return folly::makeSemiFuture< PGManager::NullResult >(folly::makeUnexpected(PGError::UNSUPPORTED_OP)); + if (_pg_map.end() == it) { co_return std::unexpected(PGError::UNKNOWN_PG); } + co_return std::unexpected(PGError::UNSUPPORTED_OP); } PGReplaceMemberStatus MemoryHomeObject::_get_replace_member_status(pg_id_t id, std::string& task_id, @@ -84,12 +83,14 @@ void MemoryHomeObject::_destroy_pg(pg_id_t pg_id) { } PGManager::NullResult MemoryHomeObject::_exit_pg(uuid_t group_id, peer_id_t peer_id, trace_id_t trace_id) { + (void)peer_id; + (void)trace_id; auto lg = std::unique_lock(_pg_lock); auto iter = std::find_if(_pg_map.begin(), _pg_map.end(), [group_id](const auto& entry) { return entry.second->pg_info_.replica_set_uuid == group_id; }); if (iter != _pg_map.end()) { _pg_map.erase(iter); } - return folly::Unit(); + return std::monostate{}; } PGManager::NullAsyncResult MemoryHomeObject::_flip_learner_flag(pg_id_t pg_id, peer_id_t const& member_id, @@ -100,7 +101,7 @@ PGManager::NullAsyncResult MemoryHomeObject::_flip_learner_flag(pg_id_t pg_id, p (void)is_learner; (void)commit_quorum; (void)trace_id; - return folly::makeSemiFuture< PGManager::NullResult >(folly::makeUnexpected(PGError::UNSUPPORTED_OP)); + co_return std::unexpected(PGError::UNSUPPORTED_OP); } PGManager::NullAsyncResult MemoryHomeObject::_remove_member(pg_id_t pg_id, peer_id_t const& member_id, @@ -109,7 +110,7 @@ PGManager::NullAsyncResult MemoryHomeObject::_remove_member(pg_id_t pg_id, peer_ (void)member_id; (void)commit_quorum; (void)trace_id; - return folly::makeSemiFuture< PGManager::NullResult >(folly::makeUnexpected(PGError::UNSUPPORTED_OP)); + co_return std::unexpected(PGError::UNSUPPORTED_OP); } PGManager::NullAsyncResult MemoryHomeObject::_clean_replace_member_task(pg_id_t pg_id, std::string& task_id, @@ -118,13 +119,13 @@ PGManager::NullAsyncResult MemoryHomeObject::_clean_replace_member_task(pg_id_t (void)task_id; (void)commit_quorum; (void)trace_id; - return folly::makeSemiFuture< PGManager::NullResult >(folly::makeUnexpected(PGError::UNSUPPORTED_OP)); + co_return std::unexpected(PGError::UNSUPPORTED_OP); } PGManager::Result< std::vector< replace_member_task > > MemoryHomeObject::_list_all_replace_member_tasks(trace_id_t trace_id) { (void)trace_id; - return folly::makeUnexpected(PGError::UNSUPPORTED_OP); + return std::unexpected(PGError::UNSUPPORTED_OP); } } // namespace homeobject diff --git a/src/lib/memory_backend/mem_shard_manager.cpp b/src/lib/memory_backend/mem_shard_manager.cpp index 3eb66e844..5c152d32e 100644 --- a/src/lib/memory_backend/mem_shard_manager.cpp +++ b/src/lib/memory_backend/mem_shard_manager.cpp @@ -9,12 +9,13 @@ uint64_t ShardManager::max_shard_size() { return Gi; } ShardManager::AsyncResult< ShardInfo > MemoryHomeObject::_create_shard(pg_id_t pg_owner, uint64_t size_bytes, std::string meta, trace_id_t tid) { (void)tid; + (void)meta; auto const now = get_current_timestamp(); auto info = ShardInfo(0ull, pg_owner, ShardInfo::State::OPEN, 0, now, now, size_bytes, size_bytes); { auto lg = std::scoped_lock(_pg_lock, _shard_lock); auto pg_it = _pg_map.find(pg_owner); - if (_pg_map.end() == pg_it) return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); + if (_pg_map.end() == pg_it) co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); auto& s_list = pg_it->second->shards_; info.id = make_new_shard_id(pg_owner, s_list.size()); @@ -23,9 +24,9 @@ ShardManager::AsyncResult< ShardInfo > MemoryHomeObject::_create_shard(pg_id_t p auto [_, s_happened] = _shard_map.emplace(info.id, iter); RELEASE_ASSERT(s_happened, "Duplicate Shard insertion!"); } - auto [it, happened] = index_.try_emplace(info.id, std::make_unique< ShardIndex >()); + bool happened = index_.try_emplace(info.id, std::make_unique< ShardIndex >()); RELEASE_ASSERT(happened, "Could not create BTree!"); - return info; + co_return info; } ShardManager::AsyncResult< ShardInfo > MemoryHomeObject::_seal_shard(ShardInfo const& info, trace_id_t tid) { @@ -35,7 +36,7 @@ ShardManager::AsyncResult< ShardInfo > MemoryHomeObject::_seal_shard(ShardInfo c RELEASE_ASSERT(_shard_map.end() != shard_it, "Missing ShardIterator!"); auto& shard_info = (*shard_it->second)->info; shard_info.state = ShardInfo::State::SEALED; - return shard_info; + co_return shard_info; } } // namespace homeobject diff --git a/src/lib/pg_manager.cpp b/src/lib/pg_manager.cpp index 47dc4a4f1..a48fdcba3 100644 --- a/src/lib/pg_manager.cpp +++ b/src/lib/pg_manager.cpp @@ -16,8 +16,8 @@ PGManager::NullAsyncResult HomeObjectImpl::create_pg(PGInfo&& pg_info, trace_id_ if (member.priority > 0) saw_leader = true; peers.insert(member.id); } - if (!saw_ourself || !saw_leader) { return folly::makeUnexpected(PGError::INVALID_ARG); } - return _create_pg(std::move(pg_info), peers, tid); + if (!saw_ourself || !saw_leader) { co_return std::unexpected(PGError::INVALID_ARG); } + co_return co_await _create_pg(std::move(pg_info), peers, tid); } PGManager::NullAsyncResult HomeObjectImpl::replace_member(pg_id_t id, std::string& task_id, peer_id_t const& old_member, @@ -28,10 +28,10 @@ PGManager::NullAsyncResult HomeObjectImpl::replace_member(pg_id_t id, std::strin if (old_member == new_member.id) { LOGW("rejecting identical replacement SvcId [{}]! task_id [{}] trace_id [{}]", to_string(old_member), task_id, tid); - return folly::makeUnexpected(PGError::INVALID_ARG); + co_return std::unexpected(PGError::INVALID_ARG); } - return _replace_member(id, task_id, old_member, new_member, commit_quorum, tid); + co_return co_await _replace_member(id, task_id, old_member, new_member, commit_quorum, tid); } PGReplaceMemberStatus HomeObjectImpl::get_replace_member_status(pg_id_t id, std::string& task_id, @@ -50,17 +50,17 @@ PGManager::NullResult HomeObjectImpl::exit_pg(uuid_t group_id, peer_id_t peer_id PGManager::NullAsyncResult HomeObjectImpl::flip_learner_flag(pg_id_t pg_id, peer_id_t const& member_id, bool is_learner, uint32_t commit_quorum, trace_id_t trace_id) { - return _flip_learner_flag(pg_id, member_id, is_learner, commit_quorum, trace_id); + co_return co_await _flip_learner_flag(pg_id, member_id, is_learner, commit_quorum, trace_id); } PGManager::NullAsyncResult HomeObjectImpl::remove_member(pg_id_t pg_id, peer_id_t const& member_id, uint32_t commit_quorum, trace_id_t trace_id) { - return _remove_member(pg_id, member_id, commit_quorum, trace_id); + co_return co_await _remove_member(pg_id, member_id, commit_quorum, trace_id); } PGManager::NullAsyncResult HomeObjectImpl::clean_replace_member_task(pg_id_t pg_id, std::string& task_id, uint32_t commit_quorum, trace_id_t trace_id) { - return _clean_replace_member_task(pg_id, task_id, commit_quorum, trace_id); + co_return co_await _clean_replace_member_task(pg_id, task_id, commit_quorum, trace_id); } PGManager::Result< std::vector< replace_member_task > > diff --git a/src/lib/shard_manager.cpp b/src/lib/shard_manager.cpp index 43aa5f6db..6a43bcd49 100644 --- a/src/lib/shard_manager.cpp +++ b/src/lib/shard_manager.cpp @@ -1,3 +1,5 @@ +#include + #include "homeobject_impl.hpp" namespace homeobject { @@ -7,54 +9,40 @@ std::shared_ptr< ShardManager > HomeObjectImpl::shard_manager() { return shared_ ShardManager::AsyncResult< ShardInfo > HomeObjectImpl::create_shard(pg_id_t pg_owner, uint64_t size_bytes, std::string meta, trace_id_t tid) { if (0 == size_bytes || max_shard_size() < size_bytes) - return folly::makeUnexpected(ShardError(ShardErrorCode::INVALID_ARG)); - return _defer().thenValue( - [this, pg_owner, size_bytes, meta, tid](auto) mutable -> ShardManager::AsyncResult< ShardInfo > { - return _create_shard(pg_owner, size_bytes, meta, tid); - }); + co_return std::unexpected(ShardError(ShardErrorCode::INVALID_ARG)); + co_return co_await _create_shard(pg_owner, size_bytes, meta, tid); } ShardManager::AsyncResult< InfoList > HomeObjectImpl::list_shards(pg_id_t pgid, trace_id_t tid) const { - return _defer().thenValue([this, pgid, tid](auto) mutable -> ShardManager::Result< InfoList > { - std::shared_lock lock_guard(_pg_lock); - auto iter = _pg_map.find(pgid); - if (iter == _pg_map.cend()) { return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); } - auto& pg = iter->second; - - auto info_l = std::list< ShardInfo >(); - for (auto const& shard : pg->shards_) { - LOGD("found [shard={}], trace_id=[{}]", shard->info.id, tid); - info_l.push_back(shard->info); - } - return info_l; - }); + std::shared_lock lock_guard(_pg_lock); + auto iter = _pg_map.find(pgid); + if (iter == _pg_map.cend()) { co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_PG)); } + auto& pg = iter->second; + + auto info_l = std::list< ShardInfo >(); + for (auto const& shard : pg->shards_) { + LOGD("found [shard={}], trace_id=[{}]", shard->info.id, tid); + info_l.push_back(shard->info); + } + co_return info_l; } ShardManager::AsyncResult< ShardInfo > HomeObjectImpl::seal_shard(shard_id_t id, trace_id_t tid) { - return _get_shard(id, tid).thenValue([this, tid](auto const e) mutable -> ShardManager::AsyncResult< ShardInfo > { - if (!e) return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); - if (ShardInfo::State::SEALED == e.value().state) return e.value(); - return _seal_shard(e.value(), tid); - }); + auto e = _get_shard(id, tid); + if (!e) co_return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); + if (ShardInfo::State::SEALED == e.value().state) co_return e.value(); + co_return co_await _seal_shard(e.value(), tid); } ShardManager::AsyncResult< ShardInfo > HomeObjectImpl::get_shard(shard_id_t id, trace_id_t tid) const { - return _get_shard(id, tid).thenValue([this](auto const e) mutable -> ShardManager::Result< ShardInfo > { - if (!e) { return folly::makeUnexpected(e.error()); } - return e.value(); - }); + co_return _get_shard(id, tid); } -/// -// This is used as a first call for many operations and initializes the Future. -// -folly::Future< ShardManager::Result< ShardInfo > > HomeObjectImpl::_get_shard(shard_id_t id, trace_id_t tid) const { - return _defer().thenValue([this, id, tid](auto) -> ShardManager::Result< ShardInfo > { - auto lg = std::shared_lock(_shard_lock); - if (auto it = _shard_map.find(id); _shard_map.end() != it) return (*it->second)->info; - LOGE("Couldn't find shard id in shard map {}, trace_id=[{}]", id, tid); - return folly::makeUnexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); - }); +ShardManager::Result< ShardInfo > HomeObjectImpl::_get_shard(shard_id_t id, trace_id_t tid) const { + auto lg = std::shared_lock(_shard_lock); + if (auto it = _shard_map.find(id); _shard_map.end() != it) return (*it->second)->info; + LOGE("Couldn't find shard id in shard map {}, trace_id=[{}]", id, tid); + return std::unexpected(ShardError(ShardErrorCode::UNKNOWN_SHARD)); } uint64_t HomeObjectImpl::get_current_timestamp() { diff --git a/src/lib/tests/BlobManagerTest.cpp b/src/lib/tests/BlobManagerTest.cpp index a802dc58b..3538d6f73 100644 --- a/src/lib/tests/BlobManagerTest.cpp +++ b/src/lib/tests/BlobManagerTest.cpp @@ -1,5 +1,9 @@ #include -#include +#include +#include + +#include +#include #include #include @@ -9,95 +13,122 @@ using homeobject::Blob; using homeobject::BlobError; using homeobject::BlobErrorCode; +namespace { + +// Coroutine parameters (not lambda captures) are copied into the frame. A temporary IIFE +// coroutine lambda is destroyed at the end of the full-expression, so captures would dangle. + +sisl::async::task< std::monostate > get_known_blob(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, blob_id_t blob_id) { + auto e = co_await homeobj->blob_manager()->get(shard_id, blob_id); + EXPECT_TRUE(!!e); + if (e) { + EXPECT_STREQ(e->user_key.c_str(), "test_blob"); + EXPECT_EQ(e->object_off, 4 * Mi); + } + co_return std::monostate{}; +} + +sisl::async::task< std::monostate > get_ignore(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, blob_id_t blob_id) { + (void)co_await homeobj->blob_manager()->get(shard_id, blob_id); + co_return std::monostate{}; +} + +sisl::async::task< std::monostate > put_unknown_shard(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, homeobject::trace_id_t tid) { + auto e = + co_await homeobj->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul}, tid); + EXPECT_FALSE(!!e); + EXPECT_EQ(BlobErrorCode::UNKNOWN_SHARD, e.error().code); + co_return std::monostate{}; +} + +sisl::async::task< std::monostate > put_ok(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, uint32_t size, char const* user_key, + homeobject::trace_id_t tid) { + auto e = + co_await homeobj->blob_manager()->put(shard_id, Blob{sisl::io_blob_safe(size, 512u), user_key, 4 * Mi}, tid); + EXPECT_TRUE(!!e); + if (e) { LOGINFO("Successfully put blob, shard {}, blobID {}", shard_id, *e); } + co_return std::monostate{}; +} + +sisl::async::task< std::monostate > del_unknown_shard(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, blob_id_t blob_id, + homeobject::trace_id_t tid) { + auto e = co_await homeobj->blob_manager()->del(shard_id, blob_id, tid); + EXPECT_FALSE(!!e); + EXPECT_EQ(BlobErrorCode::UNKNOWN_SHARD, e.error().getCode()); + co_return std::monostate{}; +} + +sisl::async::task< std::monostate > del_racing(std::shared_ptr< homeobject::HomeObject > homeobj, + homeobject::shard_id_t shard_id, blob_id_t blob_id, + homeobject::trace_id_t tid) { + auto e = co_await homeobj->blob_manager()->del(shard_id, blob_id, tid); + // Racing with other threads / putBlob: success or UNKNOWN_BLOB. + LOGINFO("Deleted blob, shard {}, blobID {}, success {}", shard_id, blob_id, !!e); + if (!e) { EXPECT_EQ(BlobErrorCode::UNKNOWN_BLOB, e.error().getCode()); } + co_return std::monostate{}; +} + +} // namespace + TEST_F(TestFixture, BasicBlobTests) { auto const batch_sz = 4; std::mutex call_lock; - auto calls = std::list< folly::SemiFuture< folly::Unit > >(); + auto calls = std::vector< sisl::async::task< std::monostate > >(); auto t_v = std::vector< std::thread >(); for (auto k = 0; batch_sz > k; ++k) { t_v.push_back(std::thread([this, &call_lock, &calls, batch_sz]() mutable { - auto our_calls = std::list< folly::SemiFuture< folly::Unit > >(); + auto our_calls = std::vector< sisl::async::task< std::monostate > >(); for (auto i = _blob_id + _shard_2.id + 1; (_blob_id + _shard_1.id + 1) + (SISL_OPTIONS["num_iters"].as< uint64_t >() / batch_sz) > i; ++i) { auto tid = homeobject::generateRandomTraceId(); - our_calls.push_back(homeobj_->blob_manager()->get(_shard_1.id, _blob_id).deferValue([](auto const& e) { - EXPECT_TRUE(!!e); - e.then([](auto const& blob) { - EXPECT_STREQ(blob.user_key.c_str(), "test_blob"); - EXPECT_EQ(blob.object_off, 4 * Mi); - }); - })); - our_calls.push_back(homeobj_->blob_manager()->get(i, _blob_id).deferValue([](auto const& e) {})); - our_calls.push_back( - homeobj_->blob_manager()->get(_shard_1.id, (i - _shard_2.id)).deferValue([](auto const&) {})); - our_calls.push_back( - homeobj_->blob_manager()->get(_shard_2.id, (i - _shard_2.id)).deferValue([](auto const&) {})); - our_calls.push_back( - homeobj_->blob_manager() - ->put(i, Blob{sisl::io_blob_safe(512u, 512u), "test_blob", 0ul}, tid) - .deferValue([](auto const& e) { EXPECT_EQ(BlobErrorCode::UNKNOWN_SHARD, e.error().code); })); + our_calls.push_back(get_known_blob(homeobj_, _shard_1.id, _blob_id)); + our_calls.push_back(get_ignore(homeobj_, i, _blob_id)); + our_calls.push_back(get_ignore(homeobj_, _shard_1.id, (i - _shard_2.id))); + our_calls.push_back(get_ignore(homeobj_, _shard_2.id, (i - _shard_2.id))); + our_calls.push_back(put_unknown_shard(homeobj_, i, tid)); LOGINFO("Calling to put blob, shard {}", _shard_1.id); - our_calls.push_back( - homeobj_->blob_manager() - ->put(_shard_1.id, Blob{sisl::io_blob_safe(4 * Ki, 512u), "test_blob", 4 * Mi}, tid) - .deferValue([this](auto const& e) { - EXPECT_TRUE(!!e); - e.then([this](auto const& blob_id) { - LOGINFO("Successfully put blob, shard {}, blobID {}", _shard_1.id, blob_id); - }); - })); - our_calls.push_back( - homeobj_->blob_manager() - ->put(_shard_2.id, Blob{sisl::io_blob_safe(8 * Ki, 512u), "test_blob_2", 4 * Mi}, tid) - .deferValue([](auto const& e) { EXPECT_TRUE(!!e); })); - our_calls.push_back(homeobj_->blob_manager()->del(i, _blob_id, tid).deferValue([](auto const& e) { - EXPECT_FALSE(!!e); - EXPECT_EQ(BlobErrorCode::UNKNOWN_SHARD, e.error().getCode()); - })); + our_calls.push_back(put_ok(homeobj_, _shard_1.id, 4 * Ki, "test_blob", tid)); + our_calls.push_back(put_ok(homeobj_, _shard_2.id, 8 * Ki, "test_blob_2", tid)); + our_calls.push_back(del_unknown_shard(homeobj_, i, _blob_id, tid)); LOGINFO("Calling to Deleting blob, shard {}, blobID {}", _shard_1.id, (i - _shard_2.id)); - our_calls.push_back(homeobj_->blob_manager() - ->del(_shard_1.id, (i - _shard_2.id), tid) - .deferValue([this, i](auto const& e) { - // It is a racing test with other threads as well as putBlob - // the result should be either success or failed with UNKNOWN_BLOB - LOGINFO("Deleted blob, shard {}, blobID {}, success {}", _shard_1.id, - (i - _shard_2.id), !!e); - if (!!e) { - return; - } else { - EXPECT_FALSE(!!e); - EXPECT_EQ(BlobErrorCode::UNKNOWN_BLOB, e.error().getCode()); - } - })); + our_calls.push_back(del_racing(homeobj_, _shard_1.id, (i - _shard_2.id), tid)); } auto lg = std::scoped_lock(call_lock); - calls.splice(calls.end(), std::move(our_calls)); + // exec::task is move-constructible but not assignable, so avoid vector::insert. + calls.reserve(calls.size() + our_calls.size()); + for (auto& t : our_calls) { + calls.push_back(std::move(t)); + } })); } for (auto& t : t_v) t.join(); - folly::collectAll(calls).via(folly::getGlobalCPUExecutor()).get(); - EXPECT_TRUE(homeobj_->shard_manager()->seal_shard(_shard_1.id).get()); + sisl::async::sync_get(sisl::async::when_all(std::move(calls))); + EXPECT_TRUE(sisl::async::sync_get(homeobj_->shard_manager()->seal_shard(_shard_1.id))); auto tid = homeobject::generateRandomTraceId(); - auto p_e = homeobj_->blob_manager() - ->put(_shard_1.id, Blob{sisl::io_blob_safe(4 * Ki, 512u), "test_blob", 4 * Mi}, tid) - .get(); + auto p_e = sisl::async::sync_get( + homeobj_->blob_manager()->put(_shard_1.id, Blob{sisl::io_blob_safe(4 * Ki, 512u), "test_blob", 4 * Mi}, tid)); ASSERT_FALSE(!!p_e); EXPECT_EQ(BlobErrorCode::SEALED_SHARD, p_e.error().getCode()); // BLOB exists - EXPECT_TRUE(homeobj_->blob_manager()->get(_shard_1.id, _blob_id).get()); + EXPECT_TRUE(sisl::async::sync_get(homeobj_->blob_manager()->get(_shard_1.id, _blob_id))); // BLOB is deleted - EXPECT_TRUE(homeobj_->blob_manager()->del(_shard_1.id, _blob_id, tid).get()); + EXPECT_TRUE(sisl::async::sync_get(homeobj_->blob_manager()->del(_shard_1.id, _blob_id, tid))); // BLOB is now unknown - auto g_e = homeobj_->blob_manager()->get(_shard_1.id, _blob_id).get(); + auto g_e = sisl::async::sync_get(homeobj_->blob_manager()->get(_shard_1.id, _blob_id)); ASSERT_FALSE(!!g_e); EXPECT_EQ(BlobErrorCode::UNKNOWN_BLOB, g_e.error().getCode()); // Delete is Idempotent - EXPECT_TRUE(homeobj_->blob_manager()->del(_shard_1.id, _blob_id, tid).get()); + EXPECT_TRUE(sisl::async::sync_get(homeobj_->blob_manager()->del(_shard_1.id, _blob_id, tid))); } diff --git a/src/lib/tests/PGManagerTest.cpp b/src/lib/tests/PGManagerTest.cpp index 47b3da36e..f9b6e0b2f 100644 --- a/src/lib/tests/PGManagerTest.cpp +++ b/src/lib/tests/PGManagerTest.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include "lib/tests/fixture_app.hpp" @@ -8,37 +10,37 @@ using homeobject::PGInfo; using homeobject::PGMember; TEST_F(TestFixture, CreatePgEmpty) { - EXPECT_EQ(homeobj_->pg_manager()->create_pg(PGInfo(0u)).get().error(), PGError::INVALID_ARG); + EXPECT_EQ(sisl::async::sync_get(homeobj_->pg_manager()->create_pg(PGInfo(0u))).error(), PGError::INVALID_ARG); } TEST_F(TestFixture, CreatePgNoLeader) { auto info = PGInfo(0u); info.members.insert(PGMember{boost::uuids::random_generator()()}); - EXPECT_EQ(homeobj_->pg_manager()->create_pg(std::move(info)).get().error(), PGError::INVALID_ARG); + EXPECT_EQ(sisl::async::sync_get(homeobj_->pg_manager()->create_pg(std::move(info))).error(), PGError::INVALID_ARG); } TEST_F(TestFixture, CreatePgNotMember) { auto info = PGInfo(0u); info.members.insert(PGMember{boost::uuids::random_generator()(), "unknown", 1}); - EXPECT_EQ(homeobj_->pg_manager()->create_pg(std::move(info)).get().error(), PGError::INVALID_ARG); + EXPECT_EQ(sisl::async::sync_get(homeobj_->pg_manager()->create_pg(std::move(info))).error(), PGError::INVALID_ARG); } TEST_F(TestFixture, CreateDuplicatePg) { auto info = PGInfo(_pg_id); info.members.insert(PGMember{boost::uuids::random_generator()(), "peer3", 6}); // info.members.insert(PGMember{boost::uuids::random_generator()(), "peer4", 2}); - EXPECT_EQ(homeobj_->pg_manager()->create_pg(std::move(info)).get().error(), PGError::INVALID_ARG); + EXPECT_EQ(sisl::async::sync_get(homeobj_->pg_manager()->create_pg(std::move(info))).error(), PGError::INVALID_ARG); } TEST_F(TestFixture, Migrate) { std::string task_id = "task_id"; - EXPECT_EQ(homeobj_->pg_manager() - ->replace_member(UINT16_MAX, task_id, boost::uuids::random_generator()(), - PGMember{boost::uuids::random_generator()()}, 0) - .get() + EXPECT_EQ(sisl::async::sync_get( + homeobj_->pg_manager()->replace_member(UINT16_MAX, task_id, boost::uuids::random_generator()(), + PGMember{boost::uuids::random_generator()()}, 0)) .error(), PGError::UNKNOWN_PG); - EXPECT_EQ(homeobj_->pg_manager()->replace_member(_pg_id, task_id, _peer1, PGMember{_peer1}).get().error(), + EXPECT_EQ(sisl::async::sync_get(homeobj_->pg_manager()->replace_member(_pg_id, task_id, _peer1, PGMember{_peer1})) + .error(), PGError::INVALID_ARG); // TODO enable after HO test framework is enabled #if 0 diff --git a/src/lib/tests/ShardManagerTest.cpp b/src/lib/tests/ShardManagerTest.cpp index 1c1dc56a1..832b9f53f 100644 --- a/src/lib/tests/ShardManagerTest.cpp +++ b/src/lib/tests/ShardManagerTest.cpp @@ -1,3 +1,5 @@ +#include + #include #include "lib/tests/fixture_app.hpp" @@ -8,67 +10,63 @@ using homeobject::ShardInfo; TEST_F(TestFixture, CreateShardTooBig) { EXPECT_EQ(ShardErrorCode::INVALID_ARG, - homeobj_->shard_manager() - ->create_shard(_pg_id, homeobject::ShardManager::max_shard_size() + 1, "shard meta") - .get() + sisl::async::sync_get(homeobj_->shard_manager()->create_shard( + _pg_id, homeobject::ShardManager::max_shard_size() + 1, "shard meta")) .error() .getCode()); } TEST_F(TestFixture, CreateShardTooSmall) { - EXPECT_EQ(ShardErrorCode::INVALID_ARG, - homeobj_->shard_manager()->create_shard(_pg_id, 0ul, "shard meta").get().error().getCode()); + EXPECT_EQ( + ShardErrorCode::INVALID_ARG, + sisl::async::sync_get(homeobj_->shard_manager()->create_shard(_pg_id, 0ul, "shard meta")).error().getCode()); } TEST_F(TestFixture, CreateShardNoPg) { - EXPECT_EQ(ShardErrorCode::UNKNOWN_PG, - homeobj_->shard_manager()->create_shard(_pg_id + 1, Mi, "shard meta").get().error().getCode()); + EXPECT_EQ( + ShardErrorCode::UNKNOWN_PG, + sisl::async::sync_get(homeobj_->shard_manager()->create_shard(_pg_id + 1, Mi, "shard meta")).error().getCode()); } TEST_F(TestFixture, GetUnknownShard) { EXPECT_EQ(ShardErrorCode::UNKNOWN_SHARD, - homeobj_->shard_manager()->get_shard(_shard_2.id + 1).get().error().getCode()); + sisl::async::sync_get(homeobj_->shard_manager()->get_shard(_shard_2.id + 1)).error().getCode()); } TEST_F(TestFixture, GetKnownShard) { - auto e = homeobj_->shard_manager()->get_shard(_shard_1.id).get(); + auto e = sisl::async::sync_get(homeobj_->shard_manager()->get_shard(_shard_1.id)); ASSERT_TRUE(!!e); - e.then([this](auto const& info) { - EXPECT_TRUE(info.id == _shard_1.id); - EXPECT_TRUE(info.placement_group == _shard_1.placement_group); - EXPECT_EQ(info.state, ShardInfo::State::OPEN); - }); + EXPECT_TRUE(e->id == _shard_1.id); + EXPECT_TRUE(e->placement_group == _shard_1.placement_group); + EXPECT_EQ(e->state, ShardInfo::State::OPEN); } TEST_F(TestFixture, ListShardsNoPg) { - EXPECT_EQ(ShardErrorCode::UNKNOWN_PG, homeobj_->shard_manager()->list_shards(_pg_id + 1).get().error().getCode()); + EXPECT_EQ(ShardErrorCode::UNKNOWN_PG, + sisl::async::sync_get(homeobj_->shard_manager()->list_shards(_pg_id + 1)).error().getCode()); } TEST_F(TestFixture, ListShards) { - auto e = homeobj_->shard_manager()->list_shards(_pg_id).get(); + auto e = sisl::async::sync_get(homeobj_->shard_manager()->list_shards(_pg_id)); ASSERT_TRUE(!!e); - e.then([this](auto const& info_list) { - ASSERT_EQ(info_list.size(), 2); - EXPECT_TRUE(info_list.begin()->id == _shard_1.id); - EXPECT_TRUE(info_list.begin()->placement_group == _shard_1.placement_group); - EXPECT_EQ(info_list.begin()->state, ShardInfo::State::OPEN); - }); + ASSERT_EQ(e->size(), 2); + EXPECT_TRUE(e->begin()->id == _shard_1.id); + EXPECT_TRUE(e->begin()->placement_group == _shard_1.placement_group); + EXPECT_EQ(e->begin()->state, ShardInfo::State::OPEN); } TEST_F(TestFixture, SealShardNoShard) { EXPECT_EQ(ShardErrorCode::UNKNOWN_SHARD, - homeobj_->shard_manager()->seal_shard(_shard_2.id + 1).get().error().getCode()); + sisl::async::sync_get(homeobj_->shard_manager()->seal_shard(_shard_2.id + 1)).error().getCode()); } TEST_F(TestFixture, SealShard) { for (auto i = 0; 2 > i; ++i) { auto tid = homeobject::generateRandomTraceId(); - auto e = homeobj_->shard_manager()->seal_shard(_shard_1.id, tid).get(); + auto e = sisl::async::sync_get(homeobj_->shard_manager()->seal_shard(_shard_1.id, tid)); ASSERT_TRUE(!!e); - e.then([this](auto const& info) { - EXPECT_TRUE(info.id == _shard_1.id); - EXPECT_TRUE(info.placement_group == _shard_1.placement_group); - EXPECT_EQ(info.state, ShardInfo::State::SEALED); - }); + EXPECT_TRUE(e->id == _shard_1.id); + EXPECT_TRUE(e->placement_group == _shard_1.placement_group); + EXPECT_EQ(e->state, ShardInfo::State::SEALED); } } diff --git a/src/lib/tests/fixture_app.cpp b/src/lib/tests/fixture_app.cpp index c71007247..8a5af0f3f 100644 --- a/src/lib/tests/fixture_app.cpp +++ b/src/lib/tests/fixture_app.cpp @@ -1,7 +1,8 @@ #include -#include #include +#include + #include "fixture_app.hpp" SISL_OPTION_GROUP( @@ -14,7 +15,7 @@ SISL_OPTION_GROUP( SISL_LOGGING_INIT(HOMEOBJECT_LOG_MODS) -#define test_options logging, homeobject, config, test_home_object +#define test_options logging, config, test_home_object SISL_OPTIONS_ENABLE(test_options) @@ -47,35 +48,32 @@ void TestFixture::SetUp() { auto tid = homeobject::generateRandomTraceId(); LOGDEBUG("Setup Pg, trace_id={}", tid); - EXPECT_TRUE(homeobj_->pg_manager()->create_pg(std::move(info), tid).get()); + EXPECT_TRUE(sisl::async::sync_get(homeobj_->pg_manager()->create_pg(std::move(info), tid))); LOGDEBUG("Setup Shards, trace_id={}", tid); - auto s_e = homeobj_->shard_manager()->create_shard(_pg_id, Mi, "shard meta", tid).get(); + auto s_e = sisl::async::sync_get(homeobj_->shard_manager()->create_shard(_pg_id, Mi, "shard meta", tid)); ASSERT_TRUE(!!s_e); - s_e.then([this](auto&& i) { _shard_1 = std::move(i); }); + _shard_1 = std::move(*s_e); - s_e = homeobj_->shard_manager()->create_shard(_pg_id, Mi, "shard meta", tid).get(); + s_e = sisl::async::sync_get(homeobj_->shard_manager()->create_shard(_pg_id, Mi, "shard meta", tid)); ASSERT_TRUE(!!s_e); - s_e.then([this](auto&& i) { _shard_2 = std::move(i); }); + _shard_2 = std::move(*s_e); LOGDEBUG("Get on empty Shard={}, trace_id={}", _shard_1.id, tid); - auto g_e = homeobj_->blob_manager()->get(_shard_1.id, 0, 0, 0, tid).get(); + auto g_e = sisl::async::sync_get(homeobj_->blob_manager()->get(_shard_1.id, 0, 0, 0, tid)); ASSERT_FALSE(g_e); EXPECT_EQ(homeobject::BlobErrorCode::UNKNOWN_BLOB, g_e.error().getCode()); LOGDEBUG("Insert Blob to={}, trace_id={}", _shard_1.id, tid); - auto o_e = homeobj_->blob_manager() - ->put(_shard_1.id, homeobject::Blob{sisl::io_blob_safe(4 * Ki, 512u), "test_blob", 4 * Mi}, tid) - .get(); + auto o_e = sisl::async::sync_get(homeobj_->blob_manager()->put( + _shard_1.id, homeobject::Blob{sisl::io_blob_safe(4 * Ki, 512u), "test_blob", 4 * Mi}, tid)); EXPECT_TRUE(!!o_e); - o_e.then([this](auto&& b) mutable { _blob_id = std::move(b); }); + _blob_id = std::move(*o_e); - g_e = homeobj_->blob_manager()->get(_shard_1.id, _blob_id, 0, 0, tid).get(); + g_e = sisl::async::sync_get(homeobj_->blob_manager()->get(_shard_1.id, _blob_id, 0, 0, tid)); EXPECT_TRUE(!!g_e); - g_e.then([](auto&& blob) { - EXPECT_STREQ(blob.user_key.c_str(), "test_blob"); - EXPECT_EQ(blob.object_off, 4 * Mi); - }); + EXPECT_STREQ(g_e->user_key.c_str(), "test_blob"); + EXPECT_EQ(g_e->object_off, 4 * Mi); // cover the memory version of get_stats // homestore version has a dedicated test for this. @@ -100,7 +98,5 @@ int main(int argc, char* argv[]) { SISL_OPTIONS_LOAD(parsed_argc, argv, test_options); sisl::logging::SetLogger(std::string(argv[0])); sisl::logging::SetLogPattern("[%D %T%z] [%^%L%$] [%t] %v"); - parsed_argc = 1; - auto f = ::folly::Init(&parsed_argc, &argv, true); return RUN_ALL_TESTS(); } diff --git a/test_package/test_package.cpp b/test_package/test_package.cpp index ba2898fe5..1f79e889d 100644 --- a/test_package/test_package.cpp +++ b/test_package/test_package.cpp @@ -1,11 +1,10 @@ -#include #include #include #include SISL_LOGGING_INIT(HOMEOBJECT_LOG_MODS) -SISL_OPTIONS_ENABLE(logging, homeobject) +SISL_OPTIONS_ENABLE(logging) class TestApp : public homeobject::HomeObjectApplication { public: @@ -22,13 +21,10 @@ class TestApp : public homeobject::HomeObjectApplication { }; int main(int argc, char** argv) { - SISL_OPTIONS_LOAD(argc, argv, logging, homeobject) + SISL_OPTIONS_LOAD(argc, argv, logging) sisl::logging::SetLogger(std::string(argv[0])); spdlog::set_pattern("[%D %T%z] [%^%l%$] [%n] [%t] %v"); - auto parsed_argc = 1; - auto f = ::folly::Init(&parsed_argc, &argv, true); - auto a = std::make_shared< TestApp >(); homeobject::init_homeobject(a); return 0; diff --git a/v4upgrade.md b/v4upgrade.md deleted file mode 100644 index a5739ad46..000000000 --- a/v4upgrade.md +++ /dev/null @@ -1,41 +0,0 @@ -# v4 upgrade Note - -## Version Changes -- DataHeader version: 0x02 -- BlobHeader version: 0x02 -- Shard superblock version: 0x02 - -Currently only support version backward compatibility. - -## Incompatibilities and Breaking Changes - -### 1. Fixed-Size BlobHeader (4KB) -- Add a blob_header_version=0x02 -- Move user key into the BlobHeader. Note, currently gw doesn't pass user key, but in next version, it will put object identifier as user key. -- More padding to align BlobHeader to 4KB - -### 2. Shard Metadata Persistence -- Add a shard sb_verison=0x02 -- Add metadata field to ShardInfo structure for future use - -### 3. Partial Read Optimization -**Impact:** New capability, backward compatible for new reads but changes read behavior -- Added `allow_skip_verify` parameter to enable optimized partial reads -- When enabled partial read, skips header read and directly reads requested data range -- Improves HDD performance by reducing I/O operations -- Only works with v4's fixed-size header (predictable data offset) - -## Upgrade Plan -1. **Stop PG traffic** to all HomeObject instances -2. **Rewrite all existing data** and upgrade to v4 in place using offline refactor tool: - - Blob data - - Read each blob with v3 format - - Rewrite with v4 fixed-size BlobHeader format - - Embed user_key into BlobHeader (validate max 1024 bytes) - - Update data_offset to fixed value (4096) - - Recalculate hash without separate user_key parameter - - Update shard metadata - - Rewrite shard superblock with version 0x02 - - Rewrite shard header and footer in chunks -3. **If chunk space is insufficient**, migrate the data from upper layer(rclone nuobject data) -4. **Restore service and traffic** \ No newline at end of file diff --git a/v5upgrade.md b/v5upgrade.md new file mode 100644 index 000000000..4dcebef29 --- /dev/null +++ b/v5upgrade.md @@ -0,0 +1,61 @@ +# v5 upgrade Note + +## Version Changes +- HomeObject package version: 5.0.0 (was 4.x) +- C++ standard: C++23 (was C++20) +- Conan: 2.x required (was 1.x) +- Upstream: sisl ^14.9, homestore ^8.3 (both already Folly-free); Folly and Pistache dropped entirely + +This is a breaking change to the public API surface, not to the on-disk format. No data rewrite is required. + +## Incompatibilities and Breaking Changes + +### 1. Public API Types (`src/include/homeobject/common.hpp`) +- `Result`: `folly::Expected` → `std::expected` +- `AsyncResult`: `folly::SemiFuture>` → `sisl::async::task>` (a C++23 coroutine) +- `NullResult` / `NullAsyncResult` success value: `folly::Unit` → `std::monostate` + +### 2. Async Execution Model +- All async manager methods are now coroutines (`co_await`/`co_return`) instead of Folly futures chained with `thenValue`/`via`/`collectAll`. +- `HomeObjectImpl::executor_` and `_defer()` removed; the `--executor immediate|cpu|io` CLI option is gone. +- Non-coroutine callers must drive an `AsyncResult` with `co_await`, `sisl::async::sync_get` (non-reactor threads only), or `detach`/`detach_then`. + +### 3. Error Handling +- `result.hasError()` → `if (!result)` +- `folly::makeUnexpected(e)` → `std::unexpected(e)` + +### 4. Concurrency Containers +- `folly::ConcurrentHashMap` → `boost::concurrent_flat_map` (no persistent iterators; use `cvisit`/`visit`/`try_emplace`/`erase_if`) +- `folly::small_vector` → `boost::container::small_vector` +- GC `folly::MPMCQueue` → `sisl::BoundedMPMCQueue` (non-blocking `write`/`read`; GC reserved-chunk logic reworked to not simulate blocking) +- Local `homestore_backend/MPMCPriorityQueue.hpp` removed → `sisl::MPMCPriorityQueue` +- `folly::EvictingCacheMap` → `sisl::LruMap` +- `folly::Uri` removed → manual `host:port` parsing + +### 5. Thread Pools +- Scrub's and GC's `IOThreadPoolExecutor` pools are gone. GC now runs on two `std::jthread`s per pdev (normal/emergent); scrub tasks run as detached coroutines on iomgr workers. + +### 6. HTTP Layer +- Pistache handlers replaced by `sisl`/`httplib` (`sisl::http_method` + `ioenvironment.get_http_server()->setup_route(...)`). +- Test HTTP client fixtures migrated from the Pistache client to `httplib::Client`. + +### 7. HomeStore 8.x API Alignment +- Headers: `*.h` → `*.hpp` (`repl_dev.hpp`, `blk.hpp`, `chunk_selector.hpp`, `vchunk.hpp`, `crc.hpp`) +- Types: `ReplDevListener` → `repl_dev_listener`, `ReplDev` → `repl_dev`, `MultiBlkId` → `multi_blk_id` +- `async_alloc_write`'s last parameter changed from `bool part_of_batch` to `io_batch* batch = nullptr` + +### 8. Build +- Conan 1.x → 2.x (`required_conan_version = ">=2.0"`) +- `check_min_cppstd(self, 23)`; root `CMakeLists.txt` sets `CMAKE_CXX_STANDARD 23` +- No direct or transitive dependency on `folly` or `pistache` + +## Upgrade Plan +1. **Update toolchain**: install Conan 2.x and a C++23-capable compiler. +2. **Rebuild upstream packages in order**: sisl (^14.9) → iomgr (^13.0) → nuraft_mesg (^5.0) → homestore (^8.3), using `@oss/dev` channel when tracking this tree. +3. **Update downstream call sites**: + - Replace `AsyncResult` consumption (`.get()`, `thenValue`) with `co_await`, `sisl::async::sync_get`, or `detach`/`detach_then`. + - Replace `hasError()` checks with `if (!r)`. + - Replace `folly::Unit` / `makeUnexpected` usages with `std::monostate` / `std::unexpected`. +4. **Memory-backend-only development**: build with `-o homeobject/*:with_homestore=False` (sets `HOMEOBJECT_MEMORY_ONLY`). +5. **Verify no Folly/Pistache remains**: `rg -i 'folly|pistache' HomeObject --glob '!docs/**'` should return nothing (comments in docs are fine). +6. **Run the full test suite** (`homestore_test_pg`/`_shard`/`_blob`/`_misc`/`_gc`/`_scrubber`, dynamic tests with `enable_http=true`) before publishing HomeObject 5.x. From 49bfcd8401310a1050134ba527a55adf2c86aa8a Mon Sep 17 00:00:00 2001 From: Jie Yao Date: Tue, 29 Sep 2026 10:01:00 +0800 Subject: [PATCH 2/2] fix CI flow --- .github/workflows/build_dependencies.yml | 3 +++ CMakeLists.txt | 8 ++++++-- conanfile.py | 19 +++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_dependencies.yml b/.github/workflows/build_dependencies.yml index 61772304b..55fa17dbe 100644 --- a/.github/workflows/build_dependencies.yml +++ b/.github/workflows/build_dependencies.yml @@ -231,6 +231,7 @@ jobs: sudo df -h conan install \ ${pre} \ + -o homeobject/*:prerelease=${{ inputs.prerelease }} \ -o sisl/*:malloc_impl=${{ inputs.malloc-impl }} \ -o iomgr/*:testing=off \ -o homestore/*:testing=off \ @@ -292,6 +293,7 @@ jobs: pre=$([[ "${{ inputs.build-type }}" != "Debug" ]] && echo "-o iomgr/*:prerelease=${{ inputs.prerelease }}" || echo "") conan create \ ${pre} \ + -o homeobject/*:prerelease=${{ inputs.prerelease }} \ -o sisl/*:malloc_impl=${{ inputs.malloc-impl }} \ -o iomgr/*:testing=off \ -o homestore/*:testing=off \ @@ -307,6 +309,7 @@ jobs: pre=$([[ "${{ inputs.build-type }}" != "Debug" ]] && echo "-o iomgr/*:prerelease=${{ inputs.prerelease }}" || echo "") conan build \ ${pre} \ + -o homeobject/*:prerelease=${{ inputs.prerelease }} \ -o sisl/*:malloc_impl=${{ inputs.malloc-impl }} \ -o iomgr/*:testing=off \ -o homestore/*:testing=off \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c3ba205e..a954fd079 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ enable_testing() if ((DEFINED CODE_COVERAGE) AND (${CODE_COVERAGE})) include (cmake/CodeCoverage.cmake) APPEND_COVERAGE_COMPILER_FLAGS() -elseif ((DEFINED MEMORY_SANITIZER_ON) AND (${MEMORY_SANITIZER_ON})) - message(WARNING "********* Running with Memory Sanitizer *********") +elseif ((DEFINED ADDRESS_SANITIZER_ON) AND (${ADDRESS_SANITIZER_ON})) + message(WARNING "********* Running with Address Sanitizer *********") add_flags("-fsanitize=address \ -fsanitize=undefined \ -fsanitize-address-use-after-scope \ @@ -27,6 +27,10 @@ elseif ((DEFINED MEMORY_SANITIZER_ON) AND (${MEMORY_SANITIZER_ON})) -fno-optimize-sibling-calls " ) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -fsanitize=undefined") +elseif ((DEFINED THREAD_SANITIZER_ON) AND (${THREAD_SANITIZER_ON})) + message(WARNING "********* Running with Thread Sanitizer *********") + add_flags("-fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") endif() find_package(GTest QUIET REQUIRED) diff --git a/conanfile.py b/conanfile.py index 923e29c56..419c4d17f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,7 +10,7 @@ class HomeObjectConan(ConanFile): name = "homeobject" - version = "5.0.0" + version = "5.0.1" homepage = "https://github.com/eBay/HomeObject" description = "Blob Store built on HomeStore" @@ -25,6 +25,7 @@ class HomeObjectConan(ConanFile): "fPIC": ['True', 'False'], "coverage": ['True', 'False'], "sanitize": ['address', 'thread', 'False'], + "prerelease": ['True', 'False'], } default_options = { @@ -32,6 +33,7 @@ class HomeObjectConan(ConanFile): 'fPIC': True, 'coverage': False, 'sanitize': False, + 'prerelease': False, } exports_sources = ("CMakeLists.txt", "cmake/*", "src/*", "LICENSE") @@ -84,15 +86,21 @@ def generate(self): tc.variables["CONAN_CMAKE_SILENT_OUTPUT"] = "ON" tc.variables['CMAKE_EXPORT_COMPILE_COMMANDS'] = 'ON' tc.variables["CTEST_OUTPUT_ON_FAILURE"] = "ON" - tc.variables["MEMORY_SANITIZER_ON"] = "OFF" + tc.variables["ADDRESS_SANITIZER_ON"] = "OFF" + tc.variables["THREAD_SANITIZER_ON"] = "OFF" tc.variables["CODE_COVERAGE"] = "OFF" tc.variables["CONAN_PACKAGE_NAME"] = self.name tc.variables["CONAN_PACKAGE_VERSION"] = self.version + if self.options.prerelease: + tc.preprocessor_definitions["_PRERELEASE"] = "1" if self.settings.build_type == "Debug": if self.options.get_safe("coverage"): tc.variables['CODE_COVERAGE'] = 'ON' elif self.options.get_safe("sanitize"): - tc.variables['MEMORY_SANITIZER_ON'] = 'ON' + if self.options.sanitize == "thread": + tc.variables['THREAD_SANITIZER_ON'] = 'ON' + else: + tc.variables['ADDRESS_SANITIZER_ON'] = 'ON' tc.generate() # This generates "boost-config.cmake" and "grpc-config.cmake" etc in self.generators_folder @@ -128,7 +136,10 @@ def package_info(self): if self.settings.os == "Linux": self.cpp_info.components["homestore"].system_libs.append("pthread") self.cpp_info.components["memory"].system_libs.append("pthread") - if self.options.sanitize: + if self.options.sanitize == "thread": + self.cpp_info.components["memory"].sharedlinkflags.append("-fsanitize=thread") + self.cpp_info.components["memory"].exelinkflags.append("-fsanitize=thread") + elif self.options.sanitize == "address": self.cpp_info.components["memory"].sharedlinkflags.append("-fsanitize=address") self.cpp_info.components["memory"].exelinkflags.append("-fsanitize=address") self.cpp_info.components["memory"].sharedlinkflags.append("-fsanitize=undefined")