mirror of
https://github.com/XRPLF/clio.git
synced 2026-01-27 10:05:31 +00:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
437168aa13 | ||
|
|
8a7c6b0aa4 | ||
|
|
93a344c3fa | ||
|
|
59d07fab64 | ||
|
|
3bb3e0b9f9 | ||
|
|
a72e5a180f | ||
|
|
0ebbaaadef | ||
|
|
b983aea15d | ||
|
|
63e7f9a72b | ||
|
|
eebee4d671 | ||
|
|
a6d5f94470 | ||
|
|
2b473c8613 | ||
|
|
6f6d8cdf25 | ||
|
|
232838862a | ||
|
|
cd93b2a469 | ||
|
|
95712c22b1 | ||
|
|
14342e087c | ||
|
|
159024898e | ||
|
|
c6be761f33 | ||
|
|
f33f15c02d | ||
|
|
e733fadb45 | ||
|
|
a7ac7b54a8 | ||
|
|
88866ea6fd | ||
|
|
bb39bce40b | ||
|
|
bb3159bda0 | ||
|
|
c0c5c14791 | ||
|
|
b0abe14057 | ||
|
|
c9df784c4e | ||
|
|
a9787b131e | ||
|
|
9f76eabf0a | ||
|
|
79c08fc735 | ||
|
|
2c9c5634ad | ||
|
|
850333528c | ||
|
|
8da4194fe2 | ||
|
|
4dece23ede | ||
|
|
2327e81b0b | ||
|
|
5269ea0223 | ||
|
|
89fbcbf66a | ||
|
|
4b731a92ae | ||
|
|
7600e740a0 | ||
|
|
db9a460867 | ||
|
|
d5b0329e70 | ||
|
|
612434677a | ||
|
|
5a5a79fe30 | ||
|
|
b1a49fdaab | ||
|
|
f451996944 | ||
|
|
488bb05d22 | ||
|
|
f2c4275f61 | ||
|
|
e9b98cf5b3 | ||
|
|
3aa1854129 | ||
|
|
f2f5a6ab19 | ||
|
|
1469d4b198 | ||
|
|
06ea05891d | ||
|
|
c7c270cc03 | ||
|
|
c1f2f5b100 | ||
|
|
bea0b51c8b | ||
|
|
69b8e5bd06 | ||
|
|
33dc4ad95a | ||
|
|
13cbb405c7 | ||
|
|
8a37a2e083 | ||
|
|
f8b6c98219 | ||
|
|
92883bf012 | ||
|
|
88881e95dd | ||
|
|
94e70e4026 | ||
|
|
b534570cdd | ||
|
|
56fbfc63c2 | ||
|
|
80978657c0 | ||
|
|
067449c3f8 | ||
|
|
946976546a | ||
|
|
73e90b0a3f | ||
|
|
7681c58a3a | ||
|
|
391e7b07ab | ||
|
|
4eadaa85fa | ||
|
|
1b1a46c429 | ||
|
|
89707d9668 | ||
|
|
ae260d1229 | ||
|
|
058c05cfb6 | ||
|
|
b2a7d185cb | ||
|
|
9ea61ba6b9 | ||
|
|
19157dec74 | ||
|
|
42a6f516dc | ||
|
|
2cd8226a11 |
2
.github/actions/build-clio/action.yml
vendored
2
.github/actions/build-clio/action.yml
vendored
@@ -14,7 +14,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
with:
|
||||
subtract: ${{ inputs.nproc_subtract }}
|
||||
|
||||
@@ -50,9 +50,9 @@ runs:
|
||||
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
cache-image: false
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
- uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ inputs.images }}
|
||||
|
||||
17
.github/actions/cmake/action.yml
vendored
17
.github/actions/cmake/action.yml
vendored
@@ -37,6 +37,10 @@ inputs:
|
||||
description: Whether to generate Debian package
|
||||
required: true
|
||||
default: "false"
|
||||
version:
|
||||
description: Version of the clio_server binary
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -57,6 +61,19 @@ runs:
|
||||
STATIC: "${{ inputs.static == 'true' && 'ON' || 'OFF' }}"
|
||||
TIME_TRACE: "${{ inputs.time_trace == 'true' && 'ON' || 'OFF' }}"
|
||||
PACKAGE: "${{ inputs.package == 'true' && 'ON' || 'OFF' }}"
|
||||
# GitHub creates a merge commit for a PR
|
||||
# https://www.kenmuse.com/blog/the-many-shas-of-a-github-pull-request/
|
||||
#
|
||||
# We:
|
||||
# - explicitly provide branch name
|
||||
# - use `github.head_ref` to get the SHA of last commit in the PR branch
|
||||
#
|
||||
# This way it works both for PRs and pushes to branches.
|
||||
GITHUB_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}"
|
||||
GITHUB_HEAD_SHA: "${{ github.event.pull_request.head.sha || github.sha }}"
|
||||
#
|
||||
# If tag is being pushed, or it's a nightly release, we use that version.
|
||||
FORCE_CLIO_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
cmake \
|
||||
-B "${BUILD_DIR}" \
|
||||
|
||||
2
.github/actions/code-coverage/action.yml
vendored
2
.github/actions/code-coverage/action.yml
vendored
@@ -24,7 +24,7 @@ runs:
|
||||
-j8 --exclude-throw-branches
|
||||
|
||||
- name: Archive coverage report
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: coverage-report.xml
|
||||
path: build/coverage_report.xml
|
||||
|
||||
4
.github/actions/conan/action.yml
vendored
4
.github/actions/conan/action.yml
vendored
@@ -21,10 +21,6 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Create build directory
|
||||
shell: bash
|
||||
run: mkdir -p "${{ inputs.build_dir }}"
|
||||
|
||||
- name: Run conan
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
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_CONTAINERS = [
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
]
|
||||
LINUX_COMPILERS = ["gcc", "clang"]
|
||||
|
||||
|
||||
4
.github/scripts/conan/init.sh
vendored
4
.github/scripts/conan/init.sh
vendored
@@ -40,9 +40,9 @@ mkdir -p "$PROFILES_DIR"
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
create_profile_with_sanitizers "apple-clang" "$APPLE_CLANG_PROFILE"
|
||||
echo "include(apple-clang)" > "$PROFILES_DIR/default"
|
||||
echo "include(apple-clang)" >"$PROFILES_DIR/default"
|
||||
else
|
||||
create_profile_with_sanitizers "clang" "$CLANG_PROFILE"
|
||||
create_profile_with_sanitizers "gcc" "$GCC_PROFILE"
|
||||
echo "include(gcc)" > "$PROFILES_DIR/default"
|
||||
echo "include(gcc)" >"$PROFILES_DIR/default"
|
||||
fi
|
||||
|
||||
25
.github/scripts/conan/regenerate_lockfile.sh
vendored
Executable file
25
.github/scripts/conan/regenerate_lockfile.sh
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
echo "Using temporary CONAN_HOME: $TEMP_DIR"
|
||||
|
||||
# We use a temporary Conan home to avoid polluting the user's existing Conan
|
||||
# configuration and to not use local cache (which leads to non-reproducible lockfiles).
|
||||
export CONAN_HOME="$TEMP_DIR"
|
||||
|
||||
# Ensure that the xrplf remote is the first to be consulted, so any recipes we
|
||||
# patched are used. We also add it there to not created huge diff when the
|
||||
# official Conan Center Index is updated.
|
||||
conan remote add --force --index 0 xrplf https://conan.ripplex.io
|
||||
|
||||
# Delete any existing lockfile.
|
||||
rm -f conan.lock
|
||||
|
||||
# Create a new lockfile that is compatible with macOS.
|
||||
# It should also work on Linux.
|
||||
conan lock create . \
|
||||
--profile:all=.github/scripts/conan/apple-clang-17.profile
|
||||
18
.github/scripts/execute-tests-under-sanitizer.sh
vendored
18
.github/scripts/execute-tests-under-sanitizer.sh
vendored
@@ -22,8 +22,8 @@ fi
|
||||
TEST_BINARY=$1
|
||||
|
||||
if [[ ! -f "$TEST_BINARY" ]]; then
|
||||
echo "Test binary not found: $TEST_BINARY"
|
||||
exit 1
|
||||
echo "Test binary not found: $TEST_BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TESTS=$($TEST_BINARY --gtest_list_tests | awk '/^ / {print suite $1} !/^ / {suite=$1}')
|
||||
@@ -35,12 +35,12 @@ export TSAN_OPTIONS="die_after_fork=0"
|
||||
export MallocNanoZone='0' # for MacOSX
|
||||
|
||||
for TEST in $TESTS; do
|
||||
OUTPUT_FILE="$OUTPUT_DIR/${TEST//\//_}.log"
|
||||
$TEST_BINARY --gtest_filter="$TEST" > "$OUTPUT_FILE" 2>&1
|
||||
OUTPUT_FILE="$OUTPUT_DIR/${TEST//\//_}.log"
|
||||
$TEST_BINARY --gtest_filter="$TEST" >"$OUTPUT_FILE" 2>&1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "'$TEST' failed a sanitizer check."
|
||||
else
|
||||
rm "$OUTPUT_FILE"
|
||||
fi
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "'$TEST' failed a sanitizer check."
|
||||
else
|
||||
rm "$OUTPUT_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
2
.github/scripts/prepare-release-artifacts.sh
vendored
2
.github/scripts/prepare-release-artifacts.sh
vendored
@@ -20,5 +20,5 @@ for artifact_name in $(ls); do
|
||||
rm "${artifact_name}/${BINARY_NAME}"
|
||||
rm -r "${artifact_name}"
|
||||
|
||||
sha256sum "./${artifact_name}.zip" > "./${artifact_name}.zip.sha256sum"
|
||||
sha256sum "./${artifact_name}.zip" >"./${artifact_name}.zip.sha256sum"
|
||||
done
|
||||
|
||||
@@ -48,11 +48,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download Clio binary from artifact
|
||||
if: ${{ inputs.artifact_name != null }}
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./docker/clio/artifact/
|
||||
|
||||
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -23,6 +23,7 @@ on:
|
||||
- "cmake/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "benchmarks/**"
|
||||
|
||||
- docs/config-description.md
|
||||
workflow_dispatch:
|
||||
@@ -49,7 +50,7 @@ jobs:
|
||||
build_type: [Release, Debug]
|
||||
container:
|
||||
[
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }',
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }',
|
||||
]
|
||||
static: [true]
|
||||
|
||||
@@ -79,7 +80,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
download_ccache: true
|
||||
@@ -92,35 +93,17 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
name: Build packages
|
||||
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: true
|
||||
upload_ccache: false
|
||||
code_coverage: false
|
||||
static: true
|
||||
upload_clio_server: false
|
||||
package: true
|
||||
targets: package
|
||||
analyze_build_time: false
|
||||
|
||||
check_config:
|
||||
name: Check Config Description
|
||||
needs: build-and-test
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: clio_server_Linux_Release_gcc
|
||||
|
||||
|
||||
16
.github/workflows/check-libxrpl.yml
vendored
16
.github/workflows/check-libxrpl.yml
vendored
@@ -21,17 +21,17 @@ jobs:
|
||||
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Update libXRPL version requirement
|
||||
run: |
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
run: strip build/clio_tests
|
||||
|
||||
- name: Upload clio_tests
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: clio_tests_check_libxrpl
|
||||
path: build/clio_tests
|
||||
@@ -69,10 +69,10 @@ jobs:
|
||||
needs: build
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: clio_tests_check_libxrpl
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Create an issue
|
||||
uses: ./.github/actions/create-issue
|
||||
|
||||
2
.github/workflows/check-pr-title.yml
vendored
2
.github/workflows/check-pr-title.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: ytanikin/pr-conventional-commits@b72758283dcbee706975950e96bc4bf323a8d8c0 # 1.4.2
|
||||
- uses: ytanikin/pr-conventional-commits@fda730cb152c05a849d6d84325e50c6182d9d1e9 # 1.5.1
|
||||
with:
|
||||
task_types: '["build","feat","fix","docs","test","ci","style","refactor","perf","chore"]'
|
||||
add_label: false
|
||||
|
||||
45
.github/workflows/clang-tidy.yml
vendored
45
.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')
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -39,14 +39,14 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Run conan
|
||||
uses: ./.github/actions/conan
|
||||
@@ -59,30 +59,33 @@ jobs:
|
||||
conan_profile: ${{ env.CONAN_PROFILE }}
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Run clang-tidy
|
||||
- name: Run clang-tidy (several times)
|
||||
continue-on-error: true
|
||||
id: run_clang_tidy
|
||||
id: clang_tidy
|
||||
run: |
|
||||
run-clang-tidy-${{ env.LLVM_TOOLS_VERSION }} -p build -j "${{ steps.nproc.outputs.nproc }}" -fix -quiet 1>output.txt
|
||||
# We run clang-tidy several times, because some fixes may enable new fixes in subsequent runs.
|
||||
CLANG_TIDY_COMMAND="run-clang-tidy-${{ env.LLVM_TOOLS_VERSION }} -p build -j ${{ steps.nproc.outputs.nproc }} -fix -quiet"
|
||||
${CLANG_TIDY_COMMAND} ||
|
||||
${CLANG_TIDY_COMMAND} ||
|
||||
${CLANG_TIDY_COMMAND}
|
||||
|
||||
- name: Check for changes
|
||||
id: files_changed
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: Fix local includes and clang-format style
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
pre-commit run --all-files fix-local-includes || true
|
||||
pre-commit run --all-files clang-format || true
|
||||
|
||||
- name: Print issues found
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
sed -i '/error\||/!d' ./output.txt
|
||||
cat output.txt
|
||||
rm output.txt
|
||||
|
||||
- name: Create an issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
if: ${{ (steps.clang_tidy.outcome != 'success' || steps.files_changed.outcome != 'success') && github.event_name != 'pull_request' }}
|
||||
id: create_issue
|
||||
uses: ./.github/actions/create-issue
|
||||
env:
|
||||
@@ -95,7 +98,7 @@ jobs:
|
||||
List of the issues found: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/
|
||||
|
||||
- uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.ACTIONS_GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.ACTIONS_GPG_PASSPHRASE }}
|
||||
@@ -103,8 +106,8 @@ jobs:
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: Create PR with fixes
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -119,5 +122,5 @@ jobs:
|
||||
reviewers: "godexsoft,kuznetsss,PeterChen13579,mathbunnyru"
|
||||
|
||||
- name: Fail the job
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
if: ${{ steps.clang_tidy.outcome != 'success' || steps.files_changed.outcome != 'success' }}
|
||||
run: exit 1
|
||||
|
||||
8
.github/workflows/docs.yml
vendored
8
.github/workflows/docs.yml
vendored
@@ -18,18 +18,18 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir build_docs
|
||||
|
||||
58
.github/workflows/nightly.yml
vendored
58
.github/workflows/nightly.yml
vendored
@@ -28,8 +28,20 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
get_date:
|
||||
name: Get Date
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
date: ${{ steps.get_date.outputs.date }}
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: get_date
|
||||
run: |
|
||||
echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
build-and-test:
|
||||
name: Build and Test
|
||||
needs: get_date
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -43,17 +55,17 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc.ubsan
|
||||
build_type: Release
|
||||
static: false
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -67,9 +79,31 @@ jobs:
|
||||
upload_clio_server: true
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
version: nightly-${{ needs.get_date.outputs.date }}
|
||||
|
||||
package:
|
||||
name: Build debian package
|
||||
needs: get_date
|
||||
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
code_coverage: false
|
||||
static: true
|
||||
upload_clio_server: false
|
||||
package: true
|
||||
version: nightly-${{ needs.get_date.outputs.date }}
|
||||
targets: package
|
||||
analyze_build_time: false
|
||||
|
||||
analyze_build_time:
|
||||
name: Analyze Build Time
|
||||
needs: get_date
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -77,7 +111,7 @@ jobs:
|
||||
include:
|
||||
- os: heavy
|
||||
conan_profile: clang
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
static: true
|
||||
- os: macos15
|
||||
conan_profile: apple-clang
|
||||
@@ -96,20 +130,10 @@ jobs:
|
||||
upload_clio_server: false
|
||||
targets: all
|
||||
analyze_build_time: true
|
||||
|
||||
get_date:
|
||||
name: Get Date
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
date: ${{ steps.get_date.outputs.date }}
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: get_date
|
||||
run: |
|
||||
echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
|
||||
version: nightly-${{ needs.get_date.outputs.date }}
|
||||
|
||||
nightly_release:
|
||||
needs: [build-and-test, get_date]
|
||||
needs: [build-and-test, package, get_date]
|
||||
uses: ./.github/workflows/reusable-release.yml
|
||||
with:
|
||||
delete_pattern: "nightly-*"
|
||||
@@ -145,7 +169,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Create an issue
|
||||
uses: ./.github/actions/create-issue
|
||||
|
||||
2
.github/workflows/pre-commit-autoupdate.yml
vendored
2
.github/workflows/pre-commit-autoupdate.yml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
|
||||
jobs:
|
||||
auto-update:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit-autoupdate.yml@afbcbdafbe0ce5439492fb87eda6441371086386
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit-autoupdate.yml@ad4ab1ae5a54a4bab0e87294c31fc0729f788b2b
|
||||
with:
|
||||
sign_commit: true
|
||||
committer: "Clio CI <skuznetsov@ripple.com>"
|
||||
|
||||
4
.github/workflows/pre-commit.yml
vendored
4
.github/workflows/pre-commit.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@34790936fae4c6c751f62ec8c06696f9c1a5753a
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@01163508e81d7dd63d4601d4090b297a260b18c2
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:c117f470f2ef954520ab5d1c8a5ed2b9e68d6f8a" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -43,10 +43,29 @@ jobs:
|
||||
upload_clio_server: true
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
expected_version: ${{ github.event_name == 'push' && github.ref_name || '' }}
|
||||
version: ${{ github.event_name == 'push' && github.ref_name || '' }}
|
||||
|
||||
package:
|
||||
name: Build debian package
|
||||
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
code_coverage: false
|
||||
static: true
|
||||
upload_clio_server: false
|
||||
package: true
|
||||
version: ${{ github.event_name == 'push' && github.ref_name || '' }}
|
||||
targets: package
|
||||
analyze_build_time: false
|
||||
|
||||
release:
|
||||
needs: build-and-test
|
||||
needs: [build-and-test, package]
|
||||
uses: ./.github/workflows/reusable-release.yml
|
||||
with:
|
||||
delete_pattern: ""
|
||||
|
||||
14
.github/workflows/reusable-build-test.yml
vendored
14
.github/workflows/reusable-build-test.yml
vendored
@@ -63,18 +63,18 @@ on:
|
||||
type: string
|
||||
default: all
|
||||
|
||||
expected_version:
|
||||
description: Expected version of the clio_server binary
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
package:
|
||||
description: Whether to generate Debian package
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
version:
|
||||
description: Version of the clio_server binary
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
@@ -90,8 +90,8 @@ jobs:
|
||||
upload_clio_server: ${{ inputs.upload_clio_server }}
|
||||
targets: ${{ inputs.targets }}
|
||||
analyze_build_time: false
|
||||
expected_version: ${{ inputs.expected_version }}
|
||||
package: ${{ inputs.package }}
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
test:
|
||||
needs: build
|
||||
|
||||
58
.github/workflows/reusable-build.yml
vendored
58
.github/workflows/reusable-build.yml
vendored
@@ -60,17 +60,17 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
expected_version:
|
||||
description: Expected version of the clio_server binary
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
package:
|
||||
description: Whether to generate Debian package
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
version:
|
||||
description: Version of the clio_server binary
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
required: false
|
||||
@@ -88,20 +88,16 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/.github/actions/cleanup-workspace@ea9970b7c211b18f4c8bcdb28c29f5711752029f
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# We need to fetch tags to have correct version in the release
|
||||
# The workaround is based on https://github.com/actions/checkout/issues/1467
|
||||
fetch-tags: true
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: ${{ !inputs.download_ccache }}
|
||||
enable_ccache: ${{ inputs.download_ccache }}
|
||||
|
||||
- name: Setup conan on macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
@@ -117,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Restore ccache cache
|
||||
if: ${{ inputs.download_ccache && github.ref != 'refs/heads/develop' }}
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ steps.cache_key.outputs.key }}
|
||||
@@ -139,6 +135,7 @@ jobs:
|
||||
static: ${{ inputs.static }}
|
||||
time_trace: ${{ inputs.analyze_build_time }}
|
||||
package: ${{ inputs.package }}
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
- name: Build Clio
|
||||
uses: ./.github/actions/build-clio
|
||||
@@ -154,7 +151,7 @@ jobs:
|
||||
|
||||
- name: Upload build time analyze report
|
||||
if: ${{ inputs.analyze_build_time }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build_time_report.txt
|
||||
@@ -162,12 +159,12 @@ jobs:
|
||||
- name: Show ccache's statistics and zero it
|
||||
if: ${{ inputs.download_ccache }}
|
||||
run: |
|
||||
ccache --show-stats
|
||||
ccache --show-stats -vv
|
||||
ccache --zero-stats
|
||||
|
||||
- name: Save ccache cache
|
||||
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' }}
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ steps.cache_key.outputs.key }}
|
||||
@@ -182,28 +179,28 @@ jobs:
|
||||
|
||||
- name: Upload clio_server
|
||||
if: ${{ inputs.upload_clio_server && !inputs.code_coverage && !inputs.analyze_build_time }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: clio_server_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build/clio_server
|
||||
|
||||
- name: Upload clio_tests
|
||||
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build/clio_tests
|
||||
|
||||
- name: Upload clio_integration_tests
|
||||
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build/clio_integration_tests
|
||||
|
||||
- name: Upload Clio Linux package
|
||||
if: ${{ inputs.package }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: clio_deb_package_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build/*.deb
|
||||
@@ -218,15 +215,20 @@ jobs:
|
||||
if: ${{ inputs.code_coverage }}
|
||||
uses: ./.github/actions/code-coverage
|
||||
|
||||
- name: Verify expected version
|
||||
if: ${{ inputs.expected_version != '' }}
|
||||
- name: Verify version is expected
|
||||
if: ${{ inputs.version != '' }}
|
||||
env:
|
||||
INPUT_EXPECTED_VERSION: ${{ inputs.expected_version }}
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
run: |
|
||||
set -e
|
||||
EXPECTED_VERSION="clio-${INPUT_EXPECTED_VERSION}"
|
||||
actual_version=$(./build/clio_server --version)
|
||||
if [[ "$actual_version" != "$EXPECTED_VERSION" ]]; then
|
||||
EXPECTED_VERSION="clio-${INPUT_VERSION}"
|
||||
if [[ "${BUILD_TYPE}" == "Debug" ]]; then
|
||||
EXPECTED_VERSION="${EXPECTED_VERSION}+DEBUG"
|
||||
fi
|
||||
|
||||
actual_version=$(./build/clio_server --version | head -n 1)
|
||||
if [[ "${actual_version}" != "${EXPECTED_VERSION}" ]]; then
|
||||
echo "Expected version '${EXPECTED_VERSION}', but got '${actual_version}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
28
.github/workflows/reusable-release.yml
vendored
28
.github/workflows/reusable-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
release:
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -55,20 +55,28 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
path: release_artifacts
|
||||
pattern: clio_server_*
|
||||
|
||||
- name: Prepare release artifacts
|
||||
run: .github/scripts/prepare-release-artifacts.sh release_artifacts
|
||||
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
path: release_artifacts
|
||||
pattern: clio_deb_package_*
|
||||
|
||||
- name: Create release notes
|
||||
env:
|
||||
RELEASE_HEADER: ${{ inputs.header }}
|
||||
@@ -83,14 +91,10 @@ jobs:
|
||||
LAST_TAG="$(gh release view --json tagName -q .tagName --repo XRPLF/clio)"
|
||||
LAST_TAG_COMMIT="$(git rev-parse $LAST_TAG)"
|
||||
BASE_COMMIT="$(git merge-base HEAD $LAST_TAG_COMMIT)"
|
||||
git-cliff "${BASE_COMMIT}..HEAD" --ignore-tags "nightly|-b|-rc"
|
||||
cat CHANGELOG.md >> "${RUNNER_TEMP}/release_notes.md"
|
||||
|
||||
- name: Prepare release artifacts
|
||||
run: .github/scripts/prepare-release-artifacts.sh release_artifacts
|
||||
git-cliff "${BASE_COMMIT}..HEAD" --ignore-tags "nightly|-b|-rc" >> "${RUNNER_TEMP}/release_notes.md"
|
||||
|
||||
- name: Upload release notes
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: release_notes_${{ inputs.version }}
|
||||
path: "${RUNNER_TEMP}/release_notes.md"
|
||||
@@ -122,4 +126,4 @@ jobs:
|
||||
--target "${GITHUB_SHA}" \
|
||||
${DRAFT_OPTION} \
|
||||
--notes-file "${RUNNER_TEMP}/release_notes.md" \
|
||||
./release_artifacts/clio_server*
|
||||
./release_artifacts/clio_*
|
||||
|
||||
30
.github/workflows/reusable-test.yml
vendored
30
.github/workflows/reusable-test.yml
vendored
@@ -52,13 +52,13 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/.github/actions/cleanup-workspace@ea9970b7c211b18f4c8bcdb28c29f5711752029f
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Upload sanitizer report
|
||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' && steps.check_report.outputs.found_report == 'true' }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: sanitizer_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: .sanitizer-report/*
|
||||
@@ -124,13 +124,19 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/.github/actions/cleanup-workspace@ea9970b7c211b18f4c8bcdb28c29f5711752029f
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- name: Spin up scylladb
|
||||
- name: Delete and start colima (macOS)
|
||||
# This is a temporary workaround for colima issues on macOS runners
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
docker rm --force scylladb || true
|
||||
colima delete --force
|
||||
colima start
|
||||
|
||||
- name: Spin up scylladb (macOS)
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
docker run \
|
||||
--detach \
|
||||
--name scylladb \
|
||||
@@ -142,11 +148,15 @@ jobs:
|
||||
--memory 16G \
|
||||
scylladb/scylla
|
||||
|
||||
- name: Wait for scylladb container to be healthy (macOS)
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
until [ "$(docker inspect -f '{{.State.Health.Status}}' scylladb)" == "healthy" ]; do
|
||||
sleep 5
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
|
||||
|
||||
@@ -16,19 +16,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download report artifact
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: coverage-report.xml
|
||||
path: build
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ hashFiles('build/coverage_report.xml') != '' }}
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
files: build/coverage_report.xml
|
||||
fail_ci_if_error: true
|
||||
|
||||
2
.github/workflows/sanitizers.yml
vendored
2
.github/workflows/sanitizers.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
36
.github/workflows/update-docker-ci.yml
vendored
36
.github/workflows/update-docker-ci.yml
vendored
@@ -56,11 +56,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -94,11 +94,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -132,16 +132,16 @@ jobs:
|
||||
needs: [repo, gcc-amd64, gcc-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -183,11 +183,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/clang/**"
|
||||
|
||||
@@ -219,11 +219,11 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -250,11 +250,11 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -281,16 +281,16 @@ jobs:
|
||||
needs: [repo, tools-amd64, tools-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -316,7 +316,7 @@ jobs:
|
||||
needs: [repo, tools-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/build-docker-image
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -338,7 +338,7 @@ jobs:
|
||||
needs: [repo, gcc-merge, clang, tools-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/build-docker-image
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.github/workflows/upload-conan-deps.yml
vendored
10
.github/workflows/upload-conan-deps.yml
vendored
@@ -22,6 +22,7 @@ on:
|
||||
|
||||
- .github/actions/conan/action.yml
|
||||
- ".github/scripts/conan/**"
|
||||
- "!.github/scripts/conan/regenerate_lockfile.sh"
|
||||
|
||||
- conanfile.py
|
||||
- conan.lock
|
||||
@@ -32,6 +33,7 @@ on:
|
||||
|
||||
- .github/actions/conan/action.yml
|
||||
- ".github/scripts/conan/**"
|
||||
- "!.github/scripts/conan/regenerate_lockfile.sh"
|
||||
|
||||
- conanfile.py
|
||||
- conan.lock
|
||||
@@ -50,7 +52,7 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Calculate conan matrix
|
||||
id: set-matrix
|
||||
@@ -73,12 +75,12 @@ jobs:
|
||||
CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
uses: XRPLF/actions/prepare-runner@e8d2d2a546a03e1d161dca52890705f3bc641215
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Setup conan on macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
.build
|
||||
.cache
|
||||
.vscode
|
||||
.zed
|
||||
.python-version
|
||||
.DS_Store
|
||||
.sanitizer-report
|
||||
|
||||
@@ -29,12 +29,12 @@ repos:
|
||||
|
||||
# Autoformat: YAML, JSON, Markdown, etc.
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2
|
||||
rev: 14abee445aea04b39069c19b4bd54efff6775819 # frozen: v3.7.4
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: 192ad822316c3a22fb3d3cc8aa6eafa0b8488360 # frozen: v0.45.0
|
||||
rev: 76b3d32d3f4b965e1d6425253c59407420ae2c43 # frozen: v0.47.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: LICENSE.md
|
||||
@@ -58,6 +58,17 @@ repos:
|
||||
--ignore-words=pre-commit-hooks/codespell_ignore.txt,
|
||||
]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 831207fd435b47aeffdf6af853097e64322b4d44 # frozen: 25.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 2a30809d16bc7a60d9b97353c797f42b510d3368 # frozen: v3.12.0-2
|
||||
hooks:
|
||||
- id: shfmt
|
||||
args: ["-i", "4", "--write"]
|
||||
|
||||
# Running some C++ hooks before clang-format
|
||||
# to ensure that the style is consistent.
|
||||
- repo: local
|
||||
@@ -83,7 +94,7 @@ repos:
|
||||
language: script
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: 719856d56a62953b8d2839fb9e851f25c3cfeef8 # frozen: v21.1.2
|
||||
rev: 75ca4ad908dc4a99f57921f29b7e6c1521e10b26 # frozen: v21.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
|
||||
@@ -180,6 +180,7 @@ Existing maintainers can resign, or be subject to a vote for removal at the behe
|
||||
- [kuznetsss](https://github.com/kuznetsss) (Ripple)
|
||||
- [legleux](https://github.com/legleux) (Ripple)
|
||||
- [PeterChen13579](https://github.com/PeterChen13579) (Ripple)
|
||||
- [mathbunnyru](https://github.com/mathbunnyru) (Ripple)
|
||||
|
||||
### Honorable ex-Maintainers
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ target_sources(
|
||||
util/async/ExecutionContextBenchmarks.cpp
|
||||
# Logger
|
||||
util/log/LoggerBenchmark.cpp
|
||||
# WorkQueue
|
||||
rpc/WorkQueueBenchmarks.cpp
|
||||
)
|
||||
|
||||
include(deps/gbench)
|
||||
|
||||
target_include_directories(clio_benchmark PRIVATE .)
|
||||
target_link_libraries(clio_benchmark PUBLIC clio_util benchmark::benchmark_main spdlog::spdlog)
|
||||
target_link_libraries(clio_benchmark PRIVATE clio_rpc clio_util benchmark::benchmark_main spdlog::spdlog)
|
||||
set_target_properties(clio_benchmark PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
145
benchmarks/rpc/WorkQueueBenchmarks.cpp
Normal file
145
benchmarks/rpc/WorkQueueBenchmarks.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "rpc/WorkQueue.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/config/Array.hpp"
|
||||
#include "util/config/ConfigConstraints.hpp"
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/config/ConfigValue.hpp"
|
||||
#include "util/config/Types.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace rpc;
|
||||
using namespace util::config;
|
||||
|
||||
namespace {
|
||||
|
||||
auto const kCONFIG = ClioConfigDefinition{
|
||||
{"prometheus.compress_reply", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
|
||||
{"prometheus.enabled", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
|
||||
{"log.channels.[].channel", Array{ConfigValue{ConfigType::String}}},
|
||||
{"log.channels.[].level", Array{ConfigValue{ConfigType::String}}},
|
||||
{"log.level", ConfigValue{ConfigType::String}.defaultValue("info")},
|
||||
{"log.format", ConfigValue{ConfigType::String}.defaultValue(R"(%Y-%m-%d %H:%M:%S.%f %^%3!l:%n%$ - %v)")},
|
||||
{"log.is_async", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||
{"log.enable_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||
{"log.directory", ConfigValue{ConfigType::String}.optional()},
|
||||
{"log.rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048).withConstraint(gValidateUint32)},
|
||||
{"log.directory_max_files", ConfigValue{ConfigType::Integer}.defaultValue(25).withConstraint(gValidateUint32)},
|
||||
{"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("none")},
|
||||
};
|
||||
|
||||
// this should be a fixture but it did not work with Args very well
|
||||
void
|
||||
init()
|
||||
{
|
||||
static std::once_flag kONCE;
|
||||
std::call_once(kONCE, [] {
|
||||
PrometheusService::init(kCONFIG);
|
||||
(void)util::LogService::init(kCONFIG);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static void
|
||||
benchmarkWorkQueue(benchmark::State& state)
|
||||
{
|
||||
init();
|
||||
|
||||
auto const wqThreads = static_cast<uint32_t>(state.range(0));
|
||||
auto const maxQueueSize = static_cast<uint32_t>(state.range(1));
|
||||
auto const clientThreads = static_cast<uint32_t>(state.range(2));
|
||||
auto const itemsPerClient = static_cast<uint32_t>(state.range(3));
|
||||
auto const clientProcessingMs = static_cast<uint32_t>(state.range(4));
|
||||
|
||||
for (auto _ : state) {
|
||||
std::atomic_size_t totalExecuted = 0uz;
|
||||
std::atomic_size_t totalQueued = 0uz;
|
||||
|
||||
state.PauseTiming();
|
||||
WorkQueue queue(wqThreads, maxQueueSize);
|
||||
state.ResumeTiming();
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(clientThreads);
|
||||
|
||||
for (auto t = 0uz; t < clientThreads; ++t) {
|
||||
threads.emplace_back([&] {
|
||||
for (auto i = 0uz; i < itemsPerClient; ++i) {
|
||||
totalQueued += static_cast<std::size_t>(queue.postCoro(
|
||||
[&clientProcessingMs, &totalExecuted](auto yield) {
|
||||
++totalExecuted;
|
||||
|
||||
boost::asio::steady_timer timer(
|
||||
yield.get_executor(), std::chrono::milliseconds{clientProcessingMs}
|
||||
);
|
||||
timer.async_wait(yield);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds{10});
|
||||
},
|
||||
/* isWhiteListed = */ false
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads)
|
||||
t.join();
|
||||
|
||||
queue.stop();
|
||||
|
||||
ASSERT(totalExecuted == totalQueued, "Totals don't match");
|
||||
ASSERT(totalQueued <= itemsPerClient * clientThreads, "Queued more than requested");
|
||||
|
||||
if (maxQueueSize == 0) {
|
||||
ASSERT(totalQueued == itemsPerClient * clientThreads, "Queued exactly the expected amount");
|
||||
} else {
|
||||
ASSERT(totalQueued >= std::min(maxQueueSize, itemsPerClient * clientThreads), "Queued less than expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage example:
|
||||
/*
|
||||
./clio_benchmark \
|
||||
--benchmark_repetitions=10 \
|
||||
--benchmark_display_aggregates_only=true \
|
||||
--benchmark_min_time=1x \
|
||||
--benchmark_filter="WorkQueue"
|
||||
*/
|
||||
// TODO: figure out what happens on 1 thread
|
||||
BENCHMARK(benchmarkWorkQueue)
|
||||
->ArgsProduct({{2, 4, 8, 16}, {0, 5'000}, {4, 8, 16}, {1'000, 10'000}, {10, 100, 250}})
|
||||
->Unit(benchmark::kMillisecond);
|
||||
@@ -49,8 +49,6 @@ postprocessors = [
|
||||
]
|
||||
# render body even when there are no releases to process
|
||||
# render_always = true
|
||||
# output file path
|
||||
output = "CHANGELOG.md"
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
find_package(Git REQUIRED)
|
||||
|
||||
set(GIT_COMMAND describe --tags --exact-match)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE TAG
|
||||
RESULT_VARIABLE RC
|
||||
ERROR_VARIABLE ERR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
if (RC EQUAL 0)
|
||||
message(STATUS "Found tag '${TAG}' in git. Will use it as Clio version")
|
||||
set(CLIO_VERSION "${TAG}")
|
||||
set(DOC_CLIO_VERSION "${TAG}")
|
||||
if (DEFINED ENV{GITHUB_BRANCH_NAME})
|
||||
set(GIT_BUILD_BRANCH $ENV{GITHUB_BRANCH_NAME})
|
||||
set(GIT_COMMIT_HASH $ENV{GITHUB_HEAD_SHA})
|
||||
else ()
|
||||
message(STATUS "Error finding tag in git: ${ERR}")
|
||||
message(STATUS "Will use 'YYYYMMDDHMS-<branch>-<git-rev>' as Clio version")
|
||||
|
||||
set(GIT_COMMAND show -s --date=format:%Y%m%d%H%M%S --format=%cd)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE DATE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
set(GIT_COMMAND branch --show-current)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE BRANCH
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_BUILD_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
set(GIT_COMMAND rev-parse --short HEAD)
|
||||
set(GIT_COMMAND rev-parse HEAD)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE REV
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(CLIO_VERSION "${DATE}-${BRANCH}-${REV}")
|
||||
execute_process(
|
||||
COMMAND date +%Y%m%d%H%M%S WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE BUILD_DATE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
message(STATUS "Git branch: ${GIT_BUILD_BRANCH}")
|
||||
message(STATUS "Git commit hash: ${GIT_COMMIT_HASH}")
|
||||
message(STATUS "Build date: ${BUILD_DATE}")
|
||||
|
||||
if (DEFINED ENV{FORCE_CLIO_VERSION} AND NOT "$ENV{FORCE_CLIO_VERSION}" STREQUAL "")
|
||||
message(STATUS "Using explicitly provided '${FORCE_CLIO_VERSION}' as Clio version")
|
||||
|
||||
set(CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
||||
set(DOC_CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
||||
else ()
|
||||
message(STATUS "Using 'YYYYMMDDHMS-<branch>-<git short rev>' as Clio version")
|
||||
|
||||
string(SUBSTRING ${GIT_COMMIT_HASH} 0 7 GIT_COMMIT_HASH_SHORT)
|
||||
|
||||
set(CLIO_VERSION "${BUILD_DATE}-${GIT_BUILD_BRANCH}-${GIT_COMMIT_HASH_SHORT}")
|
||||
set(DOC_CLIO_VERSION "develop")
|
||||
endif ()
|
||||
|
||||
|
||||
17
cmake/install/clio.service.in
Normal file
17
cmake/install/clio.service.in
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Clio XRPL API server
|
||||
Documentation=https://github.com/XRPLF/clio.git
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=@CLIO_INSTALL_DIR@/bin/clio_server @CLIO_INSTALL_DIR@/etc/config.json
|
||||
Restart=on-failure
|
||||
User=clio
|
||||
Group=clio
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -11,3 +11,6 @@ file(READ docs/examples/config/example-config.json config)
|
||||
string(REGEX REPLACE "./clio_log" "/var/log/clio/" config "${config}")
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/install-config.json "${config}")
|
||||
install(FILES ${CMAKE_BINARY_DIR}/install-config.json DESTINATION etc RENAME config.json)
|
||||
|
||||
configure_file("${CMAKE_SOURCE_DIR}/cmake/install/clio.service.in" "${CMAKE_BINARY_DIR}/clio.service")
|
||||
install(FILES "${CMAKE_BINARY_DIR}/clio.service" DESTINATION /lib/systemd/system)
|
||||
|
||||
@@ -10,37 +10,36 @@ CLIO_BIN="$CLIO_PREFIX/bin/${CLIO_EXECUTABLE}"
|
||||
CLIO_CONFIG="$CLIO_PREFIX/etc/config.json"
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if ! id -u "$USER_NAME" >/dev/null 2>&1; then
|
||||
# Users who should not have a home directory should have their home directory set to /nonexistent
|
||||
# https://www.debian.org/doc/debian-policy/ch-opersys.html#non-existent-home-directories
|
||||
useradd \
|
||||
--system \
|
||||
--home-dir /nonexistent \
|
||||
--no-create-home \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "system user for ${CLIO_EXECUTABLE}" \
|
||||
--user-group \
|
||||
${USER_NAME}
|
||||
fi
|
||||
configure)
|
||||
if ! id -u "$USER_NAME" >/dev/null 2>&1; then
|
||||
# Users who should not have a home directory should have their home directory set to /nonexistent
|
||||
# https://www.debian.org/doc/debian-policy/ch-opersys.html#non-existent-home-directories
|
||||
useradd \
|
||||
--system \
|
||||
--home-dir /nonexistent \
|
||||
--no-create-home \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "system user for ${CLIO_EXECUTABLE}" \
|
||||
--user-group \
|
||||
${USER_NAME}
|
||||
fi
|
||||
|
||||
install -d -o "$USER_NAME" -g "$GROUP_NAME" /var/log/clio
|
||||
install -d -o "$USER_NAME" -g "$GROUP_NAME" /var/log/clio
|
||||
|
||||
if [ -f "$CLIO_CONFIG" ]; then
|
||||
chown "$USER_NAME:$GROUP_NAME" "$CLIO_CONFIG"
|
||||
fi
|
||||
if [ -f "$CLIO_CONFIG" ]; then
|
||||
chown "$USER_NAME:$GROUP_NAME" "$CLIO_CONFIG"
|
||||
fi
|
||||
|
||||
chown -R "$USER_NAME:$GROUP_NAME" "$CLIO_PREFIX"
|
||||
chown -R "$USER_NAME:$GROUP_NAME" "$CLIO_PREFIX"
|
||||
|
||||
ln -sf "$CLIO_BIN" "/usr/bin/${CLIO_EXECUTABLE}"
|
||||
ln -sf "$CLIO_BIN" "/usr/bin/${CLIO_EXECUTABLE}"
|
||||
|
||||
;;
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
;;
|
||||
abort-upgrade | abort-remove | abort-deconfigure) ;;
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
55
conan.lock
55
conan.lock
@@ -1,51 +1,52 @@
|
||||
{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1756234289.683",
|
||||
"xrpl/3.0.0-rc1#f5c8ecd42bdf511ad36f57bc702dacd2%1762975621.294",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1756234266.869",
|
||||
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1754401846.61",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1756234262.318",
|
||||
"re2/20230301#dfd6e2bf050eb90ddd8729cfb4c844a4%1756234257.976",
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"xrpl/3.0.0#534d3f65a336109eee929b88962bae4e%1765375071.547",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1767636069.964",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
|
||||
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1756234251.614",
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||
"openssl/1.1.1w#a8f0792d7c5121b954578a7149d23e03%1756223730.729",
|
||||
"nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1763150366.909",
|
||||
"nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1765850143.957",
|
||||
"minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1754325004.374",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1756234228.999",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libuv/1.46.0#dc28c1f653fa197f00db5b577a6f6011%1754325003.592",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1756223727.64",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1756230911.03",
|
||||
"libarchive/3.8.1#5cf685686322e906cb42706ab7e099a8%1756234256.696",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
|
||||
"http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1754325001.385",
|
||||
"gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1754325000.842",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1755784855.585",
|
||||
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
|
||||
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1754324999.086",
|
||||
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
||||
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
|
||||
"date/3.0.4#f74bbba5a08fa388256688743136cb6f%1756234217.493",
|
||||
"cassandra-cpp-driver/2.17.0#e50919efac8418c26be6671fd702540a%1754324997.363",
|
||||
"c-ares/1.34.5#b78b91e7cfb1f11ce777a285bbf169c6%1756234217.915",
|
||||
"bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1756234261.716",
|
||||
"boost/1.83.0#5d975011d65b51abb2d2f6eb8386b368%1754325043.336",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
|
||||
"cassandra-cpp-driver/2.17.0#bd3934138689482102c265d01288a316%1764175359.611",
|
||||
"c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1765850144.336",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1%1764175359.61",
|
||||
"benchmark/1.9.4#ce4403f7a24d3e1f907cd9da4b678be4%1754578869.672",
|
||||
"abseil/20230802.1#f0f91485b111dc9837a68972cb19ca7b%1756234220.907"
|
||||
"abseil/20230802.1#90ba607d4ee8fb5fb157c3db540671fc%1764175359.429"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1756234251.614",
|
||||
"cmake/3.31.8#dde3bde00bb843687e55aea5afa0e220%1756234232.89",
|
||||
"b2/5.3.3#107c15377719889654eb9a162a673975%1756234226.28"
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||
"cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1765850153.937",
|
||||
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479",
|
||||
"b2/5.3.3#107c15377719889654eb9a162a673975%1765850144.355"
|
||||
],
|
||||
"python_requires": [],
|
||||
"overrides": {
|
||||
"boost/1.83.0": [
|
||||
null,
|
||||
"boost/1.83.0#5d975011d65b51abb2d2f6eb8386b368"
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
|
||||
],
|
||||
"protobuf/3.21.12": [
|
||||
null,
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1"
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88"
|
||||
],
|
||||
"lz4/1.9.4": [
|
||||
"lz4/1.10.0"
|
||||
|
||||
74
conanfile.py
74
conanfile.py
@@ -3,62 +3,60 @@ from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
|
||||
|
||||
class ClioConan(ConanFile):
|
||||
name = 'clio'
|
||||
license = 'ISC'
|
||||
author = 'Alex Kremer <akremer@ripple.com>, John Freeman <jfreeman@ripple.com>, Ayaz Salikhov <asalikhov@ripple.com>'
|
||||
url = 'https://github.com/xrplf/clio'
|
||||
description = 'Clio RPC server'
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
name = "clio"
|
||||
license = "ISC"
|
||||
author = "Alex Kremer <akremer@ripple.com>, John Freeman <jfreeman@ripple.com>, Ayaz Salikhov <asalikhov@ripple.com>"
|
||||
url = "https://github.com/xrplf/clio"
|
||||
description = "Clio RPC server"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {}
|
||||
|
||||
requires = [
|
||||
'boost/1.83.0',
|
||||
'cassandra-cpp-driver/2.17.0',
|
||||
'fmt/11.2.0',
|
||||
'protobuf/3.21.12',
|
||||
'grpc/1.50.1',
|
||||
'openssl/1.1.1w',
|
||||
'xrpl/3.0.0-rc1',
|
||||
'zlib/1.3.1',
|
||||
'libbacktrace/cci.20210118',
|
||||
'spdlog/1.15.3',
|
||||
"boost/1.83.0",
|
||||
"cassandra-cpp-driver/2.17.0",
|
||||
"fmt/12.1.0",
|
||||
"grpc/1.50.1",
|
||||
"libbacktrace/cci.20210118",
|
||||
"openssl/1.1.1w",
|
||||
"protobuf/3.21.12",
|
||||
"spdlog/1.17.0",
|
||||
"xrpl/3.0.0",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
'xrpl/*:tests': False,
|
||||
'xrpl/*:rocksdb': False,
|
||||
'cassandra-cpp-driver/*:shared': False,
|
||||
'date/*:header_only': True,
|
||||
'grpc/*:shared': False,
|
||||
'grpc/*:secure': True,
|
||||
'libpq/*:shared': False,
|
||||
'lz4/*:shared': False,
|
||||
'openssl/*:shared': False,
|
||||
'protobuf/*:shared': False,
|
||||
'protobuf/*:with_zlib': True,
|
||||
'snappy/*:shared': False,
|
||||
'gtest/*:no_main': True,
|
||||
"cassandra-cpp-driver/*:shared": False,
|
||||
"date/*:header_only": True,
|
||||
"grpc/*:secure": True,
|
||||
"grpc/*:shared": False,
|
||||
"gtest/*:no_main": True,
|
||||
"libpq/*:shared": False,
|
||||
"lz4/*:shared": False,
|
||||
"openssl/*:shared": False,
|
||||
"protobuf/*:shared": False,
|
||||
"protobuf/*:with_zlib": True,
|
||||
"snappy/*:shared": False,
|
||||
"xrpl/*:rocksdb": False,
|
||||
"xrpl/*:tests": False,
|
||||
}
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt', 'cmake/*', 'src/*'
|
||||
)
|
||||
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
|
||||
|
||||
def requirements(self):
|
||||
self.requires('gtest/1.14.0')
|
||||
self.requires('benchmark/1.9.4')
|
||||
self.requires("gtest/1.17.0")
|
||||
self.requires("benchmark/1.9.4")
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost'].visibility = 'global'
|
||||
if self.settings.compiler == "apple-clang":
|
||||
self.options["boost"].visibility = "global"
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
# Fix this setting to follow the default introduced in Conan 1.48
|
||||
# to align with our build instructions.
|
||||
self.folders.generators = 'build/generators'
|
||||
self.folders.generators = "build/generators"
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
generators = "CMakeDeps"
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
|
||||
@@ -36,7 +36,6 @@ RUN apt-get update \
|
||||
libmpfr-dev \
|
||||
libncurses-dev \
|
||||
make \
|
||||
ninja-build \
|
||||
wget \
|
||||
zip \
|
||||
&& apt-get clean \
|
||||
@@ -55,7 +54,7 @@ RUN pip install -q --no-cache-dir \
|
||||
# lxml 6.0.0 is not compatible with our image
|
||||
'lxml<6.0.0' \
|
||||
cmake \
|
||||
conan==2.22.1 \
|
||||
conan==2.24.0 \
|
||||
gcovr \
|
||||
# We're adding pre-commit to this image as well,
|
||||
# because clang-tidy workflow requires it
|
||||
@@ -107,6 +106,7 @@ COPY --from=clio-tools \
|
||||
/usr/local/bin/git-cliff \
|
||||
/usr/local/bin/gh \
|
||||
/usr/local/bin/gdb \
|
||||
/usr/local/bin/ninja \
|
||||
/usr/local/bin/
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
@@ -5,16 +5,17 @@ It is used in [Clio Github Actions](https://github.com/XRPLF/clio/actions) but c
|
||||
|
||||
The image is based on Ubuntu 20.04 and contains:
|
||||
|
||||
- ccache 4.12.1
|
||||
- ccache 4.12.2
|
||||
- Clang 19
|
||||
- ClangBuildAnalyzer 1.6.0
|
||||
- Conan 2.22.1
|
||||
- Doxygen 1.15.0
|
||||
- Conan 2.24.0
|
||||
- Doxygen 1.16.1
|
||||
- GCC 15.2.0
|
||||
- GDB 16.3
|
||||
- gh 2.82.1
|
||||
- git-cliff 2.10.1
|
||||
- GDB 17.1
|
||||
- gh 2.83.2
|
||||
- git-cliff 2.11.0
|
||||
- mold 2.40.4
|
||||
- Ninja 1.13.2
|
||||
- Python 3.8
|
||||
- and some other useful tools
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clio_develop:
|
||||
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
volumes:
|
||||
- clio_develop_conan_data:/root/.conan2/p
|
||||
- clio_develop_ccache:/root/.ccache
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
|
||||
pushd $script_dir > /dev/null
|
||||
pushd $script_dir >/dev/null
|
||||
|
||||
function start_container {
|
||||
if [ -z "$(docker ps -q -f name=clio_develop)" ]; then
|
||||
@@ -41,21 +41,26 @@ EOF
|
||||
}
|
||||
|
||||
case $1 in
|
||||
-h|--help)
|
||||
print_help ;;
|
||||
-h | --help)
|
||||
print_help
|
||||
;;
|
||||
|
||||
-t|--terminal)
|
||||
open_terminal ;;
|
||||
-t | --terminal)
|
||||
open_terminal
|
||||
;;
|
||||
|
||||
-s|--stop)
|
||||
stop_container ;;
|
||||
-s | --stop)
|
||||
stop_container
|
||||
;;
|
||||
|
||||
-*)
|
||||
echo "Unknown option: $1"
|
||||
print_help ;;
|
||||
-*)
|
||||
echo "Unknown option: $1"
|
||||
print_help
|
||||
;;
|
||||
|
||||
*)
|
||||
run "$@" ;;
|
||||
*)
|
||||
run "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
popd > /dev/null
|
||||
popd >/dev/null
|
||||
|
||||
@@ -12,7 +12,6 @@ ARG BUILD_VERSION=0
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends --no-install-suggests \
|
||||
ninja-build \
|
||||
python3 \
|
||||
python3-pip \
|
||||
software-properties-common \
|
||||
@@ -24,6 +23,15 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
ARG NINJA_VERSION=1.13.2
|
||||
|
||||
RUN wget --progress=dot:giga "https://github.com/ninja-build/ninja/archive/refs/tags/v${NINJA_VERSION}.tar.gz" \
|
||||
&& tar xf "v${NINJA_VERSION}.tar.gz" \
|
||||
&& cd "ninja-${NINJA_VERSION}" \
|
||||
&& ./configure.py --bootstrap \
|
||||
&& mv ninja /usr/local/bin/ninja \
|
||||
&& rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ARG MOLD_VERSION=2.40.4
|
||||
RUN wget --progress=dot:giga "https://github.com/rui314/mold/archive/refs/tags/v${MOLD_VERSION}.tar.gz" \
|
||||
&& tar xf "v${MOLD_VERSION}.tar.gz" \
|
||||
@@ -34,7 +42,7 @@ RUN wget --progress=dot:giga "https://github.com/rui314/mold/archive/refs/tags/v
|
||||
&& ninja install \
|
||||
&& rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ARG CCACHE_VERSION=4.12.1
|
||||
ARG CCACHE_VERSION=4.12.2
|
||||
RUN wget --progress=dot:giga "https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/ccache-${CCACHE_VERSION}.tar.gz" \
|
||||
&& tar xf "ccache-${CCACHE_VERSION}.tar.gz" \
|
||||
&& cd "ccache-${CCACHE_VERSION}" \
|
||||
@@ -51,7 +59,7 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG DOXYGEN_VERSION=1.15.0
|
||||
ARG DOXYGEN_VERSION=1.16.1
|
||||
RUN wget --progress=dot:giga "https://github.com/doxygen/doxygen/releases/download/Release_${DOXYGEN_VERSION//./_}/doxygen-${DOXYGEN_VERSION}.src.tar.gz" \
|
||||
&& tar xf "doxygen-${DOXYGEN_VERSION}.src.tar.gz" \
|
||||
&& cd "doxygen-${DOXYGEN_VERSION}" \
|
||||
@@ -71,13 +79,13 @@ RUN wget --progress=dot:giga "https://github.com/aras-p/ClangBuildAnalyzer/archi
|
||||
&& ninja install \
|
||||
&& rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ARG GIT_CLIFF_VERSION=2.10.1
|
||||
ARG GIT_CLIFF_VERSION=2.11.0
|
||||
RUN wget --progress=dot:giga "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
|
||||
&& tar xf git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-musl.tar.gz \
|
||||
&& mv git-cliff-${GIT_CLIFF_VERSION}/git-cliff /usr/local/bin/git-cliff \
|
||||
&& rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ARG GH_VERSION=2.82.1
|
||||
ARG GH_VERSION=2.83.2
|
||||
RUN wget --progress=dot:giga "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${TARGETARCH}.tar.gz" \
|
||||
&& tar xf gh_${GH_VERSION}_linux_${TARGETARCH}.tar.gz \
|
||||
&& mv gh_${GH_VERSION}_linux_${TARGETARCH}/bin/gh /usr/local/bin/gh \
|
||||
@@ -92,7 +100,7 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG GDB_VERSION=16.3
|
||||
ARG GDB_VERSION=17.1
|
||||
RUN wget --progress=dot:giga "https://sourceware.org/pub/gdb/releases/gdb-${GDB_VERSION}.tar.gz" \
|
||||
&& tar xf "gdb-${GDB_VERSION}.tar.gz" \
|
||||
&& cd "gdb-${GDB_VERSION}" \
|
||||
|
||||
@@ -97,30 +97,14 @@ Now you should be able to download the prebuilt dependencies (including `xrpl` p
|
||||
|
||||
#### Conan lockfile
|
||||
|
||||
To achieve reproducible dependencies, we use [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html).
|
||||
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html).
|
||||
|
||||
The `conan.lock` file in the repository contains a "snapshot" of the current dependencies.
|
||||
It is implicitly used when running `conan` commands, you don't need to specify it.
|
||||
|
||||
You have to update this file every time you add a new dependency or change a revision or version of an existing dependency.
|
||||
|
||||
> [!NOTE]
|
||||
> Conan uses local cache by default when creating a lockfile.
|
||||
>
|
||||
> To ensure, that lockfile creation works the same way on all developer machines, you should clear the local cache before creating a new lockfile.
|
||||
|
||||
To create a new lockfile, run the following commands in the repository root:
|
||||
|
||||
```bash
|
||||
conan remove '*' --confirm
|
||||
rm conan.lock
|
||||
# This ensure that xrplf remote is the first to be consulted
|
||||
conan remote add --force --index 0 xrplf https://conan.ripplex.io
|
||||
conan lock create .
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If some dependencies are exclusive for some OS, you may need to run the last command for them adding `--profile:all <PROFILE>`.
|
||||
To update a lockfile, run from the repository root: `./.github/scripts/conan/regenerate_lockfile.sh`
|
||||
|
||||
## Building Clio
|
||||
|
||||
@@ -191,7 +175,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.
|
||||
|
||||
```sh
|
||||
docker run -it ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
|
||||
docker run -it ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
git clone https://github.com/XRPLF/clio
|
||||
cd clio
|
||||
```
|
||||
|
||||
@@ -457,6 +457,14 @@ This document provides a list of all available Clio configuration properties in
|
||||
- **Constraints**: None
|
||||
- **Description**: Max allowed difference between the latest sequence in DB and in cache file. If the cache file is too old (contains too low latest sequence) Clio will reject using it.
|
||||
|
||||
### cache.file.async_save
|
||||
|
||||
- **Required**: True
|
||||
- **Type**: boolean
|
||||
- **Default value**: `False`
|
||||
- **Constraints**: None
|
||||
- **Description**: When false, Clio waits for cache saving to finish before shutting down. When true, cache saving runs in parallel with other shutdown operations.
|
||||
|
||||
### log.channels.[].channel
|
||||
|
||||
- **Required**: False
|
||||
|
||||
@@ -93,3 +93,42 @@ To completely disable Prometheus metrics add `"prometheus": { "enabled": false }
|
||||
It is important to know that Clio responds to Prometheus request only if they are admin requests. If you are using the admin password feature, the same password should be provided in the Authorization header of Prometheus requests.
|
||||
|
||||
You can find an example Docker Compose file, with Prometheus and Grafana configs, in [examples/infrastructure](../docs/examples/infrastructure/).
|
||||
|
||||
## Ledger cache file
|
||||
|
||||
Since version 2.7.0, Clio supports saving the ledger cache to a local file on shutdown and loading it on startup. This feature is disabled by default but can significantly improve restart times.
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Faster startup**: Loading cache from a file takes less than a minute, compared to 40-90 minutes on Mainnet when loading from the database.
|
||||
- **Reduced database load**: Clio doesn't put extra load on the database when starting with a cache file.
|
||||
- **Improved availability**: Faster restart times mean less downtime during maintenance or updates.
|
||||
|
||||
> [!NOTE]
|
||||
> This feature only works when Clio is restarted. When starting Clio for the first time, the cache must be loaded from `rippled` or the database as usual.
|
||||
|
||||
### Configuration
|
||||
|
||||
To enable the ledger cache file feature, specify the [`cache.file.path`](./config-description.md#cachefilepath) option in your `config.json`:
|
||||
|
||||
```json
|
||||
"cache": {
|
||||
"file": {
|
||||
"path": "/path/to/cache/file"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can optionally configure additional settings such as [`cache.file.max_sequence_age`](./config-description.md#cachefilemax_sequence_age) and [`cache.file.async_save`](./config-description.md#cachefileasync_save) to fine-tune the behavior. For a complete list of available options and their default values, see the [Configuration Description](./config-description.md#cachefilepath) documentation.
|
||||
|
||||
### How it works
|
||||
|
||||
1. **On shutdown**: Clio saves the current ledger cache to the specified file path. The file includes a hash for integrity verification.
|
||||
2. **On startup**: Clio checks if a cache file exists at the configured path. If the file exists, Clio will:
|
||||
- Verify the file's integrity to ensure it is complete and not corrupted.
|
||||
- Compare the latest ledger sequence in the cache file with the latest sequence in the database.
|
||||
- Use the cache file only if the difference is less than [`cache.file.max_sequence_age`](./config-description.md#cachefilemax_sequence_age).
|
||||
- If validation fails or the cache is too old, Clio will fall back to loading from the database.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The cache file path should point to a location with sufficient disk space. On typical deployments, the cache file size can be several gigabytes.
|
||||
|
||||
@@ -45,7 +45,7 @@ if [[ "1.14.0" > "$version" ]]; then
|
||||
|
||||
ERROR
|
||||
-----------------------------------------------------------------------------
|
||||
A minimum of version 1.14 of `which doxygen` is required.
|
||||
A minimum of version 1.14 of $(which doxygen) is required.
|
||||
Your version is $version. Please upgrade it.
|
||||
|
||||
Your changes may fail CI checks.
|
||||
@@ -55,26 +55,26 @@ EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p ${DOCDIR} > /dev/null 2>&1
|
||||
pushd ${DOCDIR} > /dev/null 2>&1
|
||||
mkdir -p ${DOCDIR} >/dev/null 2>&1
|
||||
pushd ${DOCDIR} >/dev/null 2>&1
|
||||
|
||||
cat ${ROOT}/docs/Doxyfile | \
|
||||
sed \
|
||||
-e "s/\${LINT}/YES/" \
|
||||
-e "s/\${WARN_AS_ERROR}/NO/" \
|
||||
-e "s!\${SOURCE}!${ROOT}!" \
|
||||
-e "s/\${USE_DOT}/NO/" \
|
||||
-e "s/\${EXCLUDES}/impl/" \
|
||||
| ${DOXYGEN} - 2> ${TMPFILE} 1> /dev/null
|
||||
cat ${ROOT}/docs/Doxyfile |
|
||||
sed \
|
||||
-e "s/\${LINT}/YES/" \
|
||||
-e "s/\${WARN_AS_ERROR}/NO/" \
|
||||
-e "s!\${SOURCE}!${ROOT}!" \
|
||||
-e "s/\${USE_DOT}/NO/" \
|
||||
-e "s/\${EXCLUDES}/impl/" |
|
||||
${DOXYGEN} - 2>${TMPFILE} 1>/dev/null
|
||||
|
||||
# We don't want to check for default values and typedefs as well as for member variables
|
||||
OUT=$(cat ${TMPFILE} \
|
||||
| grep -v "=default" \
|
||||
| grep -v "\(variable\)" \
|
||||
| grep -v "\(typedef\)")
|
||||
OUT=$(cat ${TMPFILE} |
|
||||
grep -v "=default" |
|
||||
grep -v "\(variable\)" |
|
||||
grep -v "\(typedef\)")
|
||||
|
||||
rm -rf ${TMPFILE} > /dev/null 2>&1
|
||||
popd > /dev/null 2>&1
|
||||
rm -rf ${TMPFILE} >/dev/null 2>&1
|
||||
popd >/dev/null 2>&1
|
||||
|
||||
if [[ ! -z "$OUT" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
@@ -23,10 +23,10 @@ fix_includes() {
|
||||
file_path_fixed="${file_path}.tmp.fixed"
|
||||
|
||||
# Make all includes to be <...> style
|
||||
sed -E 's|#include "(.*)"|#include <\1>|g' "$file_path" > "$file_path_all_global"
|
||||
sed -E 's|#include "(.*)"|#include <\1>|g' "$file_path" >"$file_path_all_global"
|
||||
|
||||
# Make local includes to be "..." style
|
||||
sed -E "s|#include <(($main_src_dirs)/.*)>|#include \"\1\"|g" "$file_path_all_global" > "$file_path_fixed"
|
||||
sed -E "s|#include <(($main_src_dirs)/.*)>|#include \"\1\"|g" "$file_path_all_global" >"$file_path_fixed"
|
||||
rm "$file_path_all_global"
|
||||
|
||||
# Check if the temporary file is different from the original file
|
||||
|
||||
@@ -4,7 +4,6 @@ import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PATTERN = r'R"JSON\((.*?)\)JSON"'
|
||||
|
||||
|
||||
@@ -40,6 +39,7 @@ def fix_colon_spacing(cpp_content: str) -> str:
|
||||
raw_json = match.group(1)
|
||||
raw_json = re.sub(r'":\n\s*(\[|\{)', r'": \1', raw_json)
|
||||
return f'R"JSON({raw_json})JSON"'
|
||||
|
||||
return re.sub(PATTERN, replace_json, cpp_content, flags=re.DOTALL)
|
||||
|
||||
|
||||
@@ -49,12 +49,12 @@ def fix_indentation(cpp_content: str) -> str:
|
||||
|
||||
lines = cpp_content.splitlines()
|
||||
|
||||
ends_with_newline = cpp_content.endswith('\n')
|
||||
ends_with_newline = cpp_content.endswith("\n")
|
||||
|
||||
def find_indentation(line: str) -> int:
|
||||
return len(line) - len(line.lstrip())
|
||||
|
||||
for (line_num, (line, next_line)) in enumerate(zip(lines[:-1], lines[1:])):
|
||||
for line_num, (line, next_line) in enumerate(zip(lines[:-1], lines[1:])):
|
||||
if "JSON(" in line and ")JSON" not in line:
|
||||
indent = find_indentation(line)
|
||||
next_indent = find_indentation(next_line)
|
||||
@@ -69,7 +69,11 @@ def fix_indentation(cpp_content: str) -> str:
|
||||
if ")JSON" in lines[i]:
|
||||
lines[i] = " " * indent + lines[i].lstrip()
|
||||
break
|
||||
lines[i] = lines[i][by_how_much:] if by_how_much > 0 else " " * (-by_how_much) + lines[i]
|
||||
lines[i] = (
|
||||
lines[i][by_how_much:]
|
||||
if by_how_much > 0
|
||||
else " " * (-by_how_much) + lines[i]
|
||||
)
|
||||
|
||||
result = "\n".join(lines)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
set -e -o pipefail
|
||||
|
||||
if ! command -v gofmt &> /dev/null ; then
|
||||
if ! command -v gofmt &>/dev/null; then
|
||||
echo "gofmt not installed or available in the PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# git for-each-ref refs/tags # see which tags are annotated and which are lightweight. Annotated tags are "tag" objects.
|
||||
# # Set these so your commits and tags are always signed
|
||||
@@ -7,7 +6,7 @@
|
||||
# git config tag.gpgsign true
|
||||
|
||||
verify_commit_signed() {
|
||||
if git verify-commit HEAD &> /dev/null; then
|
||||
if git verify-commit HEAD &>/dev/null; then
|
||||
:
|
||||
# echo "HEAD commit seems signed..."
|
||||
else
|
||||
@@ -17,7 +16,7 @@ verify_commit_signed() {
|
||||
}
|
||||
|
||||
verify_tag() {
|
||||
if git describe --exact-match --tags HEAD &> /dev/null; then
|
||||
if git describe --exact-match --tags HEAD &>/dev/null; then
|
||||
: # You might be ok to push
|
||||
# echo "Tag is annotated."
|
||||
return 0
|
||||
@@ -28,7 +27,7 @@ verify_tag() {
|
||||
}
|
||||
|
||||
verify_tag_signed() {
|
||||
if git verify-tag "$version" &> /dev/null ; then
|
||||
if git verify-tag "$version" &>/dev/null; then
|
||||
: # ok, I guess we'll let you push
|
||||
# echo "Tag appears signed"
|
||||
return 0
|
||||
@@ -40,11 +39,11 @@ verify_tag_signed() {
|
||||
}
|
||||
|
||||
# Check some things if we're pushing a branch called "release/"
|
||||
if echo "$PRE_COMMIT_REMOTE_BRANCH" | grep ^refs\/heads\/release\/ &> /dev/null ; then
|
||||
if echo "$PRE_COMMIT_REMOTE_BRANCH" | grep ^refs\/heads\/release\/ &>/dev/null; then
|
||||
version=$(git tag --points-at HEAD)
|
||||
echo "Looks like you're trying to push a $version release..."
|
||||
echo "Making sure you've signed and tagged it."
|
||||
if verify_commit_signed && verify_tag && verify_tag_signed ; then
|
||||
if verify_commit_signed && verify_tag && verify_tag_signed; then
|
||||
: # Ok, I guess you can push
|
||||
else
|
||||
exit 1
|
||||
|
||||
@@ -77,7 +77,10 @@ CliArgs::parse(int argc, char const* argv[])
|
||||
}
|
||||
|
||||
if (parsed.contains("version")) {
|
||||
std::cout << util::build::getClioFullVersionString() << '\n';
|
||||
std::cout << util::build::getClioFullVersionString() << '\n'
|
||||
<< "Git commit hash: " << util::build::getGitCommitHash() << '\n'
|
||||
<< "Git build branch: " << util::build::getGitBuildBranch() << '\n'
|
||||
<< "Build date: " << util::build::getBuildDate() << '\n';
|
||||
return Action{Action::Exit{EXIT_SUCCESS}};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "etl/ETLService.hpp"
|
||||
#include "etl/LoadBalancer.hpp"
|
||||
#include "etl/NetworkValidatedLedgers.hpp"
|
||||
#include "etl/SystemState.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
#include "feed/SubscriptionManager.hpp"
|
||||
#include "migration/MigrationInspectorFactory.hpp"
|
||||
#include "rpc/Counters.hpp"
|
||||
@@ -91,6 +93,7 @@ ClioApplication::ClioApplication(util::config::ClioConfigDefinition const& confi
|
||||
{
|
||||
LOG(util::LogService::info()) << "Clio version: " << util::build::getClioFullVersionString();
|
||||
signalsHandler_.subscribeToStop([this]() { appStopper_.stop(); });
|
||||
appStopper_.setOnComplete([this]() { signalsHandler_.notifyGracefulShutdownComplete(); });
|
||||
}
|
||||
|
||||
int
|
||||
@@ -120,7 +123,11 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
// Interface to the database
|
||||
auto backend = data::makeBackend(config_, cache);
|
||||
|
||||
cluster::ClusterCommunicationService clusterCommunicationService{backend};
|
||||
auto systemState = etl::SystemState::makeSystemState(config_);
|
||||
|
||||
cluster::ClusterCommunicationService clusterCommunicationService{
|
||||
backend, std::make_unique<etl::WriterState>(systemState)
|
||||
};
|
||||
clusterCommunicationService.run();
|
||||
|
||||
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
|
||||
@@ -150,7 +157,9 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
);
|
||||
|
||||
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only publishes
|
||||
auto etl = etl::ETLService::makeETLService(config_, ctx, backend, subscriptions, balancer, ledgers);
|
||||
auto etl = etl::ETLService::makeETLService(
|
||||
config_, std::move(systemState), ctx, backend, subscriptions, balancer, ledgers
|
||||
);
|
||||
|
||||
auto workQueue = rpc::WorkQueue::makeWorkQueue(config_);
|
||||
auto counters = rpc::Counters::makeCounters(workQueue);
|
||||
@@ -182,7 +191,7 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
httpServer->onGet("/metrics", MetricsHandler{adminVerifier});
|
||||
httpServer->onGet("/metrics", MetricsHandler{adminVerifier, workQueue});
|
||||
httpServer->onGet("/health", HealthCheckHandler{});
|
||||
httpServer->onGet("/cache_state", CacheStateHandler{cache});
|
||||
auto requestHandler = RequestHandler{adminVerifier, handler};
|
||||
@@ -196,7 +205,16 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
}
|
||||
|
||||
appStopper_.setOnStop(
|
||||
Stopper::makeOnStopCallback(httpServer.value(), *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
||||
Stopper::makeOnStopCallback(
|
||||
httpServer.value(),
|
||||
*balancer,
|
||||
*etl,
|
||||
*subscriptions,
|
||||
*backend,
|
||||
cacheSaver,
|
||||
clusterCommunicationService,
|
||||
ioc
|
||||
)
|
||||
);
|
||||
|
||||
// Blocks until stopped.
|
||||
@@ -212,7 +230,9 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
|
||||
auto const httpServer = web::makeHttpServer(config_, ioc, dosGuard, handler, cache);
|
||||
appStopper_.setOnStop(
|
||||
Stopper::makeOnStopCallback(*httpServer, *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
||||
Stopper::makeOnStopCallback(
|
||||
*httpServer, *balancer, *etl, *subscriptions, *backend, cacheSaver, clusterCommunicationService, ioc
|
||||
)
|
||||
);
|
||||
|
||||
// Blocks until stopped.
|
||||
|
||||
@@ -38,7 +38,18 @@ Stopper::~Stopper()
|
||||
void
|
||||
Stopper::setOnStop(std::function<void(boost::asio::yield_context)> cb)
|
||||
{
|
||||
util::spawn(ctx_, std::move(cb));
|
||||
util::spawn(ctx_, [this, cb = std::move(cb)](auto yield) {
|
||||
cb(yield);
|
||||
|
||||
if (onCompleteCallback_)
|
||||
onCompleteCallback_();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
Stopper::setOnComplete(std::function<void()> cb)
|
||||
{
|
||||
onCompleteCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cluster/Concepts.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "data/LedgerCacheSaver.hpp"
|
||||
#include "etl/ETLServiceInterface.hpp"
|
||||
@@ -43,6 +44,7 @@ namespace app {
|
||||
class Stopper {
|
||||
boost::asio::io_context ctx_;
|
||||
std::thread worker_;
|
||||
std::function<void()> onCompleteCallback_;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -58,6 +60,14 @@ public:
|
||||
void
|
||||
setOnStop(std::function<void(boost::asio::yield_context)> cb);
|
||||
|
||||
/**
|
||||
* @brief Set the callback to be called when graceful shutdown completes.
|
||||
*
|
||||
* @param cb The callback to be called when shutdown completes.
|
||||
*/
|
||||
void
|
||||
setOnComplete(std::function<void()> cb);
|
||||
|
||||
/**
|
||||
* @brief Stop the application and run the shutdown tasks.
|
||||
*/
|
||||
@@ -73,10 +83,14 @@ public:
|
||||
* @param subscriptions The subscription manager to stop.
|
||||
* @param backend The backend to stop.
|
||||
* @param cacheSaver The ledger cache saver
|
||||
* @param clusterCommunicationService The cluster communication service to stop.
|
||||
* @param ioc The io_context to stop.
|
||||
* @return The callback to be called on application stop.
|
||||
*/
|
||||
template <web::SomeServer ServerType, data::SomeLedgerCacheSaver LedgerCacheSaverType>
|
||||
template <
|
||||
web::SomeServer ServerType,
|
||||
data::SomeLedgerCacheSaver LedgerCacheSaverType,
|
||||
cluster::SomeClusterCommunicationService ClusterCommunicationServiceType>
|
||||
static std::function<void(boost::asio::yield_context)>
|
||||
makeOnStopCallback(
|
||||
ServerType& server,
|
||||
@@ -85,6 +99,7 @@ public:
|
||||
feed::SubscriptionManagerInterface& subscriptions,
|
||||
data::BackendInterface& backend,
|
||||
LedgerCacheSaverType& cacheSaver,
|
||||
ClusterCommunicationServiceType& clusterCommunicationService,
|
||||
boost::asio::io_context& ioc
|
||||
)
|
||||
{
|
||||
@@ -102,6 +117,8 @@ public:
|
||||
});
|
||||
coroutineGroup.asyncWait(yield);
|
||||
|
||||
clusterCommunicationService.stop();
|
||||
|
||||
etl.stop();
|
||||
LOG(util::LogService::info()) << "ETL stopped";
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
|
||||
#include "app/WebHandlers.hpp"
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/WorkQueue.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/CoroutineGroup.hpp"
|
||||
#include "util/prometheus/Http.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
@@ -31,6 +34,7 @@
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -76,8 +80,8 @@ DisconnectHook::operator()(web::ng::Connection const& connection)
|
||||
dosguard_.get().decrement(connection.ip());
|
||||
}
|
||||
|
||||
MetricsHandler::MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier)
|
||||
: adminVerifier_{std::move(adminVerifier)}
|
||||
MetricsHandler::MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, rpc::WorkQueue& workQueue)
|
||||
: adminVerifier_{std::move(adminVerifier)}, workQueue_{std::ref(workQueue)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -86,19 +90,45 @@ MetricsHandler::operator()(
|
||||
web::ng::Request const& request,
|
||||
web::ng::ConnectionMetadata& connectionMetadata,
|
||||
web::SubscriptionContextPtr,
|
||||
boost::asio::yield_context
|
||||
boost::asio::yield_context yield
|
||||
)
|
||||
{
|
||||
auto const maybeHttpRequest = request.asHttpRequest();
|
||||
ASSERT(maybeHttpRequest.has_value(), "Got not a http request in Get");
|
||||
auto const& httpRequest = maybeHttpRequest->get();
|
||||
std::optional<web::ng::Response> response;
|
||||
util::CoroutineGroup coroutineGroup{yield, 1};
|
||||
auto const onTaskComplete = coroutineGroup.registerForeign(yield);
|
||||
ASSERT(onTaskComplete.has_value(), "Coroutine group can't be full");
|
||||
|
||||
// FIXME(#1702): Using veb server thread to handle prometheus request. Better to post on work queue.
|
||||
auto maybeResponse = util::prometheus::handlePrometheusRequest(
|
||||
httpRequest, adminVerifier_->isAdmin(httpRequest, connectionMetadata.ip())
|
||||
bool const postSuccessful = workQueue_.get().postCoro(
|
||||
[this, &request, &response, &onTaskComplete = onTaskComplete.value(), &connectionMetadata](
|
||||
boost::asio::yield_context
|
||||
) mutable {
|
||||
auto const maybeHttpRequest = request.asHttpRequest();
|
||||
ASSERT(maybeHttpRequest.has_value(), "Got not a http request in Get");
|
||||
auto const& httpRequest = maybeHttpRequest->get();
|
||||
|
||||
auto maybeResponse = util::prometheus::handlePrometheusRequest(
|
||||
httpRequest, adminVerifier_->isAdmin(httpRequest, connectionMetadata.ip())
|
||||
);
|
||||
ASSERT(maybeResponse.has_value(), "Got unexpected request for Prometheus");
|
||||
response = web::ng::Response{std::move(maybeResponse).value(), request};
|
||||
// notify the coroutine group that the foreign task is done
|
||||
onTaskComplete();
|
||||
},
|
||||
/* isWhiteListed= */ true,
|
||||
rpc::WorkQueue::Priority::High
|
||||
);
|
||||
ASSERT(maybeResponse.has_value(), "Got unexpected request for Prometheus");
|
||||
return web::ng::Response{std::move(maybeResponse).value(), request};
|
||||
|
||||
if (!postSuccessful) {
|
||||
return web::ng::Response{
|
||||
boost::beast::http::status::too_many_requests, rpc::makeError(rpc::RippledError::rpcTOO_BUSY), request
|
||||
};
|
||||
}
|
||||
|
||||
// Put the coroutine to sleep until the foreign task is done
|
||||
coroutineGroup.asyncWait(yield);
|
||||
ASSERT(response.has_value(), "Woke up coroutine without setting response");
|
||||
|
||||
return std::move(response).value();
|
||||
}
|
||||
|
||||
web::ng::Response
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/LedgerCacheInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/WorkQueue.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
@@ -119,20 +120,23 @@ public:
|
||||
*/
|
||||
class MetricsHandler {
|
||||
std::shared_ptr<web::AdminVerificationStrategy> adminVerifier_;
|
||||
std::reference_wrapper<rpc::WorkQueue> workQueue_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new MetricsHandler object
|
||||
*
|
||||
* @param adminVerifier The AdminVerificationStrategy to use for verifying the connection for admin access.
|
||||
* @param workQueue The WorkQueue to use for handling the request.
|
||||
*/
|
||||
MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier);
|
||||
MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, rpc::WorkQueue& workQueue);
|
||||
|
||||
/**
|
||||
* @brief The call of the function object.
|
||||
*
|
||||
* @param request The request to handle.
|
||||
* @param connectionMetadata The connection metadata.
|
||||
* @param yield The yield context.
|
||||
* @return The response to the request.
|
||||
*/
|
||||
web::ng::Response
|
||||
@@ -140,7 +144,7 @@ public:
|
||||
web::ng::Request const& request,
|
||||
web::ng::ConnectionMetadata& connectionMetadata,
|
||||
web::SubscriptionContextPtr,
|
||||
boost::asio::yield_context
|
||||
boost::asio::yield_context yield
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
134
src/cluster/Backend.cpp
Normal file
134
src/cluster/Backend.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "cluster/Backend.hpp"
|
||||
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
Backend::Backend(
|
||||
boost::asio::thread_pool& ctx,
|
||||
std::shared_ptr<data::BackendInterface> backend,
|
||||
std::unique_ptr<etl::WriterStateInterface const> writerState,
|
||||
std::chrono::steady_clock::duration readInterval,
|
||||
std::chrono::steady_clock::duration writeInterval
|
||||
)
|
||||
: backend_(std::move(backend))
|
||||
, writerState_(std::move(writerState))
|
||||
, readerTask_(readInterval, ctx)
|
||||
, writerTask_(writeInterval, ctx)
|
||||
, selfUuid_(std::make_shared<boost::uuids::uuid>(boost::uuids::random_generator{}()))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Backend::run()
|
||||
{
|
||||
readerTask_.run([this](boost::asio::yield_context yield) {
|
||||
auto clusterData = doRead(yield);
|
||||
onNewState_(selfUuid_, std::make_shared<ClusterData>(std::move(clusterData)));
|
||||
});
|
||||
|
||||
writerTask_.run([this]() { doWrite(); });
|
||||
}
|
||||
|
||||
Backend::~Backend()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void
|
||||
Backend::stop()
|
||||
{
|
||||
readerTask_.stop();
|
||||
writerTask_.stop();
|
||||
}
|
||||
|
||||
ClioNode::CUuid
|
||||
Backend::selfId() const
|
||||
{
|
||||
return selfUuid_;
|
||||
}
|
||||
|
||||
Backend::ClusterData
|
||||
Backend::doRead(boost::asio::yield_context yield)
|
||||
{
|
||||
BackendInterface::ClioNodesDataFetchResult expectedResult;
|
||||
try {
|
||||
expectedResult = backend_->fetchClioNodesData(yield);
|
||||
} catch (...) {
|
||||
expectedResult = std::unexpected{"Failed to fetch Clio nodes data"};
|
||||
}
|
||||
|
||||
if (!expectedResult.has_value()) {
|
||||
return std::unexpected{std::move(expectedResult).error()};
|
||||
}
|
||||
|
||||
std::vector<ClioNode> otherNodesData;
|
||||
for (auto const& [uuid, nodeDataStr] : expectedResult.value()) {
|
||||
if (uuid == *selfUuid_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::system::error_code errorCode;
|
||||
auto const json = boost::json::parse(nodeDataStr, errorCode);
|
||||
if (errorCode.failed()) {
|
||||
return std::unexpected{fmt::format("Error parsing json from DB: {}", nodeDataStr)};
|
||||
}
|
||||
|
||||
auto expectedNodeData = boost::json::try_value_to<ClioNode>(json);
|
||||
if (expectedNodeData.has_error()) {
|
||||
return std::unexpected{fmt::format("Error converting json to ClioNode: {}", nodeDataStr)};
|
||||
}
|
||||
*expectedNodeData->uuid = uuid;
|
||||
otherNodesData.push_back(std::move(expectedNodeData).value());
|
||||
}
|
||||
otherNodesData.push_back(ClioNode::from(selfUuid_, *writerState_));
|
||||
return otherNodesData;
|
||||
}
|
||||
|
||||
void
|
||||
Backend::doWrite()
|
||||
{
|
||||
auto const selfData = ClioNode::from(selfUuid_, *writerState_);
|
||||
boost::json::value jsonValue{};
|
||||
boost::json::value_from(selfData, jsonValue);
|
||||
backend_->writeNodeMessage(*selfData.uuid, boost::json::serialize(jsonValue.as_object()));
|
||||
}
|
||||
|
||||
} // namespace cluster
|
||||
147
src/cluster/Backend.hpp
Normal file
147
src/cluster/Backend.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "cluster/ClioNode.hpp"
|
||||
#include "cluster/impl/RepeatedTask.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/execution_context.hpp>
|
||||
#include <boost/asio/executor.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/signals2/connection.hpp>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <boost/signals2/variadic_signal.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Backend communication handler for cluster state synchronization.
|
||||
*
|
||||
* This class manages reading and writing cluster state information to/from the backend database.
|
||||
* It periodically reads the state of other nodes in the cluster and writes the current node's state,
|
||||
* enabling cluster-wide coordination and awareness.
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
/** @brief Type representing cluster data result - either a vector of nodes or an error message */
|
||||
using ClusterData = std::expected<std::vector<ClioNode>, std::string>;
|
||||
|
||||
private:
|
||||
util::Logger log_{"ClusterCommunication"};
|
||||
|
||||
std::shared_ptr<data::BackendInterface> backend_;
|
||||
std::unique_ptr<etl::WriterStateInterface const> writerState_;
|
||||
|
||||
impl::RepeatedTask<boost::asio::thread_pool> readerTask_;
|
||||
impl::RepeatedTask<boost::asio::thread_pool> writerTask_;
|
||||
|
||||
ClioNode::Uuid selfUuid_;
|
||||
|
||||
boost::signals2::signal<void(ClioNode::CUuid, std::shared_ptr<ClusterData const>)> onNewState_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a Backend communication handler.
|
||||
*
|
||||
* @param ctx The execution context for asynchronous operations
|
||||
* @param backend Interface to the backend database
|
||||
* @param writerState State indicating whether this node is writing to the database
|
||||
* @param readInterval How often to read cluster state from the backend
|
||||
* @param writeInterval How often to write this node's state to the backend
|
||||
*/
|
||||
Backend(
|
||||
boost::asio::thread_pool& ctx,
|
||||
std::shared_ptr<data::BackendInterface> backend,
|
||||
std::unique_ptr<etl::WriterStateInterface const> writerState,
|
||||
std::chrono::steady_clock::duration readInterval,
|
||||
std::chrono::steady_clock::duration writeInterval
|
||||
);
|
||||
|
||||
~Backend();
|
||||
|
||||
Backend(Backend&&) = delete;
|
||||
Backend&
|
||||
operator=(Backend&&) = delete;
|
||||
Backend(Backend const&) = delete;
|
||||
Backend&
|
||||
operator=(Backend const&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Start the backend read and write tasks.
|
||||
*
|
||||
* Begins periodic reading of cluster state from the backend and writing of this node's state.
|
||||
*/
|
||||
void
|
||||
run();
|
||||
|
||||
/**
|
||||
* @brief Stop the backend read and write tasks.
|
||||
*
|
||||
* Stops all periodic tasks and waits for them to complete.
|
||||
*/
|
||||
void
|
||||
stop();
|
||||
|
||||
/**
|
||||
* @brief Subscribe to new cluster state notifications.
|
||||
*
|
||||
* @tparam S Callable type accepting (ClioNode::cUUID, ClusterData)
|
||||
* @param s Subscriber callback to be invoked when new cluster state is available
|
||||
* @return A connection object that can be used to unsubscribe
|
||||
*/
|
||||
template <typename S>
|
||||
requires std::invocable<S, ClioNode::CUuid, std::shared_ptr<ClusterData const>>
|
||||
boost::signals2::connection
|
||||
subscribeToNewState(S&& s)
|
||||
{
|
||||
return onNewState_.connect(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the UUID of this node in the cluster.
|
||||
*
|
||||
* @return The UUID of this node.
|
||||
*/
|
||||
ClioNode::CUuid
|
||||
selfId() const;
|
||||
|
||||
private:
|
||||
ClusterData
|
||||
doRead(boost::asio::yield_context yield);
|
||||
|
||||
void
|
||||
doWrite();
|
||||
};
|
||||
|
||||
} // namespace cluster
|
||||
@@ -1,5 +1,7 @@
|
||||
add_library(clio_cluster)
|
||||
|
||||
target_sources(clio_cluster PRIVATE ClioNode.cpp ClusterCommunicationService.cpp)
|
||||
target_sources(
|
||||
clio_cluster PRIVATE Backend.cpp ClioNode.cpp ClusterCommunicationService.cpp Metrics.cpp WriterDecider.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(clio_cluster PRIVATE clio_util clio_data)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "cluster/ClioNode.hpp"
|
||||
|
||||
#include "etl/WriterState.hpp"
|
||||
#include "util/TimeUtils.hpp"
|
||||
|
||||
#include <boost/json/conversion.hpp>
|
||||
@@ -26,39 +27,72 @@
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Fields {
|
||||
struct JsonFields {
|
||||
static constexpr std::string_view const kUPDATE_TIME = "update_time";
|
||||
static constexpr std::string_view const kDB_ROLE = "db_role";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ClioNode
|
||||
ClioNode::from(ClioNode::Uuid uuid, etl::WriterStateInterface const& writerState)
|
||||
{
|
||||
auto const dbRole = [&writerState]() {
|
||||
if (writerState.isReadOnly()) {
|
||||
return ClioNode::DbRole::ReadOnly;
|
||||
}
|
||||
if (writerState.isFallback()) {
|
||||
return ClioNode::DbRole::Fallback;
|
||||
}
|
||||
if (writerState.isLoadingCache()) {
|
||||
return ClioNode::DbRole::LoadingCache;
|
||||
}
|
||||
|
||||
return writerState.isWriting() ? ClioNode::DbRole::Writer : ClioNode::DbRole::NotWriter;
|
||||
}();
|
||||
return ClioNode{.uuid = std::move(uuid), .updateTime = std::chrono::system_clock::now(), .dbRole = dbRole};
|
||||
}
|
||||
|
||||
void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ClioNode const& node)
|
||||
{
|
||||
jv = {
|
||||
{Fields::kUPDATE_TIME, util::systemTpToUtcStr(node.updateTime, ClioNode::kTIME_FORMAT)},
|
||||
{JsonFields::kUPDATE_TIME, util::systemTpToUtcStr(node.updateTime, ClioNode::kTIME_FORMAT)},
|
||||
{JsonFields::kDB_ROLE, static_cast<int64_t>(node.dbRole)}
|
||||
};
|
||||
}
|
||||
|
||||
ClioNode
|
||||
tag_invoke(boost::json::value_to_tag<ClioNode>, boost::json::value const& jv)
|
||||
{
|
||||
auto const& updateTimeStr = jv.as_object().at(Fields::kUPDATE_TIME).as_string();
|
||||
auto const& updateTimeStr = jv.as_object().at(JsonFields::kUPDATE_TIME).as_string();
|
||||
auto const updateTime = util::systemTpFromUtcStr(std::string(updateTimeStr), ClioNode::kTIME_FORMAT);
|
||||
if (!updateTime.has_value()) {
|
||||
throw std::runtime_error("Failed to parse update time");
|
||||
}
|
||||
|
||||
return ClioNode{.uuid = std::make_shared<boost::uuids::uuid>(), .updateTime = updateTime.value()};
|
||||
auto const dbRoleValue = jv.as_object().at(JsonFields::kDB_ROLE).as_int64();
|
||||
if (dbRoleValue > static_cast<int64_t>(ClioNode::DbRole::MAX))
|
||||
throw std::runtime_error("Invalid db_role value");
|
||||
|
||||
return ClioNode{
|
||||
// Json data doesn't contain uuid so leaving it empty here. It will be filled outside of this parsing
|
||||
.uuid = std::make_shared<boost::uuids::uuid>(),
|
||||
.updateTime = updateTime.value(),
|
||||
.dbRole = static_cast<ClioNode::DbRole>(dbRoleValue)
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace cluster
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "etl/WriterState.hpp"
|
||||
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
@@ -37,16 +39,37 @@ struct ClioNode {
|
||||
*/
|
||||
static constexpr char const* kTIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ";
|
||||
|
||||
// enum class WriterRole {
|
||||
// ReadOnly,
|
||||
// NotWriter,
|
||||
// Writer
|
||||
// };
|
||||
/**
|
||||
* @brief Database role of a node in the cluster.
|
||||
*
|
||||
* Roles are used to coordinate which node writes to the database:
|
||||
* - ReadOnly: Node is configured to never write (strict read-only mode)
|
||||
* - NotWriter: Node can write but is currently not the designated writer
|
||||
* - Writer: Node is actively writing to the database
|
||||
* - Fallback: Node is using the fallback writer decision mechanism
|
||||
*
|
||||
* When any node in the cluster is in Fallback mode, the entire cluster switches
|
||||
* from the cluster communication mechanism to the slower but more reliable
|
||||
* database-based conflict detection mechanism.
|
||||
*/
|
||||
enum class DbRole { ReadOnly = 0, LoadingCache = 1, NotWriter = 2, Writer = 3, Fallback = 4, MAX = 4 };
|
||||
|
||||
std::shared_ptr<boost::uuids::uuid> uuid; ///< The UUID of the node.
|
||||
using Uuid = std::shared_ptr<boost::uuids::uuid>;
|
||||
using CUuid = std::shared_ptr<boost::uuids::uuid const>;
|
||||
|
||||
Uuid uuid; ///< The UUID of the node.
|
||||
std::chrono::system_clock::time_point updateTime; ///< The time the data about the node was last updated.
|
||||
DbRole dbRole; ///< The database role of the node
|
||||
|
||||
// WriterRole writerRole;
|
||||
/**
|
||||
* @brief Create a ClioNode from writer state.
|
||||
*
|
||||
* @param uuid The UUID of the node
|
||||
* @param writerState The writer state to determine the node's database role
|
||||
* @return A ClioNode with the current time and role derived from writerState
|
||||
*/
|
||||
static ClioNode
|
||||
from(Uuid uuid, etl::WriterStateInterface const& writerState);
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -19,98 +19,37 @@
|
||||
|
||||
#include "cluster/ClusterCommunicationService.hpp"
|
||||
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/Spawn.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include "etl/WriterState.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <latch>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr auto kTOTAL_WORKERS = 2uz; // 1 reading and 1 writing worker (coroutines)
|
||||
} // namespace
|
||||
|
||||
namespace cluster {
|
||||
|
||||
ClusterCommunicationService::ClusterCommunicationService(
|
||||
std::shared_ptr<data::BackendInterface> backend,
|
||||
std::unique_ptr<etl::WriterStateInterface> writerState,
|
||||
std::chrono::steady_clock::duration readInterval,
|
||||
std::chrono::steady_clock::duration writeInterval
|
||||
)
|
||||
: backend_(std::move(backend))
|
||||
, readInterval_(readInterval)
|
||||
, writeInterval_(writeInterval)
|
||||
, finishedCountdown_(kTOTAL_WORKERS)
|
||||
, selfData_{ClioNode{
|
||||
.uuid = std::make_shared<boost::uuids::uuid>(boost::uuids::random_generator{}()),
|
||||
.updateTime = std::chrono::system_clock::time_point{}
|
||||
}}
|
||||
: backend_(ctx_, std::move(backend), writerState->clone(), readInterval, writeInterval)
|
||||
, writerDecider_(ctx_, std::move(writerState))
|
||||
{
|
||||
nodesInClusterMetric_.set(1); // The node always sees itself
|
||||
isHealthy_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
ClusterCommunicationService::run()
|
||||
{
|
||||
ASSERT(not running_ and not stopped_, "Can only be ran once");
|
||||
running_ = true;
|
||||
|
||||
util::spawn(strand_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::steady_timer timer(yield.get_executor());
|
||||
boost::system::error_code ec;
|
||||
|
||||
while (running_) {
|
||||
timer.expires_after(readInterval_);
|
||||
auto token = cancelSignal_.slot();
|
||||
timer.async_wait(boost::asio::bind_cancellation_slot(token, yield[ec]));
|
||||
|
||||
if (ec == boost::asio::error::operation_aborted or not running_)
|
||||
break;
|
||||
|
||||
doRead(yield);
|
||||
}
|
||||
|
||||
finishedCountdown_.count_down(1);
|
||||
backend_.subscribeToNewState([this](auto&&... args) {
|
||||
metrics_.onNewState(std::forward<decltype(args)>(args)...);
|
||||
});
|
||||
|
||||
util::spawn(strand_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::steady_timer timer(yield.get_executor());
|
||||
boost::system::error_code ec;
|
||||
|
||||
while (running_) {
|
||||
doWrite();
|
||||
timer.expires_after(writeInterval_);
|
||||
auto token = cancelSignal_.slot();
|
||||
timer.async_wait(boost::asio::bind_cancellation_slot(token, yield[ec]));
|
||||
|
||||
if (ec == boost::asio::error::operation_aborted or not running_)
|
||||
break;
|
||||
}
|
||||
|
||||
finishedCountdown_.count_down(1);
|
||||
backend_.subscribeToNewState([this](auto&&... args) {
|
||||
writerDecider_.onNewState(std::forward<decltype(args)>(args)...);
|
||||
});
|
||||
backend_.run();
|
||||
}
|
||||
|
||||
ClusterCommunicationService::~ClusterCommunicationService()
|
||||
@@ -121,107 +60,7 @@ ClusterCommunicationService::~ClusterCommunicationService()
|
||||
void
|
||||
ClusterCommunicationService::stop()
|
||||
{
|
||||
if (stopped_)
|
||||
return;
|
||||
|
||||
stopped_ = true;
|
||||
|
||||
// for ASAN to see through concurrency correctly we need to exit all coroutines before joining the ctx
|
||||
running_ = false;
|
||||
|
||||
// cancelSignal_ is not thread safe so we execute emit on the same strand
|
||||
boost::asio::spawn(
|
||||
strand_, [this](auto&&) { cancelSignal_.emit(boost::asio::cancellation_type::all); }, boost::asio::use_future
|
||||
)
|
||||
.wait();
|
||||
finishedCountdown_.wait();
|
||||
|
||||
ctx_.join();
|
||||
}
|
||||
|
||||
std::shared_ptr<boost::uuids::uuid>
|
||||
ClusterCommunicationService::selfUuid() const
|
||||
{
|
||||
// Uuid never changes so it is safe to copy it without using strand_
|
||||
return selfData_.uuid;
|
||||
}
|
||||
|
||||
ClioNode
|
||||
ClusterCommunicationService::selfData() const
|
||||
{
|
||||
ClioNode result{};
|
||||
util::spawn(strand_, [this, &result](boost::asio::yield_context) { result = selfData_; });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::expected<std::vector<ClioNode>, std::string>
|
||||
ClusterCommunicationService::clusterData() const
|
||||
{
|
||||
if (not isHealthy_) {
|
||||
return std::unexpected{"Service is not healthy"};
|
||||
}
|
||||
std::vector<ClioNode> result;
|
||||
util::spawn(strand_, [this, &result](boost::asio::yield_context) {
|
||||
result = otherNodesData_;
|
||||
result.push_back(selfData_);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
ClusterCommunicationService::doRead(boost::asio::yield_context yield)
|
||||
{
|
||||
otherNodesData_.clear();
|
||||
|
||||
BackendInterface::ClioNodesDataFetchResult expectedResult;
|
||||
try {
|
||||
expectedResult = backend_->fetchClioNodesData(yield);
|
||||
} catch (...) {
|
||||
expectedResult = std::unexpected{"Failed to fecth Clio nodes data"};
|
||||
}
|
||||
|
||||
if (!expectedResult.has_value()) {
|
||||
LOG(log_.error()) << "Failed to fetch nodes data";
|
||||
isHealthy_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new vector here to not have partially parsed data in otherNodesData_
|
||||
std::vector<ClioNode> otherNodesData;
|
||||
for (auto const& [uuid, nodeDataStr] : expectedResult.value()) {
|
||||
if (uuid == *selfData_.uuid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::system::error_code errorCode;
|
||||
auto const json = boost::json::parse(nodeDataStr, errorCode);
|
||||
if (errorCode.failed()) {
|
||||
LOG(log_.error()) << "Error parsing json from DB: " << nodeDataStr;
|
||||
isHealthy_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto expectedNodeData = boost::json::try_value_to<ClioNode>(json);
|
||||
if (expectedNodeData.has_error()) {
|
||||
LOG(log_.error()) << "Error converting json to ClioNode: " << json;
|
||||
isHealthy_ = false;
|
||||
return;
|
||||
}
|
||||
*expectedNodeData->uuid = uuid;
|
||||
otherNodesData.push_back(std::move(expectedNodeData).value());
|
||||
}
|
||||
otherNodesData_ = std::move(otherNodesData);
|
||||
nodesInClusterMetric_.set(otherNodesData_.size() + 1);
|
||||
isHealthy_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
ClusterCommunicationService::doWrite()
|
||||
{
|
||||
selfData_.updateTime = std::chrono::system_clock::now();
|
||||
boost::json::value jsonValue{};
|
||||
boost::json::value_from(selfData_, jsonValue);
|
||||
backend_->writeNodeMessage(*selfData_.uuid, boost::json::serialize(jsonValue.as_object()));
|
||||
backend_.stop();
|
||||
}
|
||||
|
||||
} // namespace cluster
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "cluster/ClusterCommunicationServiceInterface.hpp"
|
||||
#include "cluster/Backend.hpp"
|
||||
#include "cluster/Concepts.hpp"
|
||||
#include "cluster/Metrics.hpp"
|
||||
#include "cluster/WriterDecider.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "util/prometheus/Bool.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
@@ -33,67 +32,49 @@
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <latch>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Service to post and read messages to/from the cluster. It uses a backend to communicate with the cluster.
|
||||
*/
|
||||
class ClusterCommunicationService : public ClusterCommunicationServiceInterface {
|
||||
util::prometheus::GaugeInt& nodesInClusterMetric_ = PrometheusService::gaugeInt(
|
||||
"cluster_nodes_total_number",
|
||||
{},
|
||||
"Total number of nodes this node can detect in the cluster."
|
||||
);
|
||||
util::prometheus::Bool isHealthy_ = PrometheusService::boolMetric(
|
||||
"cluster_communication_is_healthy",
|
||||
{},
|
||||
"Whether cluster communication service is operating healthy (1 - healthy, 0 - we have a problem)"
|
||||
);
|
||||
|
||||
class ClusterCommunicationService : public ClusterCommunicationServiceTag {
|
||||
// TODO: Use util::async::CoroExecutionContext after https://github.com/XRPLF/clio/issues/1973 is implemented
|
||||
boost::asio::thread_pool ctx_{1};
|
||||
boost::asio::strand<boost::asio::thread_pool::executor_type> strand_ = boost::asio::make_strand(ctx_);
|
||||
|
||||
util::Logger log_{"ClusterCommunication"};
|
||||
|
||||
std::shared_ptr<data::BackendInterface> backend_;
|
||||
|
||||
std::chrono::steady_clock::duration readInterval_;
|
||||
std::chrono::steady_clock::duration writeInterval_;
|
||||
|
||||
boost::asio::cancellation_signal cancelSignal_;
|
||||
std::latch finishedCountdown_;
|
||||
std::atomic_bool running_ = false;
|
||||
bool stopped_ = false;
|
||||
|
||||
ClioNode selfData_;
|
||||
std::vector<ClioNode> otherNodesData_;
|
||||
Backend backend_;
|
||||
Metrics metrics_;
|
||||
WriterDecider writerDecider_;
|
||||
|
||||
public:
|
||||
static constexpr std::chrono::milliseconds kDEFAULT_READ_INTERVAL{2100};
|
||||
static constexpr std::chrono::milliseconds kDEFAULT_WRITE_INTERVAL{1200};
|
||||
static constexpr std::chrono::milliseconds kDEFAULT_READ_INTERVAL{1000};
|
||||
static constexpr std::chrono::milliseconds kDEFAULT_WRITE_INTERVAL{1000};
|
||||
|
||||
/**
|
||||
* @brief Construct a new Cluster Communication Service object.
|
||||
*
|
||||
* @param backend The backend to use for communication.
|
||||
* @param writerState The state showing whether clio is writing to the database.
|
||||
* @param readInterval The interval to read messages from the cluster.
|
||||
* @param writeInterval The interval to write messages to the cluster.
|
||||
*/
|
||||
ClusterCommunicationService(
|
||||
std::shared_ptr<data::BackendInterface> backend,
|
||||
std::unique_ptr<etl::WriterStateInterface> writerState,
|
||||
std::chrono::steady_clock::duration readInterval = kDEFAULT_READ_INTERVAL,
|
||||
std::chrono::steady_clock::duration writeInterval = kDEFAULT_WRITE_INTERVAL
|
||||
);
|
||||
|
||||
~ClusterCommunicationService() override;
|
||||
|
||||
ClusterCommunicationService(ClusterCommunicationService&&) = delete;
|
||||
ClusterCommunicationService(ClusterCommunicationService const&) = delete;
|
||||
ClusterCommunicationService&
|
||||
operator=(ClusterCommunicationService&&) = delete;
|
||||
ClusterCommunicationService&
|
||||
operator=(ClusterCommunicationService const&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Start the service.
|
||||
*/
|
||||
@@ -105,44 +86,6 @@ public:
|
||||
*/
|
||||
void
|
||||
stop();
|
||||
|
||||
ClusterCommunicationService(ClusterCommunicationService&&) = delete;
|
||||
ClusterCommunicationService(ClusterCommunicationService const&) = delete;
|
||||
ClusterCommunicationService&
|
||||
operator=(ClusterCommunicationService&&) = delete;
|
||||
ClusterCommunicationService&
|
||||
operator=(ClusterCommunicationService const&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Get the UUID of the current node.
|
||||
*
|
||||
* @return The UUID of the current node.
|
||||
*/
|
||||
std::shared_ptr<boost::uuids::uuid>
|
||||
selfUuid() const;
|
||||
|
||||
/**
|
||||
* @brief Get the data of the current node.
|
||||
*
|
||||
* @return The data of the current node.
|
||||
*/
|
||||
ClioNode
|
||||
selfData() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the data of all nodes in the cluster (including self).
|
||||
*
|
||||
* @return The data of all nodes in the cluster or error if the service is not healthy.
|
||||
*/
|
||||
std::expected<std::vector<ClioNode>, std::string>
|
||||
clusterData() const override;
|
||||
|
||||
private:
|
||||
void
|
||||
doRead(boost::asio::yield_context yield);
|
||||
|
||||
void
|
||||
doWrite();
|
||||
};
|
||||
|
||||
} // namespace cluster
|
||||
|
||||
39
src/cluster/Concepts.hpp
Normal file
39
src/cluster/Concepts.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <concepts>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Tag type for cluster communication service implementations.
|
||||
*
|
||||
* This tag is used to identify types that implement cluster communication functionality.
|
||||
* Types should inherit from this tag to be recognized as cluster communication services.
|
||||
*/
|
||||
struct ClusterCommunicationServiceTag {
|
||||
virtual ~ClusterCommunicationServiceTag() = default;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept SomeClusterCommunicationService = std::derived_from<T, ClusterCommunicationServiceTag>;
|
||||
|
||||
} // namespace cluster
|
||||
@@ -17,38 +17,31 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
#include "cluster/Metrics.hpp"
|
||||
|
||||
#include "cluster/Backend.hpp"
|
||||
#include "cluster/ClioNode.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Interface for the cluster communication service.
|
||||
*/
|
||||
class ClusterCommunicationServiceInterface {
|
||||
public:
|
||||
virtual ~ClusterCommunicationServiceInterface() = default;
|
||||
Metrics::Metrics()
|
||||
{
|
||||
nodesInClusterMetric_.set(1); // The node always sees itself
|
||||
isHealthy_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the data of the current node.
|
||||
*
|
||||
* @return The data of the current node.
|
||||
*/
|
||||
[[nodiscard]] virtual ClioNode
|
||||
selfData() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the data of all nodes in the cluster (including self).
|
||||
*
|
||||
* @return The data of all nodes in the cluster or error if the service is not healthy.
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<std::vector<ClioNode>, std::string>
|
||||
clusterData() const = 0;
|
||||
};
|
||||
void
|
||||
Metrics::onNewState(ClioNode::CUuid, std::shared_ptr<Backend::ClusterData const> clusterData)
|
||||
{
|
||||
if (clusterData->has_value()) {
|
||||
isHealthy_ = true;
|
||||
nodesInClusterMetric_.set(clusterData->value().size());
|
||||
} else {
|
||||
isHealthy_ = false;
|
||||
nodesInClusterMetric_.set(1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cluster
|
||||
76
src/cluster/Metrics.hpp
Normal file
76
src/cluster/Metrics.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "cluster/Backend.hpp"
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "util/prometheus/Bool.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Manages Prometheus metrics for cluster communication and node tracking.
|
||||
*
|
||||
* This class tracks cluster-related metrics including:
|
||||
* - Total number of nodes detected in the cluster
|
||||
* - Health status of cluster communication
|
||||
*/
|
||||
class Metrics {
|
||||
/** @brief Gauge tracking the total number of nodes visible in the cluster */
|
||||
util::prometheus::GaugeInt& nodesInClusterMetric_ = PrometheusService::gaugeInt(
|
||||
"cluster_nodes_total_number",
|
||||
{},
|
||||
"Total number of nodes this node can detect in the cluster."
|
||||
);
|
||||
|
||||
/** @brief Boolean metric indicating whether cluster communication is healthy */
|
||||
util::prometheus::Bool isHealthy_ = PrometheusService::boolMetric(
|
||||
"cluster_communication_is_healthy",
|
||||
{},
|
||||
"Whether cluster communication service is operating healthy (1 - healthy, 0 - we have a problem)"
|
||||
);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Metrics instance and initializes metrics.
|
||||
*
|
||||
* Sets the initial node count to 1 (self) and marks communication as healthy.
|
||||
*/
|
||||
Metrics();
|
||||
|
||||
/**
|
||||
* @brief Updates metrics based on new cluster state.
|
||||
*
|
||||
* This callback is invoked when cluster state changes. It updates:
|
||||
* - Health status based on whether cluster data is available
|
||||
* - Node count to reflect the current cluster size
|
||||
*
|
||||
* @param uuid The UUID of the node (unused in current implementation)
|
||||
* @param clusterData Shared pointer to the current cluster data; may be empty if communication failed
|
||||
*/
|
||||
void
|
||||
onNewState(ClioNode::CUuid uuid, std::shared_ptr<Backend::ClusterData const> clusterData);
|
||||
};
|
||||
|
||||
} // namespace cluster
|
||||
98
src/cluster/WriterDecider.cpp
Normal file
98
src/cluster/WriterDecider.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "cluster/WriterDecider.hpp"
|
||||
|
||||
#include "cluster/Backend.hpp"
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/Spawn.hpp"
|
||||
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
WriterDecider::WriterDecider(boost::asio::thread_pool& ctx, std::unique_ptr<etl::WriterStateInterface> writerState)
|
||||
: ctx_(ctx), writerState_(std::move(writerState))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
WriterDecider::onNewState(ClioNode::CUuid selfId, std::shared_ptr<Backend::ClusterData const> clusterData)
|
||||
{
|
||||
if (not clusterData->has_value())
|
||||
return;
|
||||
|
||||
util::spawn(
|
||||
ctx_,
|
||||
[writerState = writerState_->clone(),
|
||||
selfId = std::move(selfId),
|
||||
clusterData = clusterData->value()](auto&&) mutable {
|
||||
auto const selfData =
|
||||
std::ranges::find_if(clusterData, [&selfId](ClioNode const& node) { return node.uuid == selfId; });
|
||||
ASSERT(selfData != clusterData.end(), "Self data should always be in the cluster data");
|
||||
|
||||
if (selfData->dbRole == ClioNode::DbRole::Fallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selfData->dbRole == ClioNode::DbRole::ReadOnly) {
|
||||
writerState->giveUpWriting();
|
||||
return;
|
||||
}
|
||||
|
||||
// If any node in the cluster is in Fallback mode, the entire cluster must switch
|
||||
// to the fallback writer decision mechanism for consistency
|
||||
if (std::ranges::any_of(clusterData, [](ClioNode const& node) {
|
||||
return node.dbRole == ClioNode::DbRole::Fallback;
|
||||
})) {
|
||||
writerState->setWriterDecidingFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// We are not ReadOnly and there is no Fallback in the cluster
|
||||
std::ranges::sort(clusterData, [](ClioNode const& lhs, ClioNode const& rhs) {
|
||||
return *lhs.uuid < *rhs.uuid;
|
||||
});
|
||||
|
||||
auto const it = std::ranges::find_if(clusterData, [](ClioNode const& node) {
|
||||
return node.dbRole == ClioNode::DbRole::NotWriter or node.dbRole == ClioNode::DbRole::Writer;
|
||||
});
|
||||
|
||||
if (it == clusterData.end()) {
|
||||
// No writer nodes in the cluster yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (*it->uuid == *selfId) {
|
||||
writerState->startWriting();
|
||||
} else {
|
||||
writerState->giveUpWriting();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace cluster
|
||||
75
src/cluster/WriterDecider.hpp
Normal file
75
src/cluster/WriterDecider.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "cluster/Backend.hpp"
|
||||
#include "cluster/ClioNode.hpp"
|
||||
#include "etl/WriterState.hpp"
|
||||
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace cluster {
|
||||
|
||||
/**
|
||||
* @brief Decides which node in the cluster should be the writer based on cluster state.
|
||||
*
|
||||
* This class monitors cluster state changes and determines whether the current node
|
||||
* should act as the writer to the database. The decision is made by:
|
||||
* 1. Sorting all nodes by UUID for deterministic ordering
|
||||
* 2. Selecting the first node that is allowed to write (not ReadOnly)
|
||||
* 3. Activating writing on this node if it's the current node, otherwise deactivating
|
||||
*
|
||||
* This ensures only one node in the cluster actively writes to the database at a time.
|
||||
*/
|
||||
class WriterDecider {
|
||||
/** @brief Thread pool for spawning asynchronous tasks */
|
||||
boost::asio::thread_pool& ctx_;
|
||||
|
||||
/** @brief Interface for controlling the writer state of this node */
|
||||
std::unique_ptr<etl::WriterStateInterface> writerState_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a WriterDecider.
|
||||
*
|
||||
* @param ctx Thread pool for executing asynchronous operations
|
||||
* @param writerState Writer state interface for controlling write operations
|
||||
*/
|
||||
WriterDecider(boost::asio::thread_pool& ctx, std::unique_ptr<etl::WriterStateInterface> writerState);
|
||||
|
||||
/**
|
||||
* @brief Handles cluster state changes and decides whether this node should be the writer.
|
||||
*
|
||||
* This method is called when cluster state changes. It asynchronously:
|
||||
* - Sorts all nodes by UUID to establish a deterministic order
|
||||
* - Identifies the first node allowed to write (not ReadOnly)
|
||||
* - Activates writing if this node is selected, otherwise deactivates writing
|
||||
* - Logs a warning if no nodes in the cluster are allowed to write
|
||||
*
|
||||
* @param selfId The UUID of the current node
|
||||
* @param clusterData Shared pointer to current cluster data; may be empty if communication failed
|
||||
*/
|
||||
void
|
||||
onNewState(ClioNode::CUuid selfId, std::shared_ptr<Backend::ClusterData const> clusterData);
|
||||
};
|
||||
|
||||
} // namespace cluster
|
||||
104
src/cluster/impl/RepeatedTask.hpp
Normal file
104
src/cluster/impl/RepeatedTask.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "util/Assert.hpp"
|
||||
#include "util/Spawn.hpp"
|
||||
|
||||
#include <boost/asio/bind_cancellation_slot.hpp>
|
||||
#include <boost/asio/cancellation_signal.hpp>
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/executor.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <semaphore>
|
||||
|
||||
namespace cluster::impl {
|
||||
|
||||
// TODO: Try to replace util::Repeat by this. https://github.com/XRPLF/clio/issues/2926
|
||||
template <typename Context>
|
||||
class RepeatedTask {
|
||||
std::chrono::steady_clock::duration interval_;
|
||||
boost::asio::strand<typename Context::executor_type> strand_;
|
||||
|
||||
enum class State { Running, Stopped };
|
||||
std::atomic<State> state_ = State::Stopped;
|
||||
|
||||
std::binary_semaphore semaphore_{0};
|
||||
boost::asio::steady_timer timer_;
|
||||
|
||||
public:
|
||||
RepeatedTask(std::chrono::steady_clock::duration interval, Context& ctx)
|
||||
: interval_(interval), strand_(boost::asio::make_strand(ctx)), timer_(strand_)
|
||||
{
|
||||
}
|
||||
|
||||
~RepeatedTask()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
requires std::invocable<Fn, boost::asio::yield_context> or std::invocable<Fn>
|
||||
void
|
||||
run(Fn&& f)
|
||||
{
|
||||
ASSERT(state_ == State::Stopped, "Can only be ran once");
|
||||
state_ = State::Running;
|
||||
util::spawn(strand_, [this, f = std::forward<Fn>(f)](boost::asio::yield_context yield) {
|
||||
boost::system::error_code ec;
|
||||
|
||||
while (state_ == State::Running) {
|
||||
timer_.expires_after(interval_);
|
||||
timer_.async_wait(yield[ec]);
|
||||
|
||||
if (ec or state_ != State::Running)
|
||||
break;
|
||||
|
||||
if constexpr (std::invocable<decltype(f), boost::asio::yield_context>) {
|
||||
f(yield);
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
semaphore_.release();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
stop()
|
||||
{
|
||||
if (auto expected = State::Running; not state_.compare_exchange_strong(expected, State::Stopped))
|
||||
return; // Already stopped or not started
|
||||
|
||||
boost::asio::spawn(strand_, [this](auto&&) { timer_.cancel(); }, boost::asio::use_future).wait();
|
||||
semaphore_.acquire();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cluster::impl
|
||||
@@ -146,9 +146,12 @@ AmendmentCenter::isEnabled(AmendmentKey const& key, uint32_t seq) const
|
||||
bool
|
||||
AmendmentCenter::isEnabled(boost::asio::yield_context yield, AmendmentKey const& key, uint32_t seq) const
|
||||
{
|
||||
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments)
|
||||
return lookupAmendment(all_, *listAmendments, key);
|
||||
|
||||
try {
|
||||
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments)
|
||||
return lookupAmendment(all_, *listAmendments, key);
|
||||
} catch (std::runtime_error const&) {
|
||||
return false; // Some old ledger does not contain Amendments ledger object so do best we can for now
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -157,13 +160,19 @@ AmendmentCenter::isEnabled(boost::asio::yield_context yield, std::vector<Amendme
|
||||
{
|
||||
namespace rg = std::ranges;
|
||||
|
||||
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments) {
|
||||
std::vector<bool> out;
|
||||
rg::transform(keys, std::back_inserter(out), [this, &listAmendments](auto const& key) {
|
||||
return lookupAmendment(all_, *listAmendments, key);
|
||||
});
|
||||
try {
|
||||
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments) {
|
||||
std::vector<bool> out;
|
||||
rg::transform(keys, std::back_inserter(out), [this, &listAmendments](auto const& key) {
|
||||
return lookupAmendment(all_, *listAmendments, key);
|
||||
});
|
||||
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
} catch (std::runtime_error const&) {
|
||||
return std::vector<bool>(
|
||||
keys.size(), false
|
||||
); // Some old ledger does not contain Amendments ledger object so do best we can for now
|
||||
}
|
||||
|
||||
return std::vector<bool>(keys.size(), false);
|
||||
|
||||
@@ -152,6 +152,7 @@ struct Amendments {
|
||||
REGISTER(fixDirectoryLimit);
|
||||
REGISTER(fixIncludeKeyletFields);
|
||||
REGISTER(fixTokenEscrowV1);
|
||||
REGISTER(LendingProtocol);
|
||||
|
||||
// Obsolete but supported by libxrpl
|
||||
REGISTER(CryptoConditionsSuite);
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
namespace data {
|
||||
|
||||
LedgerCacheSaver::LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache)
|
||||
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path")), cache_(cache)
|
||||
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path"))
|
||||
, cache_(cache)
|
||||
, isAsync_(config.get<bool>("cache.file.async_save"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -56,6 +58,9 @@ LedgerCacheSaver::save()
|
||||
LOG(util::LogService::error()) << "Error saving LedgerCache to file: " << success.error();
|
||||
}
|
||||
});
|
||||
if (not isAsync_) {
|
||||
waitToFinish();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -53,6 +53,7 @@ class LedgerCacheSaver {
|
||||
std::optional<std::string> cacheFilePath_;
|
||||
std::reference_wrapper<LedgerCacheInterface const> cache_;
|
||||
std::optional<std::thread> savingThread_;
|
||||
bool isAsync_;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -115,6 +115,11 @@ LedgerCacheFile::write(DataView dataView)
|
||||
auto const hash = file.hash();
|
||||
file.write(hash.data(), decltype(hash)::bytes);
|
||||
|
||||
// flush internal buffer explicitly before renaming
|
||||
if (auto const expectedSuccess = file.close(); not expectedSuccess.has_value()) {
|
||||
return expectedSuccess;
|
||||
}
|
||||
|
||||
try {
|
||||
std::filesystem::rename(newFilePath, path_);
|
||||
} catch (std::exception const& e) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -59,4 +60,14 @@ OutputFile::hash() const
|
||||
return std::move(sum).finalize();
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
OutputFile::close()
|
||||
{
|
||||
file_.close();
|
||||
if (not file_) {
|
||||
return std::unexpected{"Error closing cache file"};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace data::impl
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
@@ -60,6 +61,9 @@ public:
|
||||
ripple::uint256
|
||||
hash() const;
|
||||
|
||||
std::expected<void, std::string>
|
||||
close();
|
||||
|
||||
private:
|
||||
void
|
||||
writeToFile(char const* data, size_t size);
|
||||
|
||||
@@ -11,6 +11,7 @@ target_sources(
|
||||
NetworkValidatedLedgers.cpp
|
||||
NFTHelpers.cpp
|
||||
Source.cpp
|
||||
WriterState.cpp
|
||||
impl/AmendmentBlockHandler.cpp
|
||||
impl/AsyncGrpcCall.cpp
|
||||
impl/Extraction.cpp
|
||||
|
||||
@@ -78,6 +78,7 @@ namespace etl {
|
||||
std::shared_ptr<ETLServiceInterface>
|
||||
ETLService::makeETLService(
|
||||
util::config::ClioConfigDefinition const& config,
|
||||
std::shared_ptr<SystemState> state,
|
||||
util::async::AnyExecutionContext ctx,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
|
||||
@@ -87,9 +88,6 @@ ETLService::makeETLService(
|
||||
{
|
||||
std::shared_ptr<ETLServiceInterface> ret;
|
||||
|
||||
auto state = std::make_shared<SystemState>();
|
||||
state->isStrictReadonly = config.get<bool>("read_only");
|
||||
|
||||
auto fetcher = std::make_shared<impl::LedgerFetcher>(backend, balancer);
|
||||
auto extractor = std::make_shared<impl::Extractor>(fetcher);
|
||||
auto publisher = std::make_shared<impl::LedgerPublisher>(ctx, backend, subscriptions, *state);
|
||||
@@ -173,6 +171,7 @@ ETLService::ETLService(
|
||||
, state_(std::move(state))
|
||||
, startSequence_(config.get().maybeValue<uint32_t>("start_sequence"))
|
||||
, finishSequence_(config.get().maybeValue<uint32_t>("finish_sequence"))
|
||||
, writeCommandStrand_(ctx_.makeStrand())
|
||||
{
|
||||
ASSERT(not state_->isWriting, "ETL should never start in writer mode");
|
||||
|
||||
@@ -213,14 +212,13 @@ ETLService::run()
|
||||
return;
|
||||
}
|
||||
|
||||
auto nextSequence = rng->maxSequence + 1;
|
||||
if (backend_->cache().latestLedgerSequence() != 0) {
|
||||
nextSequence = backend_->cache().latestLedgerSequence();
|
||||
}
|
||||
|
||||
auto const nextSequence = syncCacheWithDb();
|
||||
LOG(log_.debug()) << "Database is populated. Starting monitor loop. sequence = " << nextSequence;
|
||||
|
||||
startMonitor(nextSequence);
|
||||
|
||||
state_->isLoadingCache = false;
|
||||
|
||||
// If we are a writer as the result of loading the initial ledger - start loading
|
||||
if (state_->isWriting)
|
||||
startLoading(nextSequence);
|
||||
@@ -232,6 +230,13 @@ ETLService::stop()
|
||||
{
|
||||
LOG(log_.info()) << "Stop called";
|
||||
|
||||
systemStateWriteCommandSubscription_.disconnect();
|
||||
auto count = runningWriteCommandHandlers_.load();
|
||||
while (count != 0) {
|
||||
runningWriteCommandHandlers_.wait(count); // Blocks until value changes
|
||||
count = runningWriteCommandHandlers_.load();
|
||||
}
|
||||
|
||||
if (mainLoop_)
|
||||
mainLoop_->wait();
|
||||
if (taskMan_)
|
||||
@@ -343,35 +348,77 @@ ETLService::loadInitialLedgerIfNeeded()
|
||||
return rng;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ETLService::syncCacheWithDb()
|
||||
{
|
||||
auto rng = backend_->hardFetchLedgerRangeNoThrow();
|
||||
|
||||
while (not backend_->cache().isDisabled() and rng->maxSequence > backend_->cache().latestLedgerSequence()) {
|
||||
LOG(log_.info()) << "Syncing cache with DB. DB latest seq: " << rng->maxSequence
|
||||
<< ". Cache latest seq: " << backend_->cache().latestLedgerSequence();
|
||||
for (auto seq = backend_->cache().latestLedgerSequence(); seq <= rng->maxSequence; ++seq) {
|
||||
LOG(log_.info()) << "ETLService (via syncCacheWithDb) got new seq from db: " << seq;
|
||||
updateCache(seq);
|
||||
}
|
||||
rng = backend_->hardFetchLedgerRangeNoThrow();
|
||||
}
|
||||
return rng->maxSequence + 1;
|
||||
}
|
||||
|
||||
void
|
||||
ETLService::updateCache(uint32_t seq)
|
||||
{
|
||||
auto const cacheNeedsUpdate = backend_->cache().latestLedgerSequence() < seq;
|
||||
auto const backendRange = backend_->fetchLedgerRange();
|
||||
auto const backendNeedsUpdate = backendRange.has_value() and backendRange->maxSequence < seq;
|
||||
|
||||
if (cacheNeedsUpdate) {
|
||||
auto const diff = data::synchronousAndRetryOnTimeout([this, seq](auto yield) {
|
||||
return backend_->fetchLedgerDiff(seq, yield);
|
||||
});
|
||||
cacheUpdater_->update(seq, diff);
|
||||
}
|
||||
|
||||
if (backendNeedsUpdate)
|
||||
backend_->updateRange(seq);
|
||||
|
||||
publisher_->publish(seq, {});
|
||||
}
|
||||
|
||||
void
|
||||
ETLService::startMonitor(uint32_t seq)
|
||||
{
|
||||
monitor_ = monitorProvider_->make(ctx_, backend_, ledgers_, seq);
|
||||
|
||||
systemStateWriteCommandSubscription_ =
|
||||
state_->writeCommandSignal.connect([this](SystemState::WriteCommand command) {
|
||||
++runningWriteCommandHandlers_;
|
||||
writeCommandStrand_.submit([this, command]() {
|
||||
switch (command) {
|
||||
case etl::SystemState::WriteCommand::StartWriting:
|
||||
attemptTakeoverWriter();
|
||||
break;
|
||||
case etl::SystemState::WriteCommand::StopWriting:
|
||||
giveUpWriter();
|
||||
break;
|
||||
}
|
||||
--runningWriteCommandHandlers_;
|
||||
runningWriteCommandHandlers_.notify_one();
|
||||
});
|
||||
});
|
||||
|
||||
monitorNewSeqSubscription_ = monitor_->subscribeToNewSequence([this](uint32_t seq) {
|
||||
LOG(log_.info()) << "ETLService (via Monitor) got new seq from db: " << seq;
|
||||
|
||||
if (state_->writeConflict) {
|
||||
LOG(log_.info()) << "Got a write conflict; Giving up writer seat immediately";
|
||||
giveUpWriter();
|
||||
}
|
||||
|
||||
if (not state_->isWriting) {
|
||||
auto const diff = data::synchronousAndRetryOnTimeout([this, seq](auto yield) {
|
||||
return backend_->fetchLedgerDiff(seq, yield);
|
||||
});
|
||||
|
||||
cacheUpdater_->update(seq, diff);
|
||||
backend_->updateRange(seq);
|
||||
}
|
||||
|
||||
publisher_->publish(seq, {});
|
||||
updateCache(seq);
|
||||
});
|
||||
|
||||
monitorDbStalledSubscription_ = monitor_->subscribeToDbStalled([this]() {
|
||||
LOG(log_.warn()) << "ETLService received DbStalled signal from Monitor";
|
||||
// Database stall detected - no writer has been active for 10 seconds
|
||||
// This triggers the fallback mechanism and attempts to become the writer
|
||||
if (not state_->isStrictReadonly and not state_->isWriting)
|
||||
attemptTakeoverWriter();
|
||||
state_->writeCommandSignal(SystemState::WriteCommand::StartWriting);
|
||||
state_->isWriterDecidingFallback = true;
|
||||
});
|
||||
|
||||
monitor_->run();
|
||||
@@ -394,6 +441,13 @@ ETLService::attemptTakeoverWriter()
|
||||
auto rng = backend_->hardFetchLedgerRangeNoThrow();
|
||||
ASSERT(rng.has_value(), "Ledger range can't be null");
|
||||
|
||||
if (backend_->cache().latestLedgerSequence() != rng->maxSequence) {
|
||||
LOG(log_.info()) << "Wanted to take over the ETL writer seat but LedgerCache is outdated";
|
||||
// Give ETL time to update LedgerCache. This method will be called because ClusterCommunication will likely to
|
||||
// continue sending StartWriting signal every 1 second
|
||||
return;
|
||||
}
|
||||
|
||||
state_->isWriting = true; // switch to writer
|
||||
LOG(log_.info()) << "Taking over the ETL writer seat";
|
||||
startLoading(rng->maxSequence + 1);
|
||||
@@ -404,7 +458,7 @@ ETLService::giveUpWriter()
|
||||
{
|
||||
ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes");
|
||||
state_->isWriting = false;
|
||||
state_->writeConflict = false;
|
||||
LOG(log_.info()) << "Giving up writer seat";
|
||||
taskMan_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "util/async/AnyExecutionContext.hpp"
|
||||
#include "util/async/AnyOperation.hpp"
|
||||
#include "util/async/AnyStrand.hpp"
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
|
||||
@@ -69,12 +70,12 @@
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace etl {
|
||||
|
||||
@@ -117,6 +118,9 @@ class ETLService : public ETLServiceInterface {
|
||||
|
||||
boost::signals2::scoped_connection monitorNewSeqSubscription_;
|
||||
boost::signals2::scoped_connection monitorDbStalledSubscription_;
|
||||
boost::signals2::scoped_connection systemStateWriteCommandSubscription_;
|
||||
util::async::AnyStrand writeCommandStrand_;
|
||||
std::atomic<size_t> runningWriteCommandHandlers_{0};
|
||||
|
||||
std::optional<util::async::AnyOperation<void>> mainLoop_;
|
||||
|
||||
@@ -127,6 +131,7 @@ public:
|
||||
* Creates and runs the ETL service.
|
||||
*
|
||||
* @param config The configuration to use
|
||||
* @param state The system state tracking object
|
||||
* @param ctx Execution context for asynchronous operations
|
||||
* @param backend BackendInterface implementation
|
||||
* @param subscriptions Subscription manager
|
||||
@@ -137,6 +142,7 @@ public:
|
||||
static std::shared_ptr<ETLServiceInterface>
|
||||
makeETLService(
|
||||
util::config::ClioConfigDefinition const& config,
|
||||
std::shared_ptr<SystemState> state,
|
||||
util::async::AnyExecutionContext ctx,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
|
||||
@@ -160,7 +166,7 @@ public:
|
||||
* @param initialLoadObserver The observer for initial data loading
|
||||
* @param taskManagerProvider The provider of the task manager instance
|
||||
* @param monitorProvider The provider of the monitor instance
|
||||
* @param state System state tracking object
|
||||
* @param state The system state tracking object
|
||||
*/
|
||||
ETLService(
|
||||
util::async::AnyExecutionContext ctx,
|
||||
@@ -206,6 +212,12 @@ private:
|
||||
std::optional<data::LedgerRange>
|
||||
loadInitialLedgerIfNeeded();
|
||||
|
||||
[[nodiscard]] uint32_t
|
||||
syncCacheWithDb();
|
||||
|
||||
void
|
||||
updateCache(uint32_t seq);
|
||||
|
||||
void
|
||||
startMonitor(uint32_t seq);
|
||||
|
||||
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "util/prometheus/Bool.hpp"
|
||||
#include "util/prometheus/Label.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <boost/signals2/variadic_signal.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace etl {
|
||||
|
||||
@@ -31,6 +36,25 @@ namespace etl {
|
||||
* @brief Represents the state of the ETL subsystem.
|
||||
*/
|
||||
struct SystemState {
|
||||
SystemState()
|
||||
{
|
||||
isLoadingCache = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Factory method to create a SystemState instance.
|
||||
*
|
||||
* @param config The configuration to use for initializing the system state
|
||||
* @return A shared pointer to the newly created SystemState
|
||||
*/
|
||||
static std::shared_ptr<SystemState>
|
||||
makeSystemState(util::config::ClioConfigDefinition const& config)
|
||||
{
|
||||
auto state = std::make_shared<SystemState>();
|
||||
state->isStrictReadonly = config.get<bool>("read_only");
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether the process is in strict read-only mode.
|
||||
*
|
||||
@@ -50,8 +74,31 @@ struct SystemState {
|
||||
"Whether the process is writing to the database"
|
||||
);
|
||||
|
||||
std::atomic_bool isStopping = false; /**< @brief Whether the software is stopping. */
|
||||
std::atomic_bool writeConflict = false; /**< @brief Whether a write conflict was detected. */
|
||||
/** @brief Whether the process is still loading cache after startup. */
|
||||
util::prometheus::Bool isLoadingCache = PrometheusService::boolMetric(
|
||||
"etl_loading_cache",
|
||||
util::prometheus::Labels{},
|
||||
"Whether etl is loading cache after clio startup"
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Commands for controlling the ETL writer state.
|
||||
*
|
||||
* These commands are emitted via writeCommandSignal to coordinate writer state transitions across components.
|
||||
*/
|
||||
enum class WriteCommand {
|
||||
StartWriting, /**< Request to attempt taking over as the ETL writer */
|
||||
StopWriting /**< Request to give up the ETL writer role (e.g., due to write conflict) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Signal for coordinating ETL writer state transitions.
|
||||
*
|
||||
* This signal allows components to request changes to the writer state without direct coupling.
|
||||
* - Emitted with StartWriting when database stalls and node should attempt to become writer
|
||||
* - Emitted with StopWriting when write conflicts are detected
|
||||
*/
|
||||
boost::signals2::signal<void(WriteCommand)> writeCommandSignal;
|
||||
|
||||
/**
|
||||
* @brief Whether clio detected an amendment block.
|
||||
@@ -77,6 +124,24 @@ struct SystemState {
|
||||
util::prometheus::Labels{},
|
||||
"Whether clio detected a corruption that needs manual attention"
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Whether the cluster is using the fallback writer decision mechanism.
|
||||
*
|
||||
* The fallback mechanism is triggered when:
|
||||
* - The database stalls for 10 seconds (detected by Monitor), indicating no active writer
|
||||
* - A write conflict is detected, indicating multiple nodes attempting to write simultaneously
|
||||
*
|
||||
* When fallback mode is active, the cluster stops using the cluster communication mechanism
|
||||
* (TTL-based role announcements) and relies on the slower but more reliable database-based
|
||||
* conflict detection. This flag propagates across the cluster - if any node enters fallback
|
||||
* mode, all nodes in the cluster will switch to fallback mode.
|
||||
*/
|
||||
util::prometheus::Bool isWriterDecidingFallback = PrometheusService::boolMetric(
|
||||
"etl_writing_deciding_fallback",
|
||||
util::prometheus::Labels{},
|
||||
"Whether the cluster is using the fallback writer decision mechanism"
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace etl
|
||||
|
||||
88
src/etl/WriterState.cpp
Normal file
88
src/etl/WriterState.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "etl/WriterState.hpp"
|
||||
|
||||
#include "etl/SystemState.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace etl {
|
||||
|
||||
WriterState::WriterState(std::shared_ptr<SystemState> state) : systemState_(std::move(state))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
WriterState::isReadOnly() const
|
||||
{
|
||||
return systemState_->isStrictReadonly;
|
||||
}
|
||||
|
||||
bool
|
||||
WriterState::isWriting() const
|
||||
{
|
||||
return systemState_->isWriting;
|
||||
}
|
||||
|
||||
void
|
||||
WriterState::startWriting()
|
||||
{
|
||||
if (isWriting())
|
||||
return;
|
||||
|
||||
systemState_->writeCommandSignal(SystemState::WriteCommand::StartWriting);
|
||||
}
|
||||
|
||||
void
|
||||
WriterState::giveUpWriting()
|
||||
{
|
||||
if (not isWriting())
|
||||
return;
|
||||
|
||||
systemState_->writeCommandSignal(SystemState::WriteCommand::StopWriting);
|
||||
}
|
||||
|
||||
void
|
||||
WriterState::setWriterDecidingFallback()
|
||||
{
|
||||
systemState_->isWriterDecidingFallback = true;
|
||||
}
|
||||
|
||||
bool
|
||||
WriterState::isFallback() const
|
||||
{
|
||||
return systemState_->isWriterDecidingFallback;
|
||||
}
|
||||
|
||||
bool
|
||||
WriterState::isLoadingCache() const
|
||||
{
|
||||
return systemState_->isLoadingCache;
|
||||
}
|
||||
|
||||
std::unique_ptr<WriterStateInterface>
|
||||
WriterState::clone() const
|
||||
{
|
||||
auto c = WriterState(*this);
|
||||
return std::make_unique<WriterState>(std::move(c));
|
||||
}
|
||||
|
||||
} // namespace etl
|
||||
193
src/etl/WriterState.hpp
Normal file
193
src/etl/WriterState.hpp
Normal file
@@ -0,0 +1,193 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "etl/SystemState.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace etl {
|
||||
|
||||
/**
|
||||
* @brief Interface for managing writer state in the ETL subsystem.
|
||||
*
|
||||
* This interface provides methods to query and control whether the ETL process
|
||||
* is actively writing to the database. Implementations should coordinate with
|
||||
* the ETL system state to manage write responsibilities.
|
||||
*/
|
||||
class WriterStateInterface {
|
||||
public:
|
||||
virtual ~WriterStateInterface() = default;
|
||||
|
||||
/**
|
||||
* @brief Check if the ETL process is in strict read-only mode.
|
||||
* @return true if the process is in strict read-only mode, false otherwise
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isReadOnly() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the ETL process is currently writing to the database.
|
||||
* @return true if the process is writing, false otherwise
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isWriting() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Request to start writing to the database.
|
||||
*
|
||||
* This method signals that the process should take over writing responsibilities.
|
||||
* The actual transition to writing state may not be immediate.
|
||||
*/
|
||||
virtual void
|
||||
startWriting() = 0;
|
||||
|
||||
/**
|
||||
* @brief Request to stop writing to the database.
|
||||
*
|
||||
* This method signals that the process should give up writing responsibilities.
|
||||
* The actual transition from writing state may not be immediate.
|
||||
*/
|
||||
virtual void
|
||||
giveUpWriting() = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the cluster is using the fallback writer decision mechanism.
|
||||
*
|
||||
* @return true if the cluster has switched to fallback mode, false otherwise
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isFallback() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Switch the cluster to the fallback writer decision mechanism.
|
||||
*
|
||||
* This method is called when the cluster needs to transition from the cluster
|
||||
* communication mechanism to the slower but more reliable fallback mechanism.
|
||||
* Once set, this flag propagates to all nodes in the cluster through the
|
||||
* ClioNode DbRole::Fallback state.
|
||||
*/
|
||||
virtual void
|
||||
setWriterDecidingFallback() = 0;
|
||||
|
||||
/**
|
||||
* @brief Whether clio is still loading cache after startup.
|
||||
*
|
||||
* @return true if clio is still loading cache, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isLoadingCache() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Create a clone of this writer state.
|
||||
*
|
||||
* Creates a new instance of the writer state with the same underlying system state.
|
||||
* This is used when spawning operations that need their own writer state instance
|
||||
* while sharing the same system state.
|
||||
*
|
||||
* @return A unique pointer to the cloned writer state.
|
||||
*/
|
||||
[[nodiscard]] virtual std::unique_ptr<WriterStateInterface>
|
||||
clone() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of WriterStateInterface that manages ETL writer state.
|
||||
*
|
||||
* This class coordinates with SystemState to manage whether the ETL process
|
||||
* is actively writing to the database. It provides methods to query the current
|
||||
* writing state and request transitions between writing and non-writing states.
|
||||
*/
|
||||
class WriterState : public WriterStateInterface {
|
||||
private:
|
||||
std::shared_ptr<SystemState> systemState_; /**< @brief Shared system state for ETL coordination */
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a WriterState with the given system state.
|
||||
* @param state Shared pointer to the system state for coordination
|
||||
*/
|
||||
WriterState(std::shared_ptr<SystemState> state);
|
||||
|
||||
bool
|
||||
isReadOnly() const override;
|
||||
|
||||
/**
|
||||
* @brief Check if the ETL process is currently writing to the database.
|
||||
* @return true if the process is writing, false otherwise
|
||||
*/
|
||||
bool
|
||||
isWriting() const override;
|
||||
|
||||
/**
|
||||
* @brief Request to start writing to the database.
|
||||
*
|
||||
* If already writing, this method does nothing. Otherwise, it sets the
|
||||
* shouldTakeoverWriting flag in the system state to signal the request.
|
||||
*/
|
||||
void
|
||||
startWriting() override;
|
||||
|
||||
/**
|
||||
* @brief Request to stop writing to the database.
|
||||
*
|
||||
* If not currently writing, this method does nothing. Otherwise, it sets the
|
||||
* shouldGiveUpWriter flag in the system state to signal the request.
|
||||
*/
|
||||
void
|
||||
giveUpWriting() override;
|
||||
|
||||
/**
|
||||
* @brief Switch the cluster to the fallback writer decision mechanism.
|
||||
*
|
||||
* Sets the isWriterDecidingFallback flag in the system state, which will be
|
||||
* propagated to other nodes in the cluster through the ClioNode DbRole::Fallback state.
|
||||
*/
|
||||
void
|
||||
setWriterDecidingFallback() override;
|
||||
|
||||
/**
|
||||
* @brief Check if the cluster is using the fallback writer decision mechanism.
|
||||
*
|
||||
* @return true if the cluster has switched to fallback mode, false otherwise
|
||||
*/
|
||||
bool
|
||||
isFallback() const override;
|
||||
|
||||
/**
|
||||
* @brief Whether clio is still loading cache after startup.
|
||||
*
|
||||
* @return true if clio is still loading cache, false otherwise.
|
||||
*/
|
||||
bool
|
||||
isLoadingCache() const override;
|
||||
|
||||
/**
|
||||
* @brief Create a clone of this writer state.
|
||||
*
|
||||
* Creates a new WriterState instance sharing the same system state.
|
||||
*
|
||||
* @return A unique pointer to the cloned writer state.
|
||||
*/
|
||||
std::unique_ptr<WriterStateInterface>
|
||||
clone() const override;
|
||||
};
|
||||
|
||||
} // namespace etl
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -76,6 +77,8 @@ class LedgerPublisher : public LedgerPublisherInterface {
|
||||
|
||||
util::async::AnyStrand publishStrand_;
|
||||
|
||||
std::atomic_bool stop_{false};
|
||||
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
|
||||
std::reference_wrapper<SystemState const> state_; // shared state for ETL
|
||||
@@ -125,7 +128,7 @@ public:
|
||||
{
|
||||
LOG(log_.info()) << "Attempting to publish ledger = " << ledgerSequence;
|
||||
size_t numAttempts = 0;
|
||||
while (not state_.get().isStopping) {
|
||||
while (not stop_) {
|
||||
auto range = backend_->hardFetchLedgerRangeNoThrow();
|
||||
|
||||
if (!range || range->maxSequence < ledgerSequence) {
|
||||
@@ -258,6 +261,18 @@ public:
|
||||
return *lastPublishedSequence_.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops publishing
|
||||
*
|
||||
* @note This is a basic implementation to satisfy tests. This will be improved in
|
||||
* https://github.com/XRPLF/clio/issues/2833
|
||||
*/
|
||||
void
|
||||
stop()
|
||||
{
|
||||
stop_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
setLastClose(std::chrono::time_point<ripple::NetClock> lastCloseTime)
|
||||
|
||||
@@ -75,7 +75,10 @@ Loader::load(model::LedgerData const& data)
|
||||
<< "; took " << duration << "ms";
|
||||
|
||||
if (not success) {
|
||||
state_->writeConflict = true;
|
||||
// Write conflict detected - another node wrote to the database
|
||||
// This triggers the fallback mechanism and stops this node from writing
|
||||
state_->writeCommandSignal(SystemState::WriteCommand::StopWriting);
|
||||
state_->isWriterDecidingFallback = true;
|
||||
LOG(log_.warn()) << "Another node wrote a ledger into the DB - we have a write conflict";
|
||||
return std::unexpected(LoaderError::WriteConflict);
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ TaskManager::run(std::size_t numExtractors)
|
||||
util::async::AnyOperation<void>
|
||||
TaskManager::spawnExtractor(TaskQueue& queue)
|
||||
{
|
||||
// TODO: these values may be extracted to config later and/or need to be fine-tuned on a realistic system
|
||||
static constexpr auto kDELAY_BETWEEN_ATTEMPTS = std::chrono::milliseconds{100u};
|
||||
// TODO https://github.com/XRPLF/clio/issues/2838: the approach should be changed to a reactive one instead
|
||||
static constexpr auto kDELAY_BETWEEN_ATTEMPTS = std::chrono::milliseconds{10u};
|
||||
static constexpr auto kDELAY_BETWEEN_ENQUEUE_ATTEMPTS = std::chrono::milliseconds{1u};
|
||||
|
||||
return ctx_.execute([this, &queue](auto stopRequested) {
|
||||
|
||||
@@ -25,20 +25,39 @@
|
||||
#include "util/prometheus/Label.hpp"
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
void
|
||||
WorkQueue::OneTimeCallable::setCallable(std::function<void()> func)
|
||||
{
|
||||
func_ = std::move(func);
|
||||
}
|
||||
|
||||
void
|
||||
WorkQueue::OneTimeCallable::operator()()
|
||||
{
|
||||
if (not called_) {
|
||||
func_();
|
||||
called_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
WorkQueue::OneTimeCallable::
|
||||
operator bool() const
|
||||
{
|
||||
return func_.operator bool();
|
||||
}
|
||||
|
||||
WorkQueue::WorkQueue(DontStartProcessingTag, std::uint32_t numWorkers, uint32_t maxSize)
|
||||
: queued_{PrometheusService::counterInt(
|
||||
"work_queue_queued_total_number",
|
||||
@@ -56,8 +75,6 @@ WorkQueue::WorkQueue(DontStartProcessingTag, std::uint32_t numWorkers, uint32_t
|
||||
"The current number of tasks in the queue"
|
||||
)}
|
||||
, ioc_{numWorkers}
|
||||
, strand_{ioc_.get_executor()}
|
||||
, waitTimer_(ioc_)
|
||||
{
|
||||
if (maxSize != 0)
|
||||
maxSize_ = maxSize;
|
||||
@@ -77,12 +94,14 @@ WorkQueue::~WorkQueue()
|
||||
void
|
||||
WorkQueue::startProcessing()
|
||||
{
|
||||
util::spawn(strand_, [this](auto yield) {
|
||||
ASSERT(not hasDispatcher_, "Dispatcher already running");
|
||||
ASSERT(not processingStarted_, "Attempt to start processing work queue more than once");
|
||||
processingStarted_ = true;
|
||||
|
||||
hasDispatcher_ = true;
|
||||
dispatcherLoop(yield);
|
||||
});
|
||||
// Spawn workers for all tasks that were queued before processing started
|
||||
auto const numTasks = size();
|
||||
for (auto i = 0uz; i < numTasks; ++i) {
|
||||
util::spawn(ioc_, [this](auto yield) { executeTask(yield); });
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -98,105 +117,28 @@ WorkQueue::postCoro(TaskType func, bool isWhiteListed, Priority priority)
|
||||
return false;
|
||||
}
|
||||
|
||||
++curSize_.get();
|
||||
auto needsWakeup = false;
|
||||
|
||||
{
|
||||
auto state = dispatcherState_.lock();
|
||||
|
||||
needsWakeup = std::exchange(state->isIdle, false);
|
||||
|
||||
auto state = queueState_.lock();
|
||||
state->push(priority, std::move(func));
|
||||
}
|
||||
|
||||
if (needsWakeup)
|
||||
boost::asio::post(strand_, [this] { waitTimer_.cancel(); });
|
||||
++curSize_.get();
|
||||
|
||||
if (not processingStarted_)
|
||||
return true;
|
||||
|
||||
util::spawn(ioc_, [this](auto yield) { executeTask(yield); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
WorkQueue::dispatcherLoop(boost::asio::yield_context yield)
|
||||
{
|
||||
LOG(log_.info()) << "WorkQueue dispatcher starting";
|
||||
|
||||
// all ongoing tasks must be completed before stopping fully
|
||||
while (not stopping_ or size() > 0) {
|
||||
std::vector<TaskType> batch;
|
||||
|
||||
{
|
||||
auto state = dispatcherState_.lock();
|
||||
|
||||
if (state->empty()) {
|
||||
state->isIdle = true;
|
||||
} else {
|
||||
for (auto count = 0uz; count < kTAKE_HIGH_PRIO and not state->high.empty(); ++count) {
|
||||
batch.push_back(std::move(state->high.front()));
|
||||
state->high.pop();
|
||||
}
|
||||
|
||||
if (not state->normal.empty()) {
|
||||
batch.push_back(std::move(state->normal.front()));
|
||||
state->normal.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not stopping_ and batch.empty()) {
|
||||
waitTimer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
boost::system::error_code ec;
|
||||
waitTimer_.async_wait(yield[ec]);
|
||||
} else {
|
||||
for (auto task : std::move(batch)) {
|
||||
util::spawn(
|
||||
ioc_,
|
||||
[this, spawnedAt = std::chrono::system_clock::now(), task = std::move(task)](auto yield) mutable {
|
||||
auto const takenAt = std::chrono::system_clock::now();
|
||||
auto const waited =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(takenAt - spawnedAt).count();
|
||||
|
||||
++queued_.get();
|
||||
durationUs_.get() += waited;
|
||||
LOG(log_.info()) << "WorkQueue wait time: " << waited << ", queue size: " << size();
|
||||
|
||||
task(yield);
|
||||
|
||||
--curSize_.get();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
boost::asio::post(ioc_.get_executor(), yield); // yield back to avoid hijacking the thread
|
||||
}
|
||||
}
|
||||
|
||||
LOG(log_.info()) << "WorkQueue dispatcher shutdown requested - time to execute onTasksComplete";
|
||||
|
||||
{
|
||||
auto onTasksComplete = onQueueEmpty_.lock();
|
||||
ASSERT(onTasksComplete->operator bool(), "onTasksComplete must be set when stopping is true.");
|
||||
onTasksComplete->operator()();
|
||||
}
|
||||
|
||||
LOG(log_.info()) << "WorkQueue dispatcher finished";
|
||||
}
|
||||
|
||||
void
|
||||
WorkQueue::requestStop(std::function<void()> onQueueEmpty)
|
||||
{
|
||||
auto handler = onQueueEmpty_.lock();
|
||||
*handler = std::move(onQueueEmpty);
|
||||
handler->setCallable(std::move(onQueueEmpty));
|
||||
|
||||
stopping_ = true;
|
||||
auto needsWakeup = false;
|
||||
|
||||
{
|
||||
auto state = dispatcherState_.lock();
|
||||
needsWakeup = std::exchange(state->isIdle, false);
|
||||
}
|
||||
|
||||
if (needsWakeup)
|
||||
boost::asio::post(strand_, [this] { waitTimer_.cancel(); });
|
||||
}
|
||||
|
||||
void
|
||||
@@ -206,6 +148,12 @@ WorkQueue::stop()
|
||||
requestStop();
|
||||
|
||||
ioc_.join();
|
||||
|
||||
{
|
||||
auto onTasksComplete = onQueueEmpty_.lock();
|
||||
ASSERT(onTasksComplete->operator bool(), "onTasksComplete must be set when stopping is true.");
|
||||
onTasksComplete->operator()();
|
||||
}
|
||||
}
|
||||
|
||||
WorkQueue
|
||||
@@ -239,4 +187,29 @@ WorkQueue::size() const
|
||||
return curSize_.get().value();
|
||||
}
|
||||
|
||||
void
|
||||
WorkQueue::executeTask(boost::asio::yield_context yield)
|
||||
{
|
||||
std::optional<TaskWithTimestamp> taskWithTimestamp;
|
||||
{
|
||||
auto state = queueState_.lock();
|
||||
taskWithTimestamp = state->popNext();
|
||||
}
|
||||
|
||||
ASSERT(
|
||||
taskWithTimestamp.has_value(),
|
||||
"Queue should not be empty as we spawn a coro with executeTask for each postCoro."
|
||||
);
|
||||
auto const takenAt = std::chrono::system_clock::now();
|
||||
auto const waited =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(takenAt - taskWithTimestamp->queuedAt).count();
|
||||
|
||||
++queued_.get();
|
||||
durationUs_.get() += waited;
|
||||
LOG(log_.info()) << "WorkQueue wait time: " << waited << ", queue size: " << size();
|
||||
|
||||
taskWithTimestamp->task(yield);
|
||||
--curSize_.get();
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -25,20 +25,19 @@
|
||||
#include "util/prometheus/Counter.hpp"
|
||||
#include "util/prometheus/Gauge.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
@@ -62,7 +61,13 @@ struct Reportable {
|
||||
*/
|
||||
class WorkQueue : public Reportable {
|
||||
using TaskType = std::function<void(boost::asio::yield_context)>;
|
||||
using QueueType = std::queue<TaskType>;
|
||||
|
||||
struct TaskWithTimestamp {
|
||||
TaskType task;
|
||||
std::chrono::system_clock::time_point queuedAt;
|
||||
};
|
||||
|
||||
using QueueType = std::queue<TaskWithTimestamp>;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -74,21 +79,21 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
struct DispatcherState {
|
||||
struct QueueState {
|
||||
QueueType high;
|
||||
QueueType normal;
|
||||
|
||||
bool isIdle = false;
|
||||
size_t highPriorityCounter = 0;
|
||||
|
||||
void
|
||||
push(Priority priority, auto&& task)
|
||||
push(Priority priority, TaskType&& task)
|
||||
{
|
||||
auto& queue = [this, priority] -> QueueType& {
|
||||
if (priority == Priority::High)
|
||||
return high;
|
||||
return normal;
|
||||
}();
|
||||
queue.push(std::forward<decltype(task)>(task));
|
||||
queue.push(TaskWithTimestamp{.task = std::move(task), .queuedAt = std::chrono::system_clock::now()});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -96,6 +101,26 @@ private:
|
||||
{
|
||||
return high.empty() and normal.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<TaskWithTimestamp>
|
||||
popNext()
|
||||
{
|
||||
if (not high.empty() and (highPriorityCounter < kTAKE_HIGH_PRIO or normal.empty())) {
|
||||
auto taskWithTimestamp = std::move(high.front());
|
||||
high.pop();
|
||||
++highPriorityCounter;
|
||||
return taskWithTimestamp;
|
||||
}
|
||||
|
||||
if (not normal.empty()) {
|
||||
auto taskWithTimestamp = std::move(normal.front());
|
||||
normal.pop();
|
||||
highPriorityCounter = 0;
|
||||
return taskWithTimestamp;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -110,14 +135,26 @@ private:
|
||||
|
||||
util::Logger log_{"RPC"};
|
||||
boost::asio::thread_pool ioc_;
|
||||
boost::asio::strand<boost::asio::thread_pool::executor_type> strand_;
|
||||
bool hasDispatcher_ = false;
|
||||
|
||||
std::atomic_bool stopping_;
|
||||
std::atomic_bool processingStarted_{false};
|
||||
|
||||
util::Mutex<std::function<void()>> onQueueEmpty_;
|
||||
util::Mutex<DispatcherState> dispatcherState_;
|
||||
boost::asio::steady_timer waitTimer_;
|
||||
class OneTimeCallable {
|
||||
std::function<void()> func_;
|
||||
bool called_{false};
|
||||
|
||||
public:
|
||||
void
|
||||
setCallable(std::function<void()> func);
|
||||
|
||||
void
|
||||
operator()();
|
||||
|
||||
explicit
|
||||
operator bool() const;
|
||||
};
|
||||
util::Mutex<OneTimeCallable> onQueueEmpty_;
|
||||
util::Mutex<QueueState> queueState_;
|
||||
|
||||
public:
|
||||
struct DontStartProcessingTag {};
|
||||
@@ -211,7 +248,7 @@ public:
|
||||
|
||||
private:
|
||||
void
|
||||
dispatcherLoop(boost::asio::yield_context yield);
|
||||
executeTask(boost::asio::yield_context yield);
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -316,8 +316,11 @@ tag_invoke(boost::json::value_to_tag<AMMInfoHandler::Input>, boost::json::value
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(asset)))
|
||||
input.issue1 = parseIssue(jsonObject.at(JS(asset)).as_object());
|
||||
|
||||
@@ -154,8 +154,11 @@ tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(destination_account)))
|
||||
input.destinationAccount = boost::json::value_to<std::string>(jv.at(JS(destination_account)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -128,8 +128,11 @@ tag_invoke(boost::json::value_to_tag<AccountCurrenciesHandler::Input>, boost::js
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -204,8 +204,11 @@ tag_invoke(boost::json::value_to_tag<AccountInfoHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(signer_lists)))
|
||||
input.signerLists = boost::json::value_to<JsonBool>(jsonObject.at(JS(signer_lists)));
|
||||
|
||||
@@ -215,8 +215,11 @@ tag_invoke(boost::json::value_to_tag<AccountLinesHandler::Input>, boost::json::v
|
||||
if (jsonObject.contains(JS(ignore_default)))
|
||||
input.ignoreDefault = jv.at(JS(ignore_default)).as_bool();
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ AccountMPTokenIssuancesHandler::addMPTokenIssuance(
|
||||
{
|
||||
MPTokenIssuanceResponse issuance;
|
||||
|
||||
issuance.MPTokenIssuanceID = ripple::strHex(sle.key());
|
||||
issuance.issuer = ripple::to_string(account);
|
||||
issuance.sequence = sle.getFieldU32(ripple::sfSequence);
|
||||
auto const flags = sle.getFieldU32(ripple::sfFlags);
|
||||
@@ -73,6 +74,24 @@ AccountMPTokenIssuancesHandler::addMPTokenIssuance(
|
||||
setFlag(issuance.mptCanTransfer, ripple::lsfMPTCanTransfer);
|
||||
setFlag(issuance.mptCanClawback, ripple::lsfMPTCanClawback);
|
||||
|
||||
if (sle.isFieldPresent(ripple::sfMutableFlags)) {
|
||||
auto const mutableFlags = sle.getFieldU32(ripple::sfMutableFlags);
|
||||
|
||||
auto const setMutableFlag = [&](std::optional<bool>& field, std::uint32_t mask) {
|
||||
if ((mutableFlags & mask) != 0u)
|
||||
field = true;
|
||||
};
|
||||
|
||||
setMutableFlag(issuance.mptCanMutateCanLock, ripple::lsmfMPTCanMutateCanLock);
|
||||
setMutableFlag(issuance.mptCanMutateRequireAuth, ripple::lsmfMPTCanMutateRequireAuth);
|
||||
setMutableFlag(issuance.mptCanMutateCanEscrow, ripple::lsmfMPTCanMutateCanEscrow);
|
||||
setMutableFlag(issuance.mptCanMutateCanTrade, ripple::lsmfMPTCanMutateCanTrade);
|
||||
setMutableFlag(issuance.mptCanMutateCanTransfer, ripple::lsmfMPTCanMutateCanTransfer);
|
||||
setMutableFlag(issuance.mptCanMutateCanClawback, ripple::lsmfMPTCanMutateCanClawback);
|
||||
setMutableFlag(issuance.mptCanMutateMetadata, ripple::lsmfMPTCanMutateMetadata);
|
||||
setMutableFlag(issuance.mptCanMutateTransferFee, ripple::lsmfMPTCanMutateTransferFee);
|
||||
}
|
||||
|
||||
if (sle.isFieldPresent(ripple::sfTransferFee))
|
||||
issuance.transferFee = sle.getFieldU16(ripple::sfTransferFee);
|
||||
|
||||
@@ -164,8 +183,11 @@ tag_invoke(boost::json::value_to_tag<AccountMPTokenIssuancesHandler::Input>, boo
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
@@ -198,6 +220,7 @@ tag_invoke(
|
||||
)
|
||||
{
|
||||
auto obj = boost::json::object{
|
||||
{JS(mpt_issuance_id), issuance.MPTokenIssuanceID},
|
||||
{JS(issuer), issuance.issuer},
|
||||
{JS(sequence), issuance.sequence},
|
||||
};
|
||||
@@ -224,6 +247,15 @@ tag_invoke(
|
||||
setIfPresent("mpt_can_transfer", issuance.mptCanTransfer);
|
||||
setIfPresent("mpt_can_clawback", issuance.mptCanClawback);
|
||||
|
||||
setIfPresent("mpt_can_mutate_can_lock", issuance.mptCanMutateCanLock);
|
||||
setIfPresent("mpt_can_mutate_require_auth", issuance.mptCanMutateRequireAuth);
|
||||
setIfPresent("mpt_can_mutate_can_escrow", issuance.mptCanMutateCanEscrow);
|
||||
setIfPresent("mpt_can_mutate_can_trade", issuance.mptCanMutateCanTrade);
|
||||
setIfPresent("mpt_can_mutate_can_transfer", issuance.mptCanMutateCanTransfer);
|
||||
setIfPresent("mpt_can_mutate_can_clawback", issuance.mptCanMutateCanClawback);
|
||||
setIfPresent("mpt_can_mutate_metadata", issuance.mptCanMutateMetadata);
|
||||
setIfPresent("mpt_can_mutate_transfer_fee", issuance.mptCanMutateTransferFee);
|
||||
|
||||
jv = std::move(obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
* @brief A struct to hold data for one MPTokenIssuance response.
|
||||
*/
|
||||
struct MPTokenIssuanceResponse {
|
||||
std::string MPTokenIssuanceID;
|
||||
std::string issuer;
|
||||
uint32_t sequence{};
|
||||
|
||||
@@ -80,6 +81,15 @@ public:
|
||||
std::optional<bool> mptCanTrade;
|
||||
std::optional<bool> mptCanTransfer;
|
||||
std::optional<bool> mptCanClawback;
|
||||
|
||||
std::optional<bool> mptCanMutateCanLock;
|
||||
std::optional<bool> mptCanMutateRequireAuth;
|
||||
std::optional<bool> mptCanMutateCanEscrow;
|
||||
std::optional<bool> mptCanMutateCanTrade;
|
||||
std::optional<bool> mptCanMutateCanTransfer;
|
||||
std::optional<bool> mptCanMutateCanClawback;
|
||||
std::optional<bool> mptCanMutateMetadata;
|
||||
std::optional<bool> mptCanMutateTransferFee;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ AccountMPTokensHandler::addMPToken(std::vector<MPTokenResponse>& mpts, ripple::S
|
||||
MPTokenResponse token{};
|
||||
auto const flags = sle.getFieldU32(ripple::sfFlags);
|
||||
|
||||
token.MPTokenID = ripple::strHex(sle.key());
|
||||
token.account = ripple::to_string(sle.getAccountID(ripple::sfAccount));
|
||||
token.MPTokenIssuanceID = ripple::strHex(sle.getFieldH192(ripple::sfMPTokenIssuanceID));
|
||||
token.MPTAmount = sle.getFieldU64(ripple::sfMPTAmount);
|
||||
@@ -139,8 +140,11 @@ tag_invoke(boost::json::value_to_tag<AccountMPTokensHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
@@ -167,6 +171,7 @@ void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountMPTokensHandler::MPTokenResponse const& mptoken)
|
||||
{
|
||||
auto obj = boost::json::object{
|
||||
{"mpt_id", mptoken.MPTokenID},
|
||||
{JS(account), mptoken.account},
|
||||
{JS(mpt_issuance_id), mptoken.MPTokenIssuanceID},
|
||||
{JS(mpt_amount), mptoken.MPTAmount},
|
||||
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
* @brief A struct to hold data for one MPToken response.
|
||||
*/
|
||||
struct MPTokenResponse {
|
||||
std::string MPTokenID;
|
||||
std::string account;
|
||||
std::string MPTokenIssuanceID;
|
||||
uint64_t MPTAmount{};
|
||||
|
||||
@@ -157,8 +157,11 @@ tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(limit)))
|
||||
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user