mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 03:35:55 +00:00
Compare commits
6 Commits
nightly-20
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b280e7742 | ||
|
|
7ed30bc40d | ||
|
|
ac608004bc | ||
|
|
6ab92ca0a6 | ||
|
|
77387d8f9f | ||
|
|
b62cfe949f |
2
.github/scripts/conan/generate_matrix.py
vendored
2
.github/scripts/conan/generate_matrix.py
vendored
@@ -4,7 +4,7 @@ import json
|
|||||||
|
|
||||||
LINUX_OS = ["heavy", "heavy-arm64"]
|
LINUX_OS = ["heavy", "heavy-arm64"]
|
||||||
LINUX_CONTAINERS = [
|
LINUX_CONTAINERS = [
|
||||||
'{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
'{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
]
|
]
|
||||||
LINUX_COMPILERS = ["gcc", "clang"]
|
LINUX_COMPILERS = ["gcc", "clang"]
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
build_type: [Release, Debug]
|
build_type: [Release, Debug]
|
||||||
container:
|
container:
|
||||||
[
|
[
|
||||||
'{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }',
|
'{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }',
|
||||||
]
|
]
|
||||||
static: [true]
|
static: [true]
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/reusable-build.yml
|
uses: ./.github/workflows/reusable-build.yml
|
||||||
with:
|
with:
|
||||||
runs_on: heavy
|
runs_on: heavy
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
conan_profile: gcc
|
conan_profile: gcc
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
download_ccache: true
|
download_ccache: true
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/reusable-build.yml
|
uses: ./.github/workflows/reusable-build.yml
|
||||||
with:
|
with:
|
||||||
runs_on: heavy
|
runs_on: heavy
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
conan_profile: gcc
|
conan_profile: gcc
|
||||||
build_type: Release
|
build_type: Release
|
||||||
download_ccache: true
|
download_ccache: true
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
needs: build-and-test
|
needs: build-and-test
|
||||||
runs-on: heavy
|
runs-on: heavy
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|||||||
4
.github/workflows/check-libxrpl.yml
vendored
4
.github/workflows/check-libxrpl.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
|
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
|
||||||
runs-on: heavy
|
runs-on: heavy
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
needs: build
|
needs: build
|
||||||
runs-on: heavy
|
runs-on: heavy
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
|
|||||||
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
if: github.event_name != 'push' || contains(github.event.head_commit.message, 'clang-tidy auto fixes')
|
if: github.event_name != 'push' || contains(github.event.head_commit.message, 'clang-tidy auto fixes')
|
||||||
runs-on: heavy
|
runs-on: heavy
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
@@ -43,17 +43,17 @@ jobs:
|
|||||||
conan_profile: gcc
|
conan_profile: gcc
|
||||||
build_type: Release
|
build_type: Release
|
||||||
static: true
|
static: true
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
- os: heavy
|
- os: heavy
|
||||||
conan_profile: gcc
|
conan_profile: gcc
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
static: true
|
static: true
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
- os: heavy
|
- os: heavy
|
||||||
conan_profile: gcc.ubsan
|
conan_profile: gcc.ubsan
|
||||||
build_type: Release
|
build_type: Release
|
||||||
static: false
|
static: false
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
|
|
||||||
uses: ./.github/workflows/reusable-build-test.yml
|
uses: ./.github/workflows/reusable-build-test.yml
|
||||||
with:
|
with:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: heavy
|
- os: heavy
|
||||||
conan_profile: clang
|
conan_profile: clang
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
static: true
|
static: true
|
||||||
- os: macos15
|
- os: macos15
|
||||||
conan_profile: apple-clang
|
conan_profile: apple-clang
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
conan_profile: gcc
|
conan_profile: gcc
|
||||||
build_type: Release
|
build_type: Release
|
||||||
static: true
|
static: true
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
|
|
||||||
uses: ./.github/workflows/reusable-build-test.yml
|
uses: ./.github/workflows/reusable-build-test.yml
|
||||||
with:
|
with:
|
||||||
|
|||||||
14
.github/workflows/reusable-build.yml
vendored
14
.github/workflows/reusable-build.yml
vendored
@@ -116,9 +116,8 @@ jobs:
|
|||||||
code_coverage: ${{ inputs.code_coverage }}
|
code_coverage: ${{ inputs.code_coverage }}
|
||||||
|
|
||||||
- name: Restore ccache cache
|
- name: Restore ccache cache
|
||||||
if: ${{ inputs.download_ccache }}
|
if: ${{ inputs.download_ccache && github.ref != 'refs/heads/develop' }}
|
||||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
id: restore_cache
|
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ steps.cache_key.outputs.key }}
|
key: ${{ steps.cache_key.outputs.key }}
|
||||||
@@ -160,17 +159,14 @@ jobs:
|
|||||||
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build_time_report.txt
|
path: build_time_report.txt
|
||||||
|
|
||||||
- name: Show ccache's statistics
|
- name: Show ccache's statistics and zero it
|
||||||
if: ${{ inputs.download_ccache }}
|
if: ${{ inputs.download_ccache }}
|
||||||
id: ccache_stats
|
|
||||||
run: |
|
run: |
|
||||||
ccache -s > /tmp/ccache.stats
|
ccache --show-stats
|
||||||
miss_rate=$(cat /tmp/ccache.stats | grep 'Misses' | head -n1 | sed 's/.*(\(.*\)%).*/\1/')
|
ccache --zero-stats
|
||||||
echo "miss_rate=${miss_rate}" >> $GITHUB_OUTPUT
|
|
||||||
cat /tmp/ccache.stats
|
|
||||||
|
|
||||||
- name: Save ccache cache
|
- name: Save ccache cache
|
||||||
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' && (steps.restore_cache.outputs.cache-hit != 'true' || steps.ccache_stats.outputs.miss_rate == '100.0') }}
|
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' }}
|
||||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
|
|||||||
2
.github/workflows/reusable-release.yml
vendored
2
.github/workflows/reusable-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: heavy
|
runs-on: heavy
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
env:
|
env:
|
||||||
GH_REPO: ${{ github.repository }}
|
GH_REPO: ${{ github.repository }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
5
.github/workflows/sanitizers.yml
vendored
5
.github/workflows/sanitizers.yml
vendored
@@ -44,14 +44,13 @@ jobs:
|
|||||||
uses: ./.github/workflows/reusable-build-test.yml
|
uses: ./.github/workflows/reusable-build-test.yml
|
||||||
with:
|
with:
|
||||||
runs_on: heavy
|
runs_on: heavy
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||||
download_ccache: false
|
download_ccache: false
|
||||||
upload_ccache: false
|
upload_ccache: false
|
||||||
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||||
build_type: ${{ matrix.build_type }}
|
build_type: ${{ matrix.build_type }}
|
||||||
static: false
|
static: false
|
||||||
# Currently, both gcc.tsan and clang.tsan unit tests hang
|
run_unit_tests: true
|
||||||
run_unit_tests: ${{ matrix.sanitizer_ext != '.tsan' }}
|
|
||||||
run_integration_tests: false
|
run_integration_tests: false
|
||||||
upload_clio_server: false
|
upload_clio_server: false
|
||||||
targets: clio_tests clio_integration_tests
|
targets: clio_tests clio_integration_tests
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ if (san)
|
|||||||
endif ()
|
endif ()
|
||||||
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
||||||
|
|
||||||
if (san STREQUAL "address")
|
|
||||||
# ASAN needs these definitions as well as correct b2 flags in conan profile for sanitizers
|
|
||||||
target_compile_definitions(clio_options INTERFACE BOOST_USE_ASAN=1 BOOST_USE_UCONTEXT=1)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
{% set sanitizer_opt_map = {"asan": "address", "tsan": "thread", "ubsan": "undefined"} %}
|
{% set sanitizer_opt_map = {"asan": "address", "tsan": "thread", "ubsan": "undefined"} %}
|
||||||
{% set sanitizer = sanitizer_opt_map[sani] %}
|
{% set sanitizer = sanitizer_opt_map[sani] %}
|
||||||
|
|
||||||
{% set sanitizer_b2_flags_map = {"address": "define=BOOST_USE_ASAN=1 context-impl=ucontext address-sanitizer=on", "thread": "thread-sanitizer=on", "undefined": "undefined-sanitizer=on"} %}
|
{% set sanitizer_b2_flags_map = {
|
||||||
|
"address": "context-impl=ucontext address-sanitizer=norecover",
|
||||||
|
"thread": "context-impl=ucontext thread-sanitizer=norecover",
|
||||||
|
"undefined": "undefined-sanitizer=norecover"
|
||||||
|
} %}
|
||||||
{% set sanitizer_b2_flags_str = sanitizer_b2_flags_map[sanitizer] %}
|
{% set sanitizer_b2_flags_str = sanitizer_b2_flags_map[sanitizer] %}
|
||||||
|
|
||||||
{% set sanitizer_build_flags_str = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %}
|
{% set sanitizer_build_flags_str = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %}
|
||||||
@@ -24,4 +28,10 @@ tools.build:cxxflags+={{ sanitizer_build_flags }}
|
|||||||
tools.build:exelinkflags+={{ sanitizer_link_flags }}
|
tools.build:exelinkflags+={{ sanitizer_link_flags }}
|
||||||
tools.build:sharedlinkflags+={{ sanitizer_link_flags }}
|
tools.build:sharedlinkflags+={{ sanitizer_link_flags }}
|
||||||
|
|
||||||
tools.info.package_id:confs+=["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"]
|
{% if sanitizer == "address" %}
|
||||||
|
tools.build:defines+=["BOOST_USE_ASAN", "BOOST_USE_UCONTEXT"]
|
||||||
|
{% elif sanitizer == "thread" %}
|
||||||
|
tools.build:defines+=["BOOST_USE_TSAN", "BOOST_USE_UCONTEXT"]
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
tools.info.package_id:confs+=["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
clio_develop:
|
clio_develop:
|
||||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
volumes:
|
volumes:
|
||||||
- clio_develop_conan_data:/root/.conan2/p
|
- clio_develop_conan_data:/root/.conan2/p
|
||||||
- clio_develop_ccache:/root/.ccache
|
- clio_develop_ccache:/root/.ccache
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ Open the `index.html` file in your browser to see the documentation pages.
|
|||||||
It is also possible to build Clio using [Docker](https://www.docker.com/) if you don't want to install all the dependencies on your machine.
|
It is also possible to build Clio using [Docker](https://www.docker.com/) if you don't want to install all the dependencies on your machine.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run -it ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
docker run -it ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||||
git clone https://github.com/XRPLF/clio
|
git clone https://github.com/XRPLF/clio
|
||||||
cd clio
|
cd clio
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ This document provides a list of all available Clio configuration properties in
|
|||||||
- **Type**: double
|
- **Type**: double
|
||||||
- **Default value**: `10`
|
- **Default value**: `10`
|
||||||
- **Constraints**: The value must be a positive double number.
|
- **Constraints**: The value must be a positive double number.
|
||||||
- **Description**: The number of milliseconds the server waits to shutdown gracefully. If Clio does not shutdown gracefully after the specified value, it will be killed instead.
|
- **Description**: The number of seconds the server waits to shutdown gracefully. If Clio does not shutdown gracefully after the specified value, it will be killed instead.
|
||||||
|
|
||||||
### cache.num_diffs
|
### cache.num_diffs
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "data/AmendmentCenter.hpp"
|
#include "data/AmendmentCenter.hpp"
|
||||||
#include "data/BackendFactory.hpp"
|
#include "data/BackendFactory.hpp"
|
||||||
#include "data/LedgerCache.hpp"
|
#include "data/LedgerCache.hpp"
|
||||||
|
#include "data/LedgerCacheSaver.hpp"
|
||||||
#include "etl/ETLService.hpp"
|
#include "etl/ETLService.hpp"
|
||||||
#include "etl/LoadBalancer.hpp"
|
#include "etl/LoadBalancer.hpp"
|
||||||
#include "etl/NetworkValidatedLedgers.hpp"
|
#include "etl/NetworkValidatedLedgers.hpp"
|
||||||
@@ -98,14 +99,15 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
auto const threads = config_.get<uint16_t>("io_threads");
|
auto const threads = config_.get<uint16_t>("io_threads");
|
||||||
LOG(util::LogService::info()) << "Number of io threads = " << threads;
|
LOG(util::LogService::info()) << "Number of io threads = " << threads;
|
||||||
|
|
||||||
|
// Similarly we need a context to run ETL on
|
||||||
|
// In the future we can remove the raw ioc and use ctx instead
|
||||||
|
// This context should be above ioc because its reference is getting into tasks inside ioc
|
||||||
|
util::async::CoroExecutionContext ctx{threads};
|
||||||
|
|
||||||
// IO context to handle all incoming requests, as well as other things.
|
// IO context to handle all incoming requests, as well as other things.
|
||||||
// This is not the only io context in the application.
|
// This is not the only io context in the application.
|
||||||
boost::asio::io_context ioc{threads};
|
boost::asio::io_context ioc{threads};
|
||||||
|
|
||||||
// Similarly we need a context to run ETL on
|
|
||||||
// In the future we can remove the raw ioc and use ctx instead
|
|
||||||
util::async::CoroExecutionContext ctx{threads};
|
|
||||||
|
|
||||||
// Rate limiter, to prevent abuse
|
// Rate limiter, to prevent abuse
|
||||||
auto whitelistHandler = web::dosguard::WhitelistHandler{config_};
|
auto whitelistHandler = web::dosguard::WhitelistHandler{config_};
|
||||||
auto const dosguardWeights = web::dosguard::Weights::make(config_);
|
auto const dosguardWeights = web::dosguard::Weights::make(config_);
|
||||||
@@ -113,21 +115,7 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
auto sweepHandler = web::dosguard::IntervalSweepHandler{config_, ioc, dosGuard};
|
auto sweepHandler = web::dosguard::IntervalSweepHandler{config_, ioc, dosGuard};
|
||||||
|
|
||||||
auto cache = data::LedgerCache{};
|
auto cache = data::LedgerCache{};
|
||||||
appStopper_.setOnStop([&cache, this](auto&&) {
|
auto cacheSaver = data::LedgerCacheSaver{config_, cache};
|
||||||
// TODO(kuznetsss): move this into Stopper::makeOnStopCallback()
|
|
||||||
auto const cacheFilePath = config_.maybeValue<std::string>("cache.file.path");
|
|
||||||
if (not cacheFilePath.has_value()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(util::LogService::info()) << "Saving ledger cache to " << *cacheFilePath;
|
|
||||||
if (auto const [success, duration_ms] = util::timed([&]() { return cache.saveToFile(*cacheFilePath); });
|
|
||||||
success.has_value()) {
|
|
||||||
LOG(util::LogService::info()) << "Successfully saved ledger cache in " << duration_ms << " ms";
|
|
||||||
} else {
|
|
||||||
LOG(util::LogService::error()) << "Error saving LedgerCache to file";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Interface to the database
|
// Interface to the database
|
||||||
auto backend = data::makeBackend(config_, cache);
|
auto backend = data::makeBackend(config_, cache);
|
||||||
@@ -208,7 +196,7 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
appStopper_.setOnStop(
|
appStopper_.setOnStop(
|
||||||
Stopper::makeOnStopCallback(httpServer.value(), *balancer, *etl, *subscriptions, *backend, ioc)
|
Stopper::makeOnStopCallback(httpServer.value(), *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Blocks until stopped.
|
// Blocks until stopped.
|
||||||
@@ -223,6 +211,9 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
auto handler = std::make_shared<web::RPCServerHandler<RPCEngineType>>(config_, backend, rpcEngine, etl, dosGuard);
|
auto handler = std::make_shared<web::RPCServerHandler<RPCEngineType>>(config_, backend, rpcEngine, etl, dosGuard);
|
||||||
|
|
||||||
auto const httpServer = web::makeHttpServer(config_, ioc, dosGuard, handler, cache);
|
auto const httpServer = web::makeHttpServer(config_, ioc, dosGuard, handler, cache);
|
||||||
|
appStopper_.setOnStop(
|
||||||
|
Stopper::makeOnStopCallback(*httpServer, *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
||||||
|
);
|
||||||
|
|
||||||
// Blocks until stopped.
|
// Blocks until stopped.
|
||||||
// When stopped, shared_ptrs fall out of scope
|
// When stopped, shared_ptrs fall out of scope
|
||||||
|
|||||||
@@ -20,12 +20,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "data/BackendInterface.hpp"
|
#include "data/BackendInterface.hpp"
|
||||||
|
#include "data/LedgerCacheSaver.hpp"
|
||||||
#include "etl/ETLServiceInterface.hpp"
|
#include "etl/ETLServiceInterface.hpp"
|
||||||
#include "etl/LoadBalancerInterface.hpp"
|
#include "etl/LoadBalancerInterface.hpp"
|
||||||
#include "feed/SubscriptionManagerInterface.hpp"
|
#include "feed/SubscriptionManagerInterface.hpp"
|
||||||
#include "util/CoroutineGroup.hpp"
|
#include "util/CoroutineGroup.hpp"
|
||||||
#include "util/log/Logger.hpp"
|
#include "util/log/Logger.hpp"
|
||||||
#include "web/ng/Server.hpp"
|
#include "web/interface/Concepts.hpp"
|
||||||
|
|
||||||
#include <boost/asio/executor_work_guard.hpp>
|
#include <boost/asio/executor_work_guard.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
@@ -71,10 +72,11 @@ public:
|
|||||||
* @param etl The ETL service to stop.
|
* @param etl The ETL service to stop.
|
||||||
* @param subscriptions The subscription manager to stop.
|
* @param subscriptions The subscription manager to stop.
|
||||||
* @param backend The backend to stop.
|
* @param backend The backend to stop.
|
||||||
|
* @param cacheSaver The ledger cache saver
|
||||||
* @param ioc The io_context to stop.
|
* @param ioc The io_context to stop.
|
||||||
* @return The callback to be called on application stop.
|
* @return The callback to be called on application stop.
|
||||||
*/
|
*/
|
||||||
template <web::ng::SomeServer ServerType>
|
template <web::SomeServer ServerType, data::SomeLedgerCacheSaver LedgerCacheSaverType>
|
||||||
static std::function<void(boost::asio::yield_context)>
|
static std::function<void(boost::asio::yield_context)>
|
||||||
makeOnStopCallback(
|
makeOnStopCallback(
|
||||||
ServerType& server,
|
ServerType& server,
|
||||||
@@ -82,10 +84,13 @@ public:
|
|||||||
etl::ETLServiceInterface& etl,
|
etl::ETLServiceInterface& etl,
|
||||||
feed::SubscriptionManagerInterface& subscriptions,
|
feed::SubscriptionManagerInterface& subscriptions,
|
||||||
data::BackendInterface& backend,
|
data::BackendInterface& backend,
|
||||||
|
LedgerCacheSaverType& cacheSaver,
|
||||||
boost::asio::io_context& ioc
|
boost::asio::io_context& ioc
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return [&](boost::asio::yield_context yield) {
|
return [&](boost::asio::yield_context yield) {
|
||||||
|
cacheSaver.save();
|
||||||
|
|
||||||
util::CoroutineGroup coroutineGroup{yield};
|
util::CoroutineGroup coroutineGroup{yield};
|
||||||
coroutineGroup.spawn(yield, [&server](auto innerYield) {
|
coroutineGroup.spawn(yield, [&server](auto innerYield) {
|
||||||
server.stop(innerYield);
|
server.stop(innerYield);
|
||||||
@@ -106,6 +111,8 @@ public:
|
|||||||
backend.waitForWritesToFinish();
|
backend.waitForWritesToFinish();
|
||||||
LOG(util::LogService::info()) << "Backend writes finished";
|
LOG(util::LogService::info()) << "Backend writes finished";
|
||||||
|
|
||||||
|
cacheSaver.waitToFinish();
|
||||||
|
|
||||||
ioc.stop();
|
ioc.stop();
|
||||||
LOG(util::LogService::info()) << "io_context stopped";
|
LOG(util::LogService::info()) << "io_context stopped";
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ target_sources(
|
|||||||
BackendCounters.cpp
|
BackendCounters.cpp
|
||||||
BackendInterface.cpp
|
BackendInterface.cpp
|
||||||
LedgerCache.cpp
|
LedgerCache.cpp
|
||||||
|
LedgerCacheSaver.cpp
|
||||||
LedgerHeaderCache.cpp
|
LedgerHeaderCache.cpp
|
||||||
cassandra/impl/Future.cpp
|
cassandra/impl/Future.cpp
|
||||||
cassandra/impl/Cluster.cpp
|
cassandra/impl/Cluster.cpp
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ LedgerCache::saveToFile(std::string const& path) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl::LedgerCacheFile file{path};
|
impl::LedgerCacheFile file{path};
|
||||||
std::unique_lock const lock{mtx_};
|
std::shared_lock const lock{mtx_};
|
||||||
impl::LedgerCacheFile::DataView const data{.latestSeq = latestSeq_, .map = map_, .deleted = deleted_};
|
impl::LedgerCacheFile::DataView const data{.latestSeq = latestSeq_, .map = map_, .deleted = deleted_};
|
||||||
return file.write(data);
|
return file.write(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,15 +145,8 @@ public:
|
|||||||
void
|
void
|
||||||
waitUntilCacheContainsSeq(uint32_t seq) override;
|
waitUntilCacheContainsSeq(uint32_t seq) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Save the cache to file
|
|
||||||
* @note This operation takes about 7 seconds and it keeps mtx_ exclusively locked
|
|
||||||
*
|
|
||||||
* @param path The file path to save the cache to
|
|
||||||
* @return An error as a string if any
|
|
||||||
*/
|
|
||||||
std::expected<void, std::string>
|
std::expected<void, std::string>
|
||||||
saveToFile(std::string const& path) const;
|
saveToFile(std::string const& path) const override;
|
||||||
|
|
||||||
std::expected<void, std::string>
|
std::expected<void, std::string>
|
||||||
loadFromFile(std::string const& path, uint32_t minLatestSequence) override;
|
loadFromFile(std::string const& path, uint32_t minLatestSequence) override;
|
||||||
|
|||||||
@@ -171,6 +171,16 @@ public:
|
|||||||
virtual void
|
virtual void
|
||||||
waitUntilCacheContainsSeq(uint32_t seq) = 0;
|
waitUntilCacheContainsSeq(uint32_t seq) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the cache to file
|
||||||
|
* @note This operation takes about 7 seconds and it keeps a shared lock of mtx_
|
||||||
|
*
|
||||||
|
* @param path The file path to save the cache to
|
||||||
|
* @return An error as a string if any
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, std::string>
|
||||||
|
saveToFile(std::string const& path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Load the cache from file
|
* @brief Load the cache from file
|
||||||
* @note This operation takes about 7 seconds and it keeps mtx_ exclusively locked
|
* @note This operation takes about 7 seconds and it keeps mtx_ exclusively locked
|
||||||
|
|||||||
70
src/data/LedgerCacheSaver.cpp
Normal file
70
src/data/LedgerCacheSaver.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2025, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "data/LedgerCacheSaver.hpp"
|
||||||
|
|
||||||
|
#include "data/LedgerCacheInterface.hpp"
|
||||||
|
#include "util/Assert.hpp"
|
||||||
|
#include "util/Profiler.hpp"
|
||||||
|
#include "util/log/Logger.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
LedgerCacheSaver::LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache)
|
||||||
|
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path")), cache_(cache)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LedgerCacheSaver::~LedgerCacheSaver()
|
||||||
|
{
|
||||||
|
waitToFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LedgerCacheSaver::save()
|
||||||
|
{
|
||||||
|
ASSERT(not savingThread_.has_value(), "Multiple save() calls are not allowed");
|
||||||
|
savingThread_ = std::thread([this]() {
|
||||||
|
if (not cacheFilePath_.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(util::LogService::info()) << "Saving ledger cache to " << *cacheFilePath_;
|
||||||
|
if (auto const [success, durationMs] = util::timed([&]() { return cache_.get().saveToFile(*cacheFilePath_); });
|
||||||
|
success.has_value()) {
|
||||||
|
LOG(util::LogService::info()) << "Successfully saved ledger cache in " << durationMs << " ms";
|
||||||
|
} else {
|
||||||
|
LOG(util::LogService::error()) << "Error saving LedgerCache to file";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LedgerCacheSaver::waitToFinish()
|
||||||
|
{
|
||||||
|
if (savingThread_.has_value() and savingThread_->joinable()) {
|
||||||
|
savingThread_->join();
|
||||||
|
}
|
||||||
|
savingThread_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
93
src/data/LedgerCacheSaver.hpp
Normal file
93
src/data/LedgerCacheSaver.hpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2025, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/LedgerCacheInterface.hpp"
|
||||||
|
#include "util/config/ConfigDefinition.hpp"
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A concept for a class that can save ledger cache asynchronously.
|
||||||
|
*
|
||||||
|
* This concept defines the interface requirements for any type that manages
|
||||||
|
* asynchronous saving of ledger cache to persistent storage.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
concept SomeLedgerCacheSaver = requires(T a) {
|
||||||
|
{ a.save() } -> std::same_as<void>;
|
||||||
|
{ a.waitToFinish() } -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Manages asynchronous saving of ledger cache to a file.
|
||||||
|
*
|
||||||
|
* This class provides functionality to save the ledger cache to a file in a separate thread,
|
||||||
|
* allowing the main application to continue without blocking. The file path is configured
|
||||||
|
* through the application's configuration system.
|
||||||
|
*/
|
||||||
|
class LedgerCacheSaver {
|
||||||
|
std::optional<std::string> cacheFilePath_;
|
||||||
|
std::reference_wrapper<LedgerCacheInterface const> cache_;
|
||||||
|
std::optional<std::thread> savingThread_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a LedgerCacheSaver instance.
|
||||||
|
*
|
||||||
|
* @param config The configuration object containing the cache file path setting
|
||||||
|
* @param cache Reference to the ledger cache interface to be saved
|
||||||
|
*/
|
||||||
|
LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor that ensures the saving thread is properly joined.
|
||||||
|
*
|
||||||
|
* Waits for any ongoing save operation to complete before destruction.
|
||||||
|
*/
|
||||||
|
~LedgerCacheSaver();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initiates an asynchronous save operation of the ledger cache.
|
||||||
|
*
|
||||||
|
* Spawns a new thread that saves the ledger cache to the configured file path.
|
||||||
|
* If no file path is configured, the operation is skipped. Logs the progress
|
||||||
|
* and result of the save operation.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Waits for the saving thread to complete.
|
||||||
|
*
|
||||||
|
* Blocks until the saving operation finishes if a thread is currently active.
|
||||||
|
* Safe to call multiple times or when no save operation is in progress.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
waitToFinish();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "util/StopHelper.hpp"
|
#include "util/StopHelper.hpp"
|
||||||
|
|
||||||
|
#include "util/Spawn.hpp"
|
||||||
|
|
||||||
#include <boost/asio/spawn.hpp>
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ void
|
|||||||
StopHelper::asyncWaitForStop(boost::asio::yield_context yield)
|
StopHelper::asyncWaitForStop(boost::asio::yield_context yield)
|
||||||
{
|
{
|
||||||
boost::asio::steady_timer timer{yield.get_executor(), std::chrono::steady_clock::duration::max()};
|
boost::asio::steady_timer timer{yield.get_executor(), std::chrono::steady_clock::duration::max()};
|
||||||
onStopReady_.connect([&timer]() { timer.cancel(); });
|
onStopReady_.connect([&]() { util::spawn(yield, [&timer](auto&&) { timer.cancel(); }); });
|
||||||
boost::system::error_code error;
|
boost::system::error_code error;
|
||||||
if (!*stopped_)
|
if (!*stopped_)
|
||||||
timer.async_wait(yield[error]);
|
timer.async_wait(yield[error]);
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ class StopHelper {
|
|||||||
std::unique_ptr<std::atomic_bool> stopped_ = std::make_unique<std::atomic_bool>(false);
|
std::unique_ptr<std::atomic_bool> stopped_ = std::make_unique<std::atomic_bool>(false);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
StopHelper() = default;
|
||||||
|
~StopHelper() = default;
|
||||||
|
|
||||||
|
StopHelper(StopHelper&&) = delete;
|
||||||
|
StopHelper&
|
||||||
|
operator=(StopHelper&&) = delete;
|
||||||
|
StopHelper(StopHelper const&) = delete;
|
||||||
|
StopHelper&
|
||||||
|
operator=(StopHelper const&) = delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Notify that the class is ready to stop.
|
* @brief Notify that the class is ready to stop.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ This document provides a list of all available Clio configuration properties in
|
|||||||
.value = "The number of worker threads or processes that are responsible for managing and processing "
|
.value = "The number of worker threads or processes that are responsible for managing and processing "
|
||||||
"subscription-based tasks from `rippled`."},
|
"subscription-based tasks from `rippled`."},
|
||||||
KV{.key = "graceful_period",
|
KV{.key = "graceful_period",
|
||||||
.value = "The number of milliseconds the server waits to shutdown gracefully. If Clio does not shutdown "
|
.value = "The number of seconds the server waits to shutdown gracefully. If Clio does not shutdown "
|
||||||
"gracefully after the specified value, it will be killed instead."},
|
"gracefully after the specified value, it will be killed instead."},
|
||||||
KV{.key = "cache.num_diffs",
|
KV{.key = "cache.num_diffs",
|
||||||
.value = "The number of cursors generated is the number of changed (without counting deleted) objects in "
|
.value = "The number of cursors generated is the number of changed (without counting deleted) objects in "
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <boost/asio/ip/address.hpp>
|
#include <boost/asio/ip/address.hpp>
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
#include <boost/asio/socket_base.hpp>
|
#include <boost/asio/socket_base.hpp>
|
||||||
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/asio/ssl/context.hpp>
|
#include <boost/asio/ssl/context.hpp>
|
||||||
#include <boost/asio/ssl/error.hpp>
|
#include <boost/asio/ssl/error.hpp>
|
||||||
#include <boost/asio/strand.hpp>
|
#include <boost/asio/strand.hpp>
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
#include <boost/beast/core/tcp_stream.hpp>
|
#include <boost/beast/core/tcp_stream.hpp>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -221,7 +223,8 @@ template <
|
|||||||
template <typename> class PlainSessionType,
|
template <typename> class PlainSessionType,
|
||||||
template <typename> class SslSessionType,
|
template <typename> class SslSessionType,
|
||||||
SomeServerHandler HandlerType>
|
SomeServerHandler HandlerType>
|
||||||
class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
|
class Server : public ServerTag,
|
||||||
|
public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
|
||||||
using std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
|
using std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
|
||||||
|
|
||||||
util::Logger log_{"WebServer"};
|
util::Logger log_{"WebServer"};
|
||||||
@@ -235,6 +238,7 @@ class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslS
|
|||||||
std::shared_ptr<AdminVerificationStrategy> adminVerification_;
|
std::shared_ptr<AdminVerificationStrategy> adminVerification_;
|
||||||
std::uint32_t maxWsSendingQueueSize_;
|
std::uint32_t maxWsSendingQueueSize_;
|
||||||
std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
|
std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
|
||||||
|
std::atomic_bool isStopped_{false};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -308,6 +312,13 @@ public:
|
|||||||
doAccept();
|
doAccept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Stop accepting new connections */
|
||||||
|
void
|
||||||
|
stop(boost::asio::yield_context)
|
||||||
|
{
|
||||||
|
isStopped_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
void
|
||||||
doAccept()
|
doAccept()
|
||||||
@@ -321,6 +332,10 @@ private:
|
|||||||
void
|
void
|
||||||
onAccept(boost::beast::error_code ec, tcp::socket socket)
|
onAccept(boost::beast::error_code ec, tcp::socket socket)
|
||||||
{
|
{
|
||||||
|
if (isStopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ec) {
|
if (!ec) {
|
||||||
auto ctxRef =
|
auto ctxRef =
|
||||||
ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
|
ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <boost/beast.hpp>
|
#include <boost/beast.hpp>
|
||||||
#include <boost/beast/core/error.hpp>
|
#include <boost/beast/core/error.hpp>
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -39,4 +40,14 @@ concept SomeServerHandler =
|
|||||||
{ handler(req, ws) };
|
{ handler(req, ws) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A tag class for server to help identify Server in templated code.
|
||||||
|
*/
|
||||||
|
struct ServerTag {
|
||||||
|
virtual ~ServerTag() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept SomeServer = std::derived_from<T, ServerTag>;
|
||||||
|
|
||||||
} // namespace web
|
} // namespace web
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "util/config/ConfigDefinition.hpp"
|
#include "util/config/ConfigDefinition.hpp"
|
||||||
#include "util/log/Logger.hpp"
|
#include "util/log/Logger.hpp"
|
||||||
#include "web/ProxyIpResolver.hpp"
|
#include "web/ProxyIpResolver.hpp"
|
||||||
|
#include "web/interface/Concepts.hpp"
|
||||||
#include "web/ng/Connection.hpp"
|
#include "web/ng/Connection.hpp"
|
||||||
#include "web/ng/MessageHandler.hpp"
|
#include "web/ng/MessageHandler.hpp"
|
||||||
#include "web/ng/ProcessingPolicy.hpp"
|
#include "web/ng/ProcessingPolicy.hpp"
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
#include <boost/asio/spawn.hpp>
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/asio/ssl/context.hpp>
|
#include <boost/asio/ssl/context.hpp>
|
||||||
|
|
||||||
#include <concepts>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -42,16 +42,6 @@
|
|||||||
|
|
||||||
namespace web::ng {
|
namespace web::ng {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A tag class for server to help identify Server in templated code.
|
|
||||||
*/
|
|
||||||
struct ServerTag {
|
|
||||||
virtual ~ServerTag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
concept SomeServer = std::derived_from<T, ServerTag>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Web server class.
|
* @brief Web server class.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ struct MockLedgerCache : data::LedgerCacheInterface {
|
|||||||
|
|
||||||
MOCK_METHOD(void, waitUntilCacheContainsSeq, (uint32_t), (override));
|
MOCK_METHOD(void, waitUntilCacheContainsSeq, (uint32_t), (override));
|
||||||
|
|
||||||
|
using SaveToFileReturnType = std::expected<void, std::string>;
|
||||||
|
MOCK_METHOD(SaveToFileReturnType, saveToFile, (std::string const& path), (const, override));
|
||||||
|
|
||||||
using LoadFromFileReturnType = std::expected<void, std::string>;
|
using LoadFromFileReturnType = std::expected<void, std::string>;
|
||||||
MOCK_METHOD(
|
MOCK_METHOD(
|
||||||
LoadFromFileReturnType,
|
LoadFromFileReturnType,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ target_sources(
|
|||||||
data/BackendCountersTests.cpp
|
data/BackendCountersTests.cpp
|
||||||
data/BackendInterfaceTests.cpp
|
data/BackendInterfaceTests.cpp
|
||||||
data/LedgerCacheTests.cpp
|
data/LedgerCacheTests.cpp
|
||||||
|
data/LedgerCacheSaverTests.cpp
|
||||||
data/cassandra/AsyncExecutorTests.cpp
|
data/cassandra/AsyncExecutorTests.cpp
|
||||||
data/cassandra/ExecutionStrategyTests.cpp
|
data/cassandra/ExecutionStrategyTests.cpp
|
||||||
data/cassandra/LedgerHeaderCacheTests.cpp
|
data/cassandra/LedgerHeaderCacheTests.cpp
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#include "util/MockPrometheus.hpp"
|
#include "util/MockPrometheus.hpp"
|
||||||
#include "util/MockSubscriptionManager.hpp"
|
#include "util/MockSubscriptionManager.hpp"
|
||||||
#include "util/config/ConfigDefinition.hpp"
|
#include "util/config/ConfigDefinition.hpp"
|
||||||
#include "web/ng/Server.hpp"
|
#include "web/interface/Concepts.hpp"
|
||||||
|
|
||||||
#include <boost/asio/executor_work_guard.hpp>
|
#include <boost/asio/executor_work_guard.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
@@ -61,16 +61,22 @@ TEST_F(StopperTest, stopCalledMultipleTimes)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct StopperMakeCallbackTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
|
struct StopperMakeCallbackTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
|
||||||
struct ServerMock : web::ng::ServerTag {
|
struct ServerMock : web::ServerTag {
|
||||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MockLedgerCacheSaver {
|
||||||
|
MOCK_METHOD(void, save, ());
|
||||||
|
MOCK_METHOD(void, waitToFinish, ());
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
testing::StrictMock<ServerMock> serverMock_;
|
testing::StrictMock<ServerMock> serverMock_;
|
||||||
testing::StrictMock<MockLoadBalancer> loadBalancerMock_;
|
testing::StrictMock<MockLoadBalancer> loadBalancerMock_;
|
||||||
testing::StrictMock<MockETLService> etlServiceMock_;
|
testing::StrictMock<MockETLService> etlServiceMock_;
|
||||||
testing::StrictMock<MockSubscriptionManager> subscriptionManagerMock_;
|
testing::StrictMock<MockSubscriptionManager> subscriptionManagerMock_;
|
||||||
testing::StrictMock<MockBackend> backendMock_{util::config::ClioConfigDefinition{}};
|
testing::StrictMock<MockBackend> backendMock_{util::config::ClioConfigDefinition{}};
|
||||||
|
testing::StrictMock<MockLedgerCacheSaver> cacheSaverMock_;
|
||||||
boost::asio::io_context ioContextToStop_;
|
boost::asio::io_context ioContextToStop_;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -86,10 +92,17 @@ TEST_F(StopperMakeCallbackTest, makeCallbackTest)
|
|||||||
std::thread t{[this]() { ioContextToStop_.run(); }};
|
std::thread t{[this]() { ioContextToStop_.run(); }};
|
||||||
|
|
||||||
auto callback = Stopper::makeOnStopCallback(
|
auto callback = Stopper::makeOnStopCallback(
|
||||||
serverMock_, loadBalancerMock_, etlServiceMock_, subscriptionManagerMock_, backendMock_, ioContextToStop_
|
serverMock_,
|
||||||
|
loadBalancerMock_,
|
||||||
|
etlServiceMock_,
|
||||||
|
subscriptionManagerMock_,
|
||||||
|
backendMock_,
|
||||||
|
cacheSaverMock_,
|
||||||
|
ioContextToStop_
|
||||||
);
|
);
|
||||||
|
|
||||||
testing::Sequence const s1, s2;
|
testing::Sequence const s1, s2;
|
||||||
|
EXPECT_CALL(cacheSaverMock_, save).InSequence(s1).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||||
EXPECT_CALL(serverMock_, stop).InSequence(s1).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
EXPECT_CALL(serverMock_, stop).InSequence(s1).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||||
EXPECT_CALL(loadBalancerMock_, stop).InSequence(s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
EXPECT_CALL(loadBalancerMock_, stop).InSequence(s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||||
EXPECT_CALL(etlServiceMock_, stop).InSequence(s1, s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
EXPECT_CALL(etlServiceMock_, stop).InSequence(s1, s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||||
@@ -99,6 +112,7 @@ TEST_F(StopperMakeCallbackTest, makeCallbackTest)
|
|||||||
EXPECT_CALL(backendMock_, waitForWritesToFinish).InSequence(s1, s2).WillOnce([this]() {
|
EXPECT_CALL(backendMock_, waitForWritesToFinish).InSequence(s1, s2).WillOnce([this]() {
|
||||||
EXPECT_FALSE(isContextStopped());
|
EXPECT_FALSE(isContextStopped());
|
||||||
});
|
});
|
||||||
|
EXPECT_CALL(cacheSaverMock_, waitToFinish).InSequence(s1).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||||
|
|
||||||
runSpawn([&](boost::asio::yield_context yield) {
|
runSpawn([&](boost::asio::yield_context yield) {
|
||||||
callback(yield);
|
callback(yield);
|
||||||
|
|||||||
156
tests/unit/data/LedgerCacheSaverTests.cpp
Normal file
156
tests/unit/data/LedgerCacheSaverTests.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2025, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "data/LedgerCacheSaver.hpp"
|
||||||
|
#include "util/MockAssert.hpp"
|
||||||
|
#include "util/MockLedgerCache.hpp"
|
||||||
|
#include "util/config/ConfigDefinition.hpp"
|
||||||
|
#include "util/config/ConfigFileJson.hpp"
|
||||||
|
#include "util/config/ConfigValue.hpp"
|
||||||
|
#include "util/config/Types.hpp"
|
||||||
|
|
||||||
|
#include <boost/json/object.hpp>
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <expected>
|
||||||
|
#include <semaphore>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace data;
|
||||||
|
using namespace util::config;
|
||||||
|
|
||||||
|
struct LedgerCacheSaverTest : virtual testing::Test {
|
||||||
|
testing::StrictMock<MockLedgerCache> cache;
|
||||||
|
constexpr static auto kFILE_PATH = "./cache.bin";
|
||||||
|
|
||||||
|
static ClioConfigDefinition
|
||||||
|
generateConfig(bool cacheFilePathHasValue)
|
||||||
|
{
|
||||||
|
auto config = ClioConfigDefinition{{
|
||||||
|
{"cache.file.path", ConfigValue{ConfigType::String}.optional()},
|
||||||
|
}};
|
||||||
|
|
||||||
|
ConfigFileJson jsonFile{boost::json::object{}};
|
||||||
|
if (cacheFilePathHasValue) {
|
||||||
|
auto const jsonObject =
|
||||||
|
boost::json::parse(fmt::format(R"JSON({{"cache": {{"file": {{"path": "{}"}}}}}})JSON", kFILE_PATH))
|
||||||
|
.as_object();
|
||||||
|
jsonFile = ConfigFileJson{jsonObject};
|
||||||
|
}
|
||||||
|
auto const errors = config.parse(jsonFile);
|
||||||
|
EXPECT_FALSE(errors.has_value());
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, SaveSuccessfully)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
|
||||||
|
EXPECT_CALL(cache, saveToFile(kFILE_PATH)).WillOnce(testing::Return(std::expected<void, std::string>{}));
|
||||||
|
|
||||||
|
saver.save();
|
||||||
|
saver.waitToFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, SaveWithError)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
|
||||||
|
EXPECT_CALL(cache, saveToFile(kFILE_PATH))
|
||||||
|
.WillOnce(testing::Return(std::expected<void, std::string>(std::unexpected("Failed to save"))));
|
||||||
|
|
||||||
|
saver.save();
|
||||||
|
saver.waitToFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, NoSaveWhenPathNotConfigured)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(false);
|
||||||
|
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
saver.save();
|
||||||
|
saver.waitToFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, DestructorWaitsForCompletion)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
|
||||||
|
std::binary_semaphore semaphore{1};
|
||||||
|
std::atomic_bool saveCompleted{false};
|
||||||
|
|
||||||
|
EXPECT_CALL(cache, saveToFile(kFILE_PATH)).WillOnce([&]() {
|
||||||
|
semaphore.release();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
saveCompleted = true;
|
||||||
|
return std::expected<void, std::string>{};
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
saver.save();
|
||||||
|
EXPECT_TRUE(semaphore.try_acquire_for(std::chrono::seconds{5}));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(saveCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, WaitToFinishCanBeCalledMultipleTimes)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
|
||||||
|
EXPECT_CALL(cache, saveToFile(kFILE_PATH));
|
||||||
|
|
||||||
|
saver.save();
|
||||||
|
saver.waitToFinish();
|
||||||
|
EXPECT_NO_THROW(saver.waitToFinish());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverTest, WaitToFinishWithoutSaveIsSafe)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
EXPECT_NO_THROW(saver.waitToFinish());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LedgerCacheSaverAssertTest : LedgerCacheSaverTest, common::util::WithMockAssert {};
|
||||||
|
|
||||||
|
TEST_F(LedgerCacheSaverAssertTest, MultipleSavesNotAllowed)
|
||||||
|
{
|
||||||
|
auto const config = generateConfig(true);
|
||||||
|
|
||||||
|
LedgerCacheSaver saver{config, cache};
|
||||||
|
|
||||||
|
EXPECT_CALL(cache, saveToFile(kFILE_PATH));
|
||||||
|
saver.save();
|
||||||
|
EXPECT_CLIO_ASSERT_FAIL({ saver.save(); });
|
||||||
|
|
||||||
|
saver.waitToFinish();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user