mirror of
https://github.com/XRPLF/clio.git
synced 2026-02-23 15:22:45 +00:00
Compare commits
48 Commits
2.7.0-rc2
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86f3c28a9c | ||
|
|
2d6f82c27f | ||
|
|
6ba58f42f0 | ||
|
|
480264ff8f | ||
|
|
584d2bb5f2 | ||
|
|
9d3dbce73b | ||
|
|
8b6f65f0b7 | ||
|
|
91aba853af | ||
|
|
af03a2fbe4 | ||
|
|
61c47c8efd | ||
|
|
8f36b1d5ca | ||
|
|
9fd15eb08b | ||
|
|
cf77a10555 | ||
|
|
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 |
@@ -22,7 +22,7 @@ BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: WebKit
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
ColumnLimit: 120
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
||||
@@ -2,15 +2,6 @@ _help_parse: Options affecting listfile parsing
|
||||
parse:
|
||||
_help_additional_commands:
|
||||
- Specify structure for custom cmake functions
|
||||
additional_commands:
|
||||
foo:
|
||||
flags:
|
||||
- BAR
|
||||
- BAZ
|
||||
kwargs:
|
||||
HEADERS: "*"
|
||||
SOURCES: "*"
|
||||
DEPENDS: "*"
|
||||
_help_override_spec:
|
||||
- Override configurations per-command where available
|
||||
override_spec: {}
|
||||
@@ -30,7 +21,7 @@ format:
|
||||
line_width: 120
|
||||
_help_tab_size:
|
||||
- How many spaces to tab for indent
|
||||
tab_size: 2
|
||||
tab_size: 4
|
||||
_help_use_tabchars:
|
||||
- If true, lines are indented using tab characters (utf-8
|
||||
- 0x09) instead of <tab_size> space characters (utf-8 0x20).
|
||||
@@ -70,19 +61,19 @@ format:
|
||||
_help_dangle_parens:
|
||||
- If a statement is wrapped to more than one line, than dangle
|
||||
- the closing parenthesis on its own line.
|
||||
dangle_parens: true
|
||||
dangle_parens: false
|
||||
_help_dangle_align:
|
||||
- If the trailing parenthesis must be 'dangled' on its on
|
||||
- "line, then align it to this reference: `prefix`: the start"
|
||||
- "of the statement, `prefix-indent`: the start of the"
|
||||
- "statement, plus one indentation level, `child`: align to"
|
||||
- "of the statement, `prefix-indent`: the start of the"
|
||||
- "statement, plus one indentation level, `child`: align to"
|
||||
- the column of the arguments
|
||||
dangle_align: prefix
|
||||
_help_min_prefix_chars:
|
||||
- If the statement spelling length (including space and
|
||||
- parenthesis) is smaller than this amount, then force reject
|
||||
- nested layouts.
|
||||
min_prefix_chars: 4
|
||||
min_prefix_chars: 18
|
||||
_help_max_prefix_chars:
|
||||
- If the statement spelling length (including space and
|
||||
- parenthesis) is larger than the tab width by more than this
|
||||
@@ -127,7 +118,7 @@ _help_markup: Options affecting comment reflow and formatting.
|
||||
markup:
|
||||
_help_bullet_char:
|
||||
- What character to use for bulleted lists
|
||||
bullet_char: "*"
|
||||
bullet_char: "-"
|
||||
_help_enum_char:
|
||||
- What character to use as punctuation after numerals in an
|
||||
- enumerated list
|
||||
|
||||
@@ -34,14 +34,14 @@ runs:
|
||||
steps:
|
||||
- name: Login to DockerHub
|
||||
if: ${{ inputs.push_image == 'true' && inputs.dockerhub_repo != '' }}
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USER }}
|
||||
password: ${{ env.DOCKERHUB_PW }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.push_image == 'true' }}
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -50,7 +50,7 @@ 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@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
id: meta
|
||||
|
||||
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/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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
]
|
||||
LINUX_COMPILERS = ["gcc", "clang"]
|
||||
|
||||
|
||||
46
.github/scripts/execute-tests-under-sanitizer.sh
vendored
46
.github/scripts/execute-tests-under-sanitizer.sh
vendored
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# Note: This script is intended to be run from the root of the repository.
|
||||
#
|
||||
# This script runs each unit-test separately and generates reports from the currently active sanitizer.
|
||||
# Output is saved in ./.sanitizer-report in the root of the repository
|
||||
|
||||
if [[ -z "$1" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
ERROR
|
||||
-----------------------------------------------------------------------------
|
||||
Path to clio_tests should be passed as first argument to the script.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TEST_BINARY=$1
|
||||
|
||||
if [[ ! -f "$TEST_BINARY" ]]; then
|
||||
echo "Test binary not found: $TEST_BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TESTS=$($TEST_BINARY --gtest_list_tests | awk '/^ / {print suite $1} !/^ / {suite=$1}')
|
||||
|
||||
OUTPUT_DIR="./.sanitizer-report"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
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
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "'$TEST' failed a sanitizer check."
|
||||
else
|
||||
rm "$OUTPUT_FILE"
|
||||
fi
|
||||
done
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download Clio binary from artifact
|
||||
if: ${{ inputs.artifact_name != null }}
|
||||
|
||||
9
.github/workflows/build.yml
vendored
9
.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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }',
|
||||
'{ "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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
download_ccache: true
|
||||
@@ -97,10 +98,10 @@ jobs:
|
||||
needs: build-and-test
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
|
||||
12
.github/workflows/check-libxrpl.yml
vendored
12
.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:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Update libXRPL version requirement
|
||||
run: |
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
needs: build
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Create an issue
|
||||
uses: ./.github/actions/create-issue
|
||||
|
||||
10
.github/workflows/clang-tidy.yml
vendored
10
.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:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -39,14 +39,14 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Run conan
|
||||
uses: ./.github/actions/conan
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Create PR with fixes
|
||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
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:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir build_docs
|
||||
|
||||
40
.github/workflows/nightly.yml
vendored
40
.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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -67,14 +79,16 @@ 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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
@@ -83,11 +97,13 @@ jobs:
|
||||
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
|
||||
@@ -95,7 +111,7 @@ jobs:
|
||||
include:
|
||||
- os: heavy
|
||||
conan_profile: clang
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
static: true
|
||||
- os: macos15
|
||||
conan_profile: apple-clang
|
||||
@@ -114,17 +130,7 @@ 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, package, get_date]
|
||||
@@ -163,7 +169,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- 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@8b19d3462e52cd8ea4d76b4c8d0f7533e7469c15
|
||||
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@320be44621ca2a080f05aeb15817c44b84518108
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
7
.github/workflows/release.yml
vendored
7
.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:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -43,7 +43,7 @@ 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
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
@@ -60,6 +60,7 @@ jobs:
|
||||
static: true
|
||||
upload_clio_server: false
|
||||
package: true
|
||||
version: ${{ github.event_name == 'push' && github.ref_name || '' }}
|
||||
targets: package
|
||||
analyze_build_time: false
|
||||
|
||||
|
||||
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
|
||||
|
||||
46
.github/workflows/reusable-build.yml
vendored
46
.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
|
||||
@@ -90,18 +90,14 @@ jobs:
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- 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/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
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
|
||||
@@ -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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ steps.cache_key.outputs.key }}
|
||||
@@ -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
|
||||
|
||||
11
.github/workflows/reusable-release.yml
vendored
11
.github/workflows/reusable-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
release:
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -55,14 +55,14 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
@@ -91,8 +91,7 @@ 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"
|
||||
git-cliff "${BASE_COMMIT}..HEAD" --ignore-tags "nightly|-b|-rc" >> "${RUNNER_TEMP}/release_notes.md"
|
||||
|
||||
- name: Upload release notes
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
|
||||
60
.github/workflows/reusable-test.yml
vendored
60
.github/workflows/reusable-test.yml
vendored
@@ -45,16 +45,12 @@ jobs:
|
||||
|
||||
if: ${{ inputs.run_unit_tests }}
|
||||
|
||||
env:
|
||||
# TODO: remove completely when we have fixed all currently existing issues with sanitizers
|
||||
SANITIZER_IGNORE_ERRORS: ${{ endsWith(inputs.conan_profile, '.tsan') }}
|
||||
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -65,34 +61,13 @@ jobs:
|
||||
- name: Make clio_tests executable
|
||||
run: chmod +x ./clio_tests
|
||||
|
||||
- name: Run clio_tests (regular)
|
||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'false' }}
|
||||
- name: Run clio_tests
|
||||
continue-on-error: true
|
||||
id: run_clio_tests
|
||||
run: ./clio_tests
|
||||
|
||||
- name: Run clio_tests (sanitizer errors ignored)
|
||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' }}
|
||||
run: ./.github/scripts/execute-tests-under-sanitizer.sh ./clio_tests
|
||||
|
||||
- name: Check for sanitizer report
|
||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' }}
|
||||
id: check_report
|
||||
run: |
|
||||
if ls .sanitizer-report/* 1> /dev/null 2>&1; then
|
||||
echo "found_report=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "found_report=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Upload sanitizer report
|
||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'true' && steps.check_report.outputs.found_report == 'true' }}
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: sanitizer_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: .sanitizer-report/*
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Create an issue
|
||||
if: ${{ false && env.SANITIZER_IGNORE_ERRORS == 'true' && steps.check_report.outputs.found_report == 'true' }}
|
||||
if: ${{ steps.run_clio_tests.outcome == 'failure' && endsWith(inputs.conan_profile, 'san') }}
|
||||
uses: ./.github/actions/create-issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -100,10 +75,13 @@ jobs:
|
||||
labels: "bug"
|
||||
title: "[${{ inputs.conan_profile }}] reported issues"
|
||||
body: >
|
||||
Clio tests failed one or more sanitizer checks when built with ${{ inputs.conan_profile }}`.
|
||||
Clio tests failed one or more sanitizer checks when built with `${{ inputs.conan_profile }}`.
|
||||
|
||||
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/
|
||||
Reports are available as artifacts.
|
||||
|
||||
- name: Fail the job if clio_tests failed
|
||||
if: ${{ steps.run_clio_tests.outcome == 'failure' }}
|
||||
run: exit 1
|
||||
|
||||
integration_tests:
|
||||
name: Integration testing
|
||||
@@ -126,11 +104,17 @@ jobs:
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
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,8 +126,12 @@ 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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
3
.github/workflows/sanitizers.yml
vendored
3
.github/workflows/sanitizers.yml
vendored
@@ -15,7 +15,6 @@ on:
|
||||
- ".github/actions/**"
|
||||
- "!.github/actions/build-docker-image/**"
|
||||
- "!.github/actions/create-issue/**"
|
||||
- .github/scripts/execute-tests-under-sanitizer.sh
|
||||
|
||||
- CMakeLists.txt
|
||||
- conanfile.py
|
||||
@@ -44,7 +43,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
42
.github/workflows/update-docker-ci.yml
vendored
42
.github/workflows/update-docker-ci.yml
vendored
@@ -56,11 +56,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -94,11 +94,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -132,20 +132,20 @@ jobs:
|
||||
needs: [repo, gcc-amd64, gcc-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
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' }}
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_PW }}
|
||||
@@ -183,11 +183,11 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: "docker/compilers/clang/**"
|
||||
|
||||
@@ -219,11 +219,11 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -250,11 +250,11 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -281,20 +281,20 @@ jobs:
|
||||
needs: [repo, tools-amd64, tools-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
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' }}
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -316,7 +316,7 @@ jobs:
|
||||
needs: [repo, tools-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/build-docker-image
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
8
.github/workflows/upload-conan-deps.yml
vendored
8
.github/workflows/upload-conan-deps.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Calculate conan matrix
|
||||
id: set-matrix
|
||||
@@ -75,12 +75,12 @@ jobs:
|
||||
CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
disable_ccache: true
|
||||
enable_ccache: false
|
||||
|
||||
- name: Setup conan on macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,8 +4,11 @@
|
||||
.build
|
||||
.cache
|
||||
.vscode
|
||||
.zed
|
||||
.python-version
|
||||
.DS_Store
|
||||
.sanitizer-report
|
||||
CMakeUserPresets.json
|
||||
config.json
|
||||
CLAUDE.md
|
||||
.claude/**
|
||||
|
||||
@@ -29,12 +29,12 @@ repos:
|
||||
|
||||
# Autoformat: YAML, JSON, Markdown, etc.
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 3c603eae8faac85303ae675fd33325cff699a797 # frozen: v3.7.3
|
||||
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: c8fd5003603dd6f12447314ecd935ba87c09aff5 # frozen: v0.46.0
|
||||
rev: 76b3d32d3f4b965e1d6425253c59407420ae2c43 # frozen: v0.47.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: LICENSE.md
|
||||
@@ -59,7 +59,7 @@ repos:
|
||||
]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 2892f1f81088477370d4fbc56545c05d33d2493f # frozen: 25.11.0
|
||||
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
@@ -94,7 +94,7 @@ repos:
|
||||
language: script
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: 4c26f99731e7c22a047c35224150ee9e43d7c03e # frozen: v21.1.6
|
||||
rev: 75ca4ad908dc4a99f57921f29b7e6c1521e10b26 # frozen: v21.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(clio VERSION ${CLIO_VERSION} HOMEPAGE_URL "https://github.com/XRPLF/clio"
|
||||
DESCRIPTION "An XRP Ledger API Server"
|
||||
)
|
||||
DESCRIPTION "An XRP Ledger API Server")
|
||||
|
||||
# =========================== Options ====================================== #
|
||||
option(verbose "Verbose build" FALSE)
|
||||
@@ -36,7 +35,7 @@ target_compile_features(clio_options INTERFACE cxx_std_23) # Clio needs c++23 bu
|
||||
target_include_directories(clio_options INTERFACE ${CMAKE_SOURCE_DIR}/src)
|
||||
|
||||
if (verbose)
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
endif ()
|
||||
|
||||
# Clio tweaks and checks
|
||||
@@ -58,36 +57,36 @@ add_subdirectory(src)
|
||||
add_subdirectory(tests)
|
||||
|
||||
if (benchmark)
|
||||
add_subdirectory(benchmarks)
|
||||
add_subdirectory(benchmarks)
|
||||
endif ()
|
||||
|
||||
# Enable selected sanitizer if enabled via `san`
|
||||
if (san)
|
||||
set(SUPPORTED_SANITIZERS "address" "thread" "memory" "undefined")
|
||||
if (NOT san IN_LIST SUPPORTED_SANITIZERS)
|
||||
message(FATAL_ERROR "Error: Unsupported sanitizer '${san}'. Supported values are: ${SUPPORTED_SANITIZERS}.")
|
||||
endif ()
|
||||
set(SUPPORTED_SANITIZERS "address" "thread" "memory" "undefined")
|
||||
if (NOT san IN_LIST SUPPORTED_SANITIZERS)
|
||||
message(FATAL_ERROR "Error: Unsupported sanitizer '${san}'. Supported values are: ${SUPPORTED_SANITIZERS}.")
|
||||
endif ()
|
||||
|
||||
# Sanitizers recommend minimum of -O1 for reasonable performance so we enable it for debug builds
|
||||
set(SAN_OPTIMIZATION_FLAG "")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(SAN_OPTIMIZATION_FLAG -O1)
|
||||
endif ()
|
||||
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
||||
# Sanitizers recommend minimum of -O1 for reasonable performance so we enable it for debug builds
|
||||
set(SAN_OPTIMIZATION_FLAG "")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(SAN_OPTIMIZATION_FLAG -O1)
|
||||
endif ()
|
||||
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
||||
|
||||
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||
endif ()
|
||||
|
||||
# Generate `docs` target for doxygen documentation if enabled Note: use `make docs` to generate the documentation
|
||||
if (docs)
|
||||
add_subdirectory(docs)
|
||||
add_subdirectory(docs)
|
||||
endif ()
|
||||
|
||||
include(install/install)
|
||||
if (package)
|
||||
include(ClioPackage)
|
||||
include(ClioPackage)
|
||||
endif ()
|
||||
|
||||
if (snapshot)
|
||||
add_subdirectory(tools/snapshot)
|
||||
add_subdirectory(tools/snapshot)
|
||||
endif ()
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
add_executable(clio_benchmark)
|
||||
|
||||
target_sources(
|
||||
clio_benchmark
|
||||
PRIVATE # Common
|
||||
Main.cpp
|
||||
Playground.cpp
|
||||
# ExecutionContext
|
||||
util/async/ExecutionContextBenchmarks.cpp
|
||||
# Logger
|
||||
util/log/LoggerBenchmark.cpp
|
||||
# WorkQueue
|
||||
rpc/WorkQueueBenchmarks.cpp
|
||||
)
|
||||
target_sources(clio_benchmark
|
||||
PRIVATE # Common
|
||||
Main.cpp
|
||||
Playground.cpp
|
||||
# ExecutionContext
|
||||
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 clio_rpc 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})
|
||||
|
||||
@@ -28,19 +28,17 @@
|
||||
#include "util/prometheus/Prometheus.hpp"
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.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;
|
||||
@@ -53,12 +51,15 @@ auto const kCONFIG = ClioConfigDefinition{
|
||||
{"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.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.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")},
|
||||
};
|
||||
|
||||
@@ -80,36 +81,61 @@ benchmarkWorkQueue(benchmark::State& state)
|
||||
{
|
||||
init();
|
||||
|
||||
auto const total = static_cast<size_t>(state.range(0));
|
||||
auto const numThreads = static_cast<uint32_t>(state.range(1));
|
||||
auto const maxSize = static_cast<uint32_t>(state.range(2));
|
||||
auto const delayMs = static_cast<uint32_t>(state.range(3));
|
||||
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(numThreads, maxSize);
|
||||
WorkQueue queue(wqThreads, maxQueueSize);
|
||||
state.ResumeTiming();
|
||||
|
||||
for (auto i = 0uz; i < total; ++i) {
|
||||
totalQueued += static_cast<std::size_t>(queue.postCoro(
|
||||
[&delayMs, &totalExecuted](auto yield) {
|
||||
++totalExecuted;
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(clientThreads);
|
||||
|
||||
boost::asio::steady_timer timer(yield.get_executor(), std::chrono::milliseconds{delayMs});
|
||||
timer.async_wait(yield);
|
||||
},
|
||||
/* isWhiteListed = */ false
|
||||
));
|
||||
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 <= total, "Queued more than requested");
|
||||
ASSERT(totalQueued >= maxSize, "Queued less than maxSize");
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,5 +149,5 @@ benchmarkWorkQueue(benchmark::State& state)
|
||||
*/
|
||||
// TODO: figure out what happens on 1 thread
|
||||
BENCHMARK(benchmarkWorkQueue)
|
||||
->ArgsProduct({{1'000, 10'000, 100'000}, {2, 4, 8}, {0, 5'000}, {10, 100, 250}})
|
||||
->ArgsProduct({{2, 4, 8, 16}, {0, 5'000}, {4, 8, 16}, {1'000, 10'000}, {10, 100, 250}})
|
||||
->Unit(benchmark::kMillisecond);
|
||||
|
||||
@@ -62,8 +62,9 @@ uniqueLogDir()
|
||||
{
|
||||
auto const epochTime = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
auto const tmpDir = std::filesystem::temp_directory_path();
|
||||
std::string const dirName =
|
||||
fmt::format("logs_{}", std::chrono::duration_cast<std::chrono::microseconds>(epochTime).count());
|
||||
std::string const dirName = fmt::format(
|
||||
"logs_{}", std::chrono::duration_cast<std::chrono::microseconds>(epochTime).count()
|
||||
);
|
||||
return tmpDir / "clio_benchmark" / dirName;
|
||||
}
|
||||
|
||||
@@ -108,7 +109,8 @@ benchmarkConcurrentFileLogging(benchmark::State& state)
|
||||
channel, fileSink, spdlog::thread_pool(), spdlog::async_overflow_policy::block
|
||||
);
|
||||
spdlog::register_logger(logger);
|
||||
Logger const threadLogger = BenchmarkLoggingInitializer::getLogger(std::move(logger));
|
||||
Logger const threadLogger =
|
||||
BenchmarkLoggingInitializer::getLogger(std::move(logger));
|
||||
|
||||
barrier.arrive_and_wait();
|
||||
|
||||
@@ -124,13 +126,16 @@ benchmarkConcurrentFileLogging(benchmark::State& state)
|
||||
spdlog::shutdown();
|
||||
|
||||
auto const end = std::chrono::high_resolution_clock::now();
|
||||
state.SetIterationTime(std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count());
|
||||
state.SetIterationTime(
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count()
|
||||
);
|
||||
|
||||
std::filesystem::remove_all(logDir);
|
||||
}
|
||||
|
||||
auto const totalMessages = numThreads * messagesPerThread;
|
||||
state.counters["TotalMessagesRate"] = benchmark::Counter(totalMessages, benchmark::Counter::kIsRate);
|
||||
state.counters["TotalMessagesRate"] =
|
||||
benchmark::Counter(totalMessages, benchmark::Counter::kIsRate);
|
||||
state.counters["Threads"] = numThreads;
|
||||
state.counters["MessagesPerThread"] = messagesPerThread;
|
||||
}
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
find_program(CCACHE_PATH "ccache")
|
||||
if (CCACHE_PATH)
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}")
|
||||
message(STATUS "Using ccache: ${CCACHE_PATH}")
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}")
|
||||
message(STATUS "Using ccache: ${CCACHE_PATH}")
|
||||
endif ()
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)
|
||||
message(FATAL_ERROR "Clang 16+ required for building clio")
|
||||
endif ()
|
||||
set(is_clang TRUE)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)
|
||||
message(FATAL_ERROR "Clang 16+ required for building clio")
|
||||
endif ()
|
||||
set(is_clang TRUE)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15)
|
||||
message(FATAL_ERROR "AppleClang 15+ required for building clio")
|
||||
endif ()
|
||||
set(is_appleclang TRUE)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15)
|
||||
message(FATAL_ERROR "AppleClang 15+ required for building clio")
|
||||
endif ()
|
||||
set(is_appleclang TRUE)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12)
|
||||
message(FATAL_ERROR "GCC 12+ required for building clio")
|
||||
endif ()
|
||||
set(is_gcc TRUE)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12)
|
||||
message(FATAL_ERROR "GCC 12+ required for building clio")
|
||||
endif ()
|
||||
set(is_gcc TRUE)
|
||||
else ()
|
||||
message(FATAL_ERROR "Supported compilers: AppleClang 15+, Clang 16+, GCC 12+")
|
||||
message(FATAL_ERROR "Supported compilers: AppleClang 15+, Clang 16+, GCC 12+")
|
||||
endif ()
|
||||
|
||||
if (san)
|
||||
string(TOLOWER ${san} san)
|
||||
set(SAN_FLAG "-fsanitize=${san}")
|
||||
set(SAN_LIB "")
|
||||
if (is_gcc)
|
||||
if (san STREQUAL "address")
|
||||
set(SAN_LIB "asan")
|
||||
elseif (san STREQUAL "thread")
|
||||
set(SAN_LIB "tsan")
|
||||
elseif (san STREQUAL "memory")
|
||||
set(SAN_LIB "msan")
|
||||
elseif (san STREQUAL "undefined")
|
||||
set(SAN_LIB "ubsan")
|
||||
string(TOLOWER ${san} san)
|
||||
set(SAN_FLAG "-fsanitize=${san}")
|
||||
set(SAN_LIB "")
|
||||
if (is_gcc)
|
||||
if (san STREQUAL "address")
|
||||
set(SAN_LIB "asan")
|
||||
elseif (san STREQUAL "thread")
|
||||
set(SAN_LIB "tsan")
|
||||
elseif (san STREQUAL "memory")
|
||||
set(SAN_LIB "msan")
|
||||
elseif (san STREQUAL "undefined")
|
||||
set(SAN_LIB "ubsan")
|
||||
endif ()
|
||||
endif ()
|
||||
set(_saved_CRL ${CMAKE_REQUIRED_LIBRARIES})
|
||||
set(CMAKE_REQUIRED_LIBRARIES "${SAN_FLAG};${SAN_LIB}")
|
||||
check_cxx_compiler_flag(${SAN_FLAG} COMPILER_SUPPORTS_SAN)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${_saved_CRL})
|
||||
if (NOT COMPILER_SUPPORTS_SAN)
|
||||
message(FATAL_ERROR "${san} sanitizer does not seem to be supported by your compiler")
|
||||
endif ()
|
||||
endif ()
|
||||
set(_saved_CRL ${CMAKE_REQUIRED_LIBRARIES})
|
||||
set(CMAKE_REQUIRED_LIBRARIES "${SAN_FLAG};${SAN_LIB}")
|
||||
check_cxx_compiler_flag(${SAN_FLAG} COMPILER_SUPPORTS_SAN)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${_saved_CRL})
|
||||
if (NOT COMPILER_SUPPORTS_SAN)
|
||||
message(FATAL_ERROR "${san} sanitizer does not seem to be supported by your compiler")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
if (lint)
|
||||
|
||||
# Find clang-tidy binary
|
||||
if (DEFINED ENV{CLIO_CLANG_TIDY_BIN})
|
||||
set(_CLANG_TIDY_BIN $ENV{CLIO_CLANG_TIDY_BIN})
|
||||
if ((NOT EXISTS ${_CLANG_TIDY_BIN}) OR IS_DIRECTORY ${_CLANG_TIDY_BIN})
|
||||
message(FATAL_ERROR "$ENV{CLIO_CLANG_TIDY_BIN} no such file. Check CLIO_CLANG_TIDY_BIN env variable")
|
||||
# Find clang-tidy binary
|
||||
if (DEFINED ENV{CLIO_CLANG_TIDY_BIN})
|
||||
set(_CLANG_TIDY_BIN $ENV{CLIO_CLANG_TIDY_BIN})
|
||||
if ((NOT EXISTS ${_CLANG_TIDY_BIN}) OR IS_DIRECTORY ${_CLANG_TIDY_BIN})
|
||||
message(FATAL_ERROR "$ENV{CLIO_CLANG_TIDY_BIN} no such file. Check CLIO_CLANG_TIDY_BIN env variable")
|
||||
endif ()
|
||||
message(STATUS "Using clang-tidy from CLIO_CLANG_TIDY_BIN")
|
||||
else ()
|
||||
find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-20" "clang-tidy" REQUIRED)
|
||||
endif ()
|
||||
message(STATUS "Using clang-tidy from CLIO_CLANG_TIDY_BIN")
|
||||
else ()
|
||||
find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-20" "clang-tidy" REQUIRED)
|
||||
endif ()
|
||||
|
||||
if (NOT _CLANG_TIDY_BIN)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"clang-tidy binary not found. Please set the CLIO_CLANG_TIDY_BIN environment variable or install clang-tidy."
|
||||
)
|
||||
endif ()
|
||||
if (NOT _CLANG_TIDY_BIN)
|
||||
message(FATAL_ERROR "clang-tidy binary not found. Please set the CLIO_CLANG_TIDY_BIN environment variable or install clang-tidy."
|
||||
)
|
||||
endif ()
|
||||
|
||||
# Support for https://github.com/matus-chochlik/ctcache
|
||||
find_program(CLANG_TIDY_CACHE_PATH NAMES "clang-tidy-cache")
|
||||
if (CLANG_TIDY_CACHE_PATH)
|
||||
set(_CLANG_TIDY_CMD "${CLANG_TIDY_CACHE_PATH};${_CLANG_TIDY_BIN}"
|
||||
CACHE STRING "A combined command to run clang-tidy with caching wrapper"
|
||||
)
|
||||
else ()
|
||||
set(_CLANG_TIDY_CMD "${_CLANG_TIDY_BIN}")
|
||||
endif ()
|
||||
# Support for https://github.com/matus-chochlik/ctcache
|
||||
find_program(CLANG_TIDY_CACHE_PATH NAMES "clang-tidy-cache")
|
||||
if (CLANG_TIDY_CACHE_PATH)
|
||||
set(_CLANG_TIDY_CMD "${CLANG_TIDY_CACHE_PATH};${_CLANG_TIDY_BIN}"
|
||||
CACHE STRING "A combined command to run clang-tidy with caching wrapper")
|
||||
else ()
|
||||
set(_CLANG_TIDY_CMD "${_CLANG_TIDY_BIN}")
|
||||
endif ()
|
||||
|
||||
set(CMAKE_CXX_CLANG_TIDY "${_CLANG_TIDY_CMD};--quiet")
|
||||
message(STATUS "Using clang-tidy: ${CMAKE_CXX_CLANG_TIDY}")
|
||||
set(CMAKE_CXX_CLANG_TIDY "${_CLANG_TIDY_CMD};--quiet")
|
||||
message(STATUS "Using clang-tidy: ${CMAKE_CXX_CLANG_TIDY}")
|
||||
endif ()
|
||||
|
||||
@@ -1,47 +1,41 @@
|
||||
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 branch --show-current)
|
||||
execute_process(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 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 rev-parse HEAD)
|
||||
execute_process(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(GIT_COMMAND branch --show-current)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
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)
|
||||
|
||||
set(GIT_COMMAND rev-parse --short HEAD)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE REV
|
||||
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}")
|
||||
|
||||
set(CLIO_VERSION "${DATE}-${BRANCH}-${REV}")
|
||||
set(DOC_CLIO_VERSION "develop")
|
||||
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 ()
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(CLIO_VERSION "${CLIO_VERSION}+DEBUG")
|
||||
set(CLIO_VERSION "${CLIO_VERSION}+DEBUG")
|
||||
endif ()
|
||||
|
||||
message(STATUS "Build version: ${CLIO_VERSION}")
|
||||
|
||||
@@ -107,76 +107,69 @@ option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE)
|
||||
find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
|
||||
|
||||
if (DEFINED CODE_COVERAGE_GCOV_TOOL)
|
||||
set(GCOV_TOOL "${CODE_COVERAGE_GCOV_TOOL}")
|
||||
set(GCOV_TOOL "${CODE_COVERAGE_GCOV_TOOL}")
|
||||
elseif (DEFINED ENV{CODE_COVERAGE_GCOV_TOOL})
|
||||
set(GCOV_TOOL "$ENV{CODE_COVERAGE_GCOV_TOOL}")
|
||||
set(GCOV_TOOL "$ENV{CODE_COVERAGE_GCOV_TOOL}")
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||
if (APPLE)
|
||||
execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
else ()
|
||||
find_program(LLVMCOV_PATH llvm-cov)
|
||||
endif ()
|
||||
if (LLVMCOV_PATH)
|
||||
set(GCOV_TOOL "${LLVMCOV_PATH} gcov")
|
||||
endif ()
|
||||
if (APPLE)
|
||||
execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
else ()
|
||||
find_program(LLVMCOV_PATH llvm-cov)
|
||||
endif ()
|
||||
if (LLVMCOV_PATH)
|
||||
set(GCOV_TOOL "${LLVMCOV_PATH} gcov")
|
||||
endif ()
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
||||
find_program(GCOV_PATH gcov)
|
||||
set(GCOV_TOOL "${GCOV_PATH}")
|
||||
find_program(GCOV_PATH gcov)
|
||||
set(GCOV_TOOL "${GCOV_PATH}")
|
||||
endif ()
|
||||
|
||||
# Check supported compiler (Clang, GNU and Flang)
|
||||
get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
|
||||
foreach (LANG ${LANGUAGES})
|
||||
if ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||
if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
|
||||
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
|
||||
if ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||
if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
|
||||
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
|
||||
endif ()
|
||||
elseif (NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES
|
||||
"(LLVM)?[Ff]lang")
|
||||
message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...")
|
||||
endif ()
|
||||
elseif (NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES
|
||||
"(LLVM)?[Ff]lang"
|
||||
)
|
||||
message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...")
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
|
||||
if (HAVE_cxx_fprofile_abs_path)
|
||||
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||
endif ()
|
||||
include(CheckCCompilerFlag)
|
||||
check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
|
||||
if (HAVE_c_fprofile_abs_path)
|
||||
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||
endif ()
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
|
||||
if (HAVE_cxx_fprofile_abs_path)
|
||||
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||
endif ()
|
||||
include(CheckCCompilerFlag)
|
||||
check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
|
||||
if (HAVE_c_fprofile_abs_path)
|
||||
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(CMAKE_Fortran_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS}
|
||||
CACHE STRING "Flags used by the Fortran compiler during coverage builds." FORCE
|
||||
)
|
||||
CACHE STRING "Flags used by the Fortran compiler during coverage builds." FORCE)
|
||||
set(CMAKE_CXX_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS}
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE
|
||||
)
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE)
|
||||
set(CMAKE_C_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS}
|
||||
CACHE STRING "Flags used by the C compiler during coverage builds." FORCE
|
||||
)
|
||||
CACHE STRING "Flags used by the C compiler during coverage builds." FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used for linking binaries during coverage builds." FORCE)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE ""
|
||||
CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE
|
||||
)
|
||||
mark_as_advanced(
|
||||
CMAKE_Fortran_FLAGS_COVERAGE CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
CMAKE_SHARED_LINKER_FLAGS_COVERAGE
|
||||
)
|
||||
CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE)
|
||||
mark_as_advanced(CMAKE_Fortran_FLAGS_COVERAGE CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE
|
||||
CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE)
|
||||
|
||||
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG))
|
||||
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
|
||||
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
|
||||
endif () # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)
|
||||
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||
link_libraries(gcov)
|
||||
link_libraries(gcov)
|
||||
endif ()
|
||||
|
||||
# Defines a target for running and collection code coverage information Builds dependencies, runs the given executable
|
||||
@@ -192,170 +185,164 @@ endif ()
|
||||
# with CMake 3.4+) ) The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the GCVOR
|
||||
# command.
|
||||
function (setup_target_for_coverage_gcovr)
|
||||
set(options NONE)
|
||||
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
|
||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
set(options NONE)
|
||||
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
|
||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (NOT GCOV_TOOL)
|
||||
message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...")
|
||||
endif ()
|
||||
if (NOT GCOV_TOOL)
|
||||
message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...")
|
||||
endif ()
|
||||
|
||||
if (NOT GCOVR_PATH)
|
||||
message(FATAL_ERROR "Could not find gcovr tool! Aborting...")
|
||||
endif ()
|
||||
if (NOT GCOVR_PATH)
|
||||
message(FATAL_ERROR "Could not find gcovr tool! Aborting...")
|
||||
endif ()
|
||||
|
||||
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
|
||||
if (DEFINED Coverage_BASE_DIRECTORY)
|
||||
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
|
||||
else ()
|
||||
set(BASEDIR ${PROJECT_SOURCE_DIR})
|
||||
endif ()
|
||||
|
||||
if (NOT DEFINED Coverage_FORMAT)
|
||||
set(Coverage_FORMAT xml)
|
||||
endif ()
|
||||
|
||||
if ("--output" IN_LIST GCOVR_ADDITIONAL_ARGS)
|
||||
message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...")
|
||||
else ()
|
||||
if ((Coverage_FORMAT STREQUAL "html-details") OR (Coverage_FORMAT STREQUAL "html-nested"))
|
||||
set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html)
|
||||
set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME})
|
||||
elseif (Coverage_FORMAT STREQUAL "html-single")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html)
|
||||
elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL "json-details")
|
||||
OR (Coverage_FORMAT STREQUAL "coveralls")
|
||||
)
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json)
|
||||
elseif (Coverage_FORMAT STREQUAL "txt")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
|
||||
elseif (Coverage_FORMAT STREQUAL "csv")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
|
||||
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
|
||||
if (DEFINED Coverage_BASE_DIRECTORY)
|
||||
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
|
||||
else ()
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if ((Coverage_FORMAT STREQUAL "cobertura") OR (Coverage_FORMAT STREQUAL "xml"))
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty)
|
||||
set(Coverage_FORMAT cobertura) # overwrite xml
|
||||
elseif (Coverage_FORMAT STREQUAL "sonarqube")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "json-summary")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "json-details")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "coveralls")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "csv")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --csv "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "txt")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --txt "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "html-single")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-self-contained)
|
||||
elseif (Coverage_FORMAT STREQUAL "html-nested")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-nested "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "html-details")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-details "${GCOVR_OUTPUT_FILE}")
|
||||
else ()
|
||||
message(FATAL_ERROR "Unsupported output style ${Coverage_FORMAT}! Aborting...")
|
||||
endif ()
|
||||
|
||||
# Collect excludes (CMake 3.4+: Also compute absolute paths)
|
||||
set(GCOVR_EXCLUDES "")
|
||||
foreach (EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
|
||||
if (CMAKE_VERSION VERSION_GREATER 3.4)
|
||||
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
|
||||
endif ()
|
||||
list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
|
||||
endforeach ()
|
||||
list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
|
||||
|
||||
# Combine excludes to several -e arguments
|
||||
set(GCOVR_EXCLUDE_ARGS "")
|
||||
foreach (EXCLUDE ${GCOVR_EXCLUDES})
|
||||
list(APPEND GCOVR_EXCLUDE_ARGS "-e")
|
||||
list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
|
||||
endforeach ()
|
||||
|
||||
# Set up commands which will be run to generate coverage data Run tests
|
||||
set(GCOVR_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS})
|
||||
|
||||
# Create folder
|
||||
if (DEFINED GCOVR_CREATE_FOLDER)
|
||||
set(GCOVR_FOLDER_CMD ${CMAKE_COMMAND} -E make_directory ${GCOVR_CREATE_FOLDER})
|
||||
else ()
|
||||
set(GCOVR_FOLDER_CMD echo) # dummy
|
||||
endif ()
|
||||
|
||||
# Running gcovr
|
||||
set(GCOVR_CMD
|
||||
${GCOVR_PATH}
|
||||
--gcov-executable
|
||||
${GCOV_TOOL}
|
||||
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
|
||||
-r
|
||||
${BASEDIR}
|
||||
${GCOVR_ADDITIONAL_ARGS}
|
||||
${GCOVR_EXCLUDE_ARGS}
|
||||
--object-directory=${PROJECT_BINARY_DIR}
|
||||
)
|
||||
|
||||
if (CODE_COVERAGE_VERBOSE)
|
||||
message(STATUS "Executed command report")
|
||||
|
||||
message(STATUS "Command to run tests: ")
|
||||
string(REPLACE ";" " " GCOVR_EXEC_TESTS_CMD_SPACED "${GCOVR_EXEC_TESTS_CMD}")
|
||||
message(STATUS "${GCOVR_EXEC_TESTS_CMD_SPACED}")
|
||||
|
||||
if (NOT GCOVR_FOLDER_CMD STREQUAL "echo")
|
||||
message(STATUS "Command to create a folder: ")
|
||||
string(REPLACE ";" " " GCOVR_FOLDER_CMD_SPACED "${GCOVR_FOLDER_CMD}")
|
||||
message(STATUS "${GCOVR_FOLDER_CMD_SPACED}")
|
||||
set(BASEDIR ${PROJECT_SOURCE_DIR})
|
||||
endif ()
|
||||
|
||||
message(STATUS "Command to generate gcovr coverage data: ")
|
||||
string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}")
|
||||
message(STATUS "${GCOVR_CMD_SPACED}")
|
||||
endif ()
|
||||
if (NOT DEFINED Coverage_FORMAT)
|
||||
set(Coverage_FORMAT xml)
|
||||
endif ()
|
||||
|
||||
add_custom_target(
|
||||
${Coverage_NAME}
|
||||
COMMAND ${GCOVR_EXEC_TESTS_CMD}
|
||||
COMMAND ${GCOVR_FOLDER_CMD}
|
||||
COMMAND ${GCOVR_CMD}
|
||||
BYPRODUCTS ${GCOVR_OUTPUT_FILE}
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
DEPENDS ${Coverage_DEPENDENCIES}
|
||||
VERBATIM # Protect arguments to commands
|
||||
COMMENT "Running gcovr to produce code coverage report."
|
||||
)
|
||||
if ("--output" IN_LIST GCOVR_ADDITIONAL_ARGS)
|
||||
message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...")
|
||||
else ()
|
||||
if ((Coverage_FORMAT STREQUAL "html-details") OR (Coverage_FORMAT STREQUAL "html-nested"))
|
||||
set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html)
|
||||
set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME})
|
||||
elseif (Coverage_FORMAT STREQUAL "html-single")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html)
|
||||
elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL "json-details")
|
||||
OR (Coverage_FORMAT STREQUAL "coveralls"))
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json)
|
||||
elseif (Coverage_FORMAT STREQUAL "txt")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
|
||||
elseif (Coverage_FORMAT STREQUAL "csv")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
|
||||
else ()
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Show info where to find the report
|
||||
add_custom_command(
|
||||
TARGET ${Coverage_NAME} POST_BUILD COMMAND ;
|
||||
COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}"
|
||||
)
|
||||
if ((Coverage_FORMAT STREQUAL "cobertura") OR (Coverage_FORMAT STREQUAL "xml"))
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty)
|
||||
set(Coverage_FORMAT cobertura) # overwrite xml
|
||||
elseif (Coverage_FORMAT STREQUAL "sonarqube")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "json-summary")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "json-details")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "coveralls")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls-pretty)
|
||||
elseif (Coverage_FORMAT STREQUAL "csv")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --csv "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "txt")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --txt "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "html-single")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html "${GCOVR_OUTPUT_FILE}")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-self-contained)
|
||||
elseif (Coverage_FORMAT STREQUAL "html-nested")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-nested "${GCOVR_OUTPUT_FILE}")
|
||||
elseif (Coverage_FORMAT STREQUAL "html-details")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --html-details "${GCOVR_OUTPUT_FILE}")
|
||||
else ()
|
||||
message(FATAL_ERROR "Unsupported output style ${Coverage_FORMAT}! Aborting...")
|
||||
endif ()
|
||||
|
||||
# Collect excludes (CMake 3.4+: Also compute absolute paths)
|
||||
set(GCOVR_EXCLUDES "")
|
||||
foreach (EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
|
||||
if (CMAKE_VERSION VERSION_GREATER 3.4)
|
||||
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
|
||||
endif ()
|
||||
list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
|
||||
endforeach ()
|
||||
list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
|
||||
|
||||
# Combine excludes to several -e arguments
|
||||
set(GCOVR_EXCLUDE_ARGS "")
|
||||
foreach (EXCLUDE ${GCOVR_EXCLUDES})
|
||||
list(APPEND GCOVR_EXCLUDE_ARGS "-e")
|
||||
list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
|
||||
endforeach ()
|
||||
|
||||
# Set up commands which will be run to generate coverage data Run tests
|
||||
set(GCOVR_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS})
|
||||
|
||||
# Create folder
|
||||
if (DEFINED GCOVR_CREATE_FOLDER)
|
||||
set(GCOVR_FOLDER_CMD ${CMAKE_COMMAND} -E make_directory ${GCOVR_CREATE_FOLDER})
|
||||
else ()
|
||||
set(GCOVR_FOLDER_CMD echo) # dummy
|
||||
endif ()
|
||||
|
||||
# Running gcovr
|
||||
set(GCOVR_CMD
|
||||
${GCOVR_PATH}
|
||||
--gcov-executable
|
||||
${GCOV_TOOL}
|
||||
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
|
||||
-r
|
||||
${BASEDIR}
|
||||
${GCOVR_ADDITIONAL_ARGS}
|
||||
${GCOVR_EXCLUDE_ARGS}
|
||||
--object-directory=${PROJECT_BINARY_DIR})
|
||||
|
||||
if (CODE_COVERAGE_VERBOSE)
|
||||
message(STATUS "Executed command report")
|
||||
|
||||
message(STATUS "Command to run tests: ")
|
||||
string(REPLACE ";" " " GCOVR_EXEC_TESTS_CMD_SPACED "${GCOVR_EXEC_TESTS_CMD}")
|
||||
message(STATUS "${GCOVR_EXEC_TESTS_CMD_SPACED}")
|
||||
|
||||
if (NOT GCOVR_FOLDER_CMD STREQUAL "echo")
|
||||
message(STATUS "Command to create a folder: ")
|
||||
string(REPLACE ";" " " GCOVR_FOLDER_CMD_SPACED "${GCOVR_FOLDER_CMD}")
|
||||
message(STATUS "${GCOVR_FOLDER_CMD_SPACED}")
|
||||
endif ()
|
||||
|
||||
message(STATUS "Command to generate gcovr coverage data: ")
|
||||
string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}")
|
||||
message(STATUS "${GCOVR_CMD_SPACED}")
|
||||
endif ()
|
||||
|
||||
add_custom_target(${Coverage_NAME}
|
||||
COMMAND ${GCOVR_EXEC_TESTS_CMD}
|
||||
COMMAND ${GCOVR_FOLDER_CMD}
|
||||
COMMAND ${GCOVR_CMD}
|
||||
BYPRODUCTS ${GCOVR_OUTPUT_FILE}
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
DEPENDS ${Coverage_DEPENDENCIES}
|
||||
VERBATIM # Protect arguments to commands
|
||||
COMMENT "Running gcovr to produce code coverage report.")
|
||||
|
||||
# Show info where to find the report
|
||||
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ;
|
||||
COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}")
|
||||
endfunction () # setup_target_for_coverage_gcovr
|
||||
|
||||
function (append_coverage_compiler_flags)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
|
||||
endfunction () # append_coverage_compiler_flags
|
||||
|
||||
# Setup coverage for specific library
|
||||
function (append_coverage_compiler_flags_to_target name mode)
|
||||
separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}")
|
||||
target_compile_options(${name} ${mode} ${_flag_list})
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(${name} ${mode} gcov)
|
||||
endif ()
|
||||
separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}")
|
||||
target_compile_options(${name} ${mode} ${_flag_list})
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_libraries(${name} ${mode} gcov)
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
@@ -11,10 +11,8 @@ set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
|
||||
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
|
||||
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT})
|
||||
add_custom_target(
|
||||
docs
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(docs
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
if (DEFINED CMAKE_LINKER_TYPE)
|
||||
message(STATUS "Custom linker is already set: ${CMAKE_LINKER_TYPE}")
|
||||
return()
|
||||
message(STATUS "Custom linker is already set: ${CMAKE_LINKER_TYPE}")
|
||||
return()
|
||||
endif ()
|
||||
|
||||
find_program(MOLD_PATH mold)
|
||||
|
||||
if (MOLD_PATH AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
message(STATUS "Using Mold linker: ${MOLD_PATH}")
|
||||
set(CMAKE_LINKER_TYPE MOLD)
|
||||
message(STATUS "Using Mold linker: ${MOLD_PATH}")
|
||||
set(CMAKE_LINKER_TYPE MOLD)
|
||||
endif ()
|
||||
|
||||
@@ -31,52 +31,51 @@ set(COMPILER_FLAGS
|
||||
# -Wduplicated-cond -Wlogical-op -Wuseless-cast ) endif ()
|
||||
|
||||
if (is_clang)
|
||||
list(APPEND COMPILER_FLAGS -Wshadow # gcc is to aggressive with shadowing
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78147
|
||||
)
|
||||
list(APPEND COMPILER_FLAGS -Wshadow # gcc is to aggressive with shadowing
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78147
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (is_appleclang)
|
||||
list(APPEND COMPILER_FLAGS -Wreorder-init-list)
|
||||
list(APPEND COMPILER_FLAGS -Wreorder-init-list)
|
||||
endif ()
|
||||
|
||||
if (san)
|
||||
# When building with sanitizers some compilers will actually produce extra warnings/errors. We don't want this yet, at
|
||||
# least not until we have fixed all runtime issues reported by the sanitizers. Once that is done we can start removing
|
||||
# some of these and trying to fix it in our codebase. We can never remove all of below because most of them are
|
||||
# reported from deep inside libraries like boost or libxrpl.
|
||||
#
|
||||
# TODO: Address in https://github.com/XRPLF/clio/issues/1885
|
||||
list(
|
||||
APPEND
|
||||
COMPILER_FLAGS
|
||||
-Wno-error=tsan # Disables treating TSAN warnings as errors
|
||||
-Wno-tsan # Disables TSAN warnings (thread-safety analysis)
|
||||
-Wno-uninitialized # Disables warnings about uninitialized variables (AddressSanitizer, UndefinedBehaviorSanitizer,
|
||||
# etc.)
|
||||
-Wno-stringop-overflow # Disables warnings about potential string operation overflows (AddressSanitizer)
|
||||
-Wno-unsafe-buffer-usage # Disables warnings about unsafe memory operations (AddressSanitizer)
|
||||
-Wno-frame-larger-than # Disables warnings about stack frame size being too large (AddressSanitizer)
|
||||
-Wno-unused-function # Disables warnings about unused functions (LeakSanitizer, memory-related issues)
|
||||
-Wno-unused-but-set-variable # Disables warnings about unused variables (MemorySanitizer)
|
||||
-Wno-thread-safety-analysis # Disables warnings related to thread safety usage (ThreadSanitizer)
|
||||
-Wno-thread-safety # Disables warnings related to thread safety usage (ThreadSanitizer)
|
||||
-Wno-sign-compare # Disables warnings about signed/unsigned comparison (UndefinedBehaviorSanitizer)
|
||||
-Wno-nonnull # Disables warnings related to null pointer dereferencing (UndefinedBehaviorSanitizer)
|
||||
-Wno-address # Disables warnings about address-related issues (UndefinedBehaviorSanitizer)
|
||||
-Wno-array-bounds # Disables array bounds checks (UndefinedBehaviorSanitizer)
|
||||
)
|
||||
# When building with sanitizers some compilers will actually produce extra warnings/errors. We don't want this yet,
|
||||
# at least not until we have fixed all runtime issues reported by the sanitizers. Once that is done we can start
|
||||
# removing some of these and trying to fix it in our codebase. We can never remove all of below because most of them
|
||||
# are reported from deep inside libraries like boost or libxrpl.
|
||||
#
|
||||
# TODO: Address in https://github.com/XRPLF/clio/issues/1885
|
||||
list(APPEND
|
||||
COMPILER_FLAGS
|
||||
-Wno-error=tsan # Disables treating TSAN warnings as errors
|
||||
-Wno-tsan # Disables TSAN warnings (thread-safety analysis)
|
||||
-Wno-uninitialized # Disables warnings about uninitialized variables (AddressSanitizer,
|
||||
# UndefinedBehaviorSanitizer, etc.)
|
||||
-Wno-stringop-overflow # Disables warnings about potential string operation overflows (AddressSanitizer)
|
||||
-Wno-unsafe-buffer-usage # Disables warnings about unsafe memory operations (AddressSanitizer)
|
||||
-Wno-frame-larger-than # Disables warnings about stack frame size being too large (AddressSanitizer)
|
||||
-Wno-unused-function # Disables warnings about unused functions (LeakSanitizer, memory-related issues)
|
||||
-Wno-unused-but-set-variable # Disables warnings about unused variables (MemorySanitizer)
|
||||
-Wno-thread-safety-analysis # Disables warnings related to thread safety usage (ThreadSanitizer)
|
||||
-Wno-thread-safety # Disables warnings related to thread safety usage (ThreadSanitizer)
|
||||
-Wno-sign-compare # Disables warnings about signed/unsigned comparison (UndefinedBehaviorSanitizer)
|
||||
-Wno-nonnull # Disables warnings related to null pointer dereferencing (UndefinedBehaviorSanitizer)
|
||||
-Wno-address # Disables warnings about address-related issues (UndefinedBehaviorSanitizer)
|
||||
-Wno-array-bounds # Disables array bounds checks (UndefinedBehaviorSanitizer)
|
||||
)
|
||||
endif ()
|
||||
|
||||
# See https://github.com/cpp-best-practices/cppbestpractices/blob/master/02-Use_the_Tools_Available.md#gcc--clang for
|
||||
# the flags description
|
||||
|
||||
if (time_trace)
|
||||
if (is_clang OR is_appleclang)
|
||||
list(APPEND COMPILER_FLAGS -ftime-trace)
|
||||
else ()
|
||||
message(FATAL_ERROR "Clang or AppleClang is required to use `-ftime-trace`")
|
||||
endif ()
|
||||
if (is_clang OR is_appleclang)
|
||||
list(APPEND COMPILER_FLAGS -ftime-trace)
|
||||
else ()
|
||||
message(FATAL_ERROR "Clang or AppleClang is required to use `-ftime-trace`")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
target_compile_options(clio_options INTERFACE ${COMPILER_FLAGS})
|
||||
|
||||
@@ -2,10 +2,10 @@ include(CheckIncludeFileCXX)
|
||||
|
||||
check_include_file_cxx("source_location" SOURCE_LOCATION_AVAILABLE)
|
||||
if (SOURCE_LOCATION_AVAILABLE)
|
||||
target_compile_definitions(clio_options INTERFACE "HAS_SOURCE_LOCATION")
|
||||
target_compile_definitions(clio_options INTERFACE "HAS_SOURCE_LOCATION")
|
||||
endif ()
|
||||
|
||||
check_include_file_cxx("experimental/source_location" EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
|
||||
if (EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
|
||||
target_compile_definitions(clio_options INTERFACE "HAS_EXPERIMENTAL_SOURCE_LOCATION")
|
||||
target_compile_definitions(clio_options INTERFACE "HAS_EXPERIMENTAL_SOURCE_LOCATION")
|
||||
endif ()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
if ("${san}" STREQUAL "")
|
||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_LINK)
|
||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_USE_BACKTRACE)
|
||||
find_package(libbacktrace REQUIRED CONFIG)
|
||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_LINK)
|
||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_USE_BACKTRACE)
|
||||
find_package(libbacktrace REQUIRED CONFIG)
|
||||
else ()
|
||||
# Some sanitizers (TSAN and ASAN for sure) can't be used with libbacktrace because they have their own backtracing
|
||||
# capabilities and there are conflicts. In any case, this makes sure Clio code knows that backtrace is not available.
|
||||
# See relevant conan profiles for sanitizers where we disable stacktrace in Boost explicitly.
|
||||
target_compile_definitions(clio_options INTERFACE CLIO_WITHOUT_STACKTRACE)
|
||||
message(STATUS "Sanitizer enabled, disabling stacktrace")
|
||||
# Some sanitizers (TSAN and ASAN for sure) can't be used with libbacktrace because they have their own backtracing
|
||||
# capabilities and there are conflicts. In any case, this makes sure Clio code knows that backtrace is not
|
||||
# available. See relevant conan profiles for sanitizers where we disable stacktrace in Boost explicitly.
|
||||
target_compile_definitions(clio_options INTERFACE CLIO_WITHOUT_STACKTRACE)
|
||||
message(STATUS "Sanitizer enabled, disabling stacktrace")
|
||||
endif ()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
find_package(spdlog REQUIRED)
|
||||
|
||||
if (NOT TARGET spdlog::spdlog)
|
||||
message(FATAL_ERROR "spdlog::spdlog target not found")
|
||||
message(FATAL_ERROR "spdlog::spdlog target not found")
|
||||
endif ()
|
||||
|
||||
56
conan.lock
56
conan.lock
@@ -1,61 +1,61 @@
|
||||
{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1756234289.683",
|
||||
"xrpl/3.0.0#534d3f65a336109eee929b88962bae4e%1765375071.547",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1756234266.869",
|
||||
"spdlog/1.16.0#942c2c39562ae25ba575d9c8e2bdf3b6%1763984117.108",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1756234262.318",
|
||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1764175362.029",
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"xrpl/3.1.0#3d408ab8c8020014fa7dd52bc7cc7ea8%1769706825.165",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1768312128.781",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
|
||||
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
|
||||
"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#0432758a24204da08fee953ec9ea03cb%1769436073.32",
|
||||
"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#ffee18995c706e02bf96e7a2f7042e0d%1764175360.142",
|
||||
"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%1768312129.152",
|
||||
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
|
||||
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
||||
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1763584497.32",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
|
||||
"cassandra-cpp-driver/2.17.0#bd3934138689482102c265d01288a316%1764175359.611",
|
||||
"c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1764175359.429",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1764175359.429",
|
||||
"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#90ba607d4ee8fb5fb157c3db540671fc%1764175359.429"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||
"cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1764175359.44",
|
||||
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1764175359.429",
|
||||
"b2/5.3.3#107c15377719889654eb9a162a673975%1756234226.28"
|
||||
"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": [
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88": [
|
||||
null,
|
||||
"protobuf/3.21.12"
|
||||
],
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1": [
|
||||
null,
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
|
||||
],
|
||||
"protobuf/3.21.12": [
|
||||
null,
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88"
|
||||
],
|
||||
"lz4/1.9.4": [
|
||||
"lz4/1.10.0"
|
||||
],
|
||||
"boost/1.90.0": [
|
||||
"boost/1.83.0"
|
||||
],
|
||||
"sqlite3/3.44.2": [
|
||||
"sqlite3/3.49.1"
|
||||
],
|
||||
"fmt/12.0.0": [
|
||||
"fmt/12.1.0"
|
||||
]
|
||||
},
|
||||
"config_requires": []
|
||||
|
||||
24
conanfile.py
24
conanfile.py
@@ -12,39 +12,39 @@ class ClioConan(ConanFile):
|
||||
options = {}
|
||||
|
||||
requires = [
|
||||
"boost/1.83.0",
|
||||
"cassandra-cpp-driver/2.17.0",
|
||||
"protobuf/3.21.12",
|
||||
"fmt/12.1.0",
|
||||
"grpc/1.50.1",
|
||||
"openssl/1.1.1w",
|
||||
"xrpl/3.0.0",
|
||||
"zlib/1.3.1",
|
||||
"libbacktrace/cci.20210118",
|
||||
"spdlog/1.16.0",
|
||||
"openssl/1.1.1w",
|
||||
"protobuf/3.21.12",
|
||||
"spdlog/1.17.0",
|
||||
"xrpl/3.1.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,
|
||||
"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,
|
||||
"gtest/*:no_main": True,
|
||||
"xrpl/*:rocksdb": False,
|
||||
"xrpl/*:tests": False,
|
||||
}
|
||||
|
||||
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
|
||||
|
||||
def requirements(self):
|
||||
self.requires("gtest/1.14.0")
|
||||
self.requires("boost/1.83.0", force=True)
|
||||
self.requires("gtest/1.17.0")
|
||||
self.requires("benchmark/1.9.4")
|
||||
self.requires("fmt/12.1.0", force=True)
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == "apple-clang":
|
||||
|
||||
@@ -54,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
|
||||
|
||||
@@ -5,15 +5,15 @@ 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clio_develop:
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||
volumes:
|
||||
- clio_develop_conan_data:/root/.conan2/p
|
||||
- clio_develop_ccache:/root/.ccache
|
||||
|
||||
@@ -42,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}" \
|
||||
@@ -59,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}" \
|
||||
@@ -79,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 \
|
||||
@@ -100,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}" \
|
||||
|
||||
@@ -175,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:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
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.
|
||||
|
||||
@@ -2,12 +2,11 @@ add_library(clio_app)
|
||||
target_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp Stopper.cpp WebHandlers.cpp)
|
||||
|
||||
target_link_libraries(
|
||||
clio_app
|
||||
PUBLIC clio_cluster
|
||||
clio_etl
|
||||
clio_feed
|
||||
clio_migration
|
||||
clio_rpc
|
||||
clio_web
|
||||
PRIVATE Boost::program_options
|
||||
)
|
||||
clio_app
|
||||
PUBLIC clio_cluster
|
||||
clio_etl
|
||||
clio_feed
|
||||
clio_migration
|
||||
clio_rpc
|
||||
clio_web
|
||||
PRIVATE Boost::program_options)
|
||||
|
||||
@@ -58,12 +58,16 @@ CliArgs::parse(int argc, char const* argv[])
|
||||
positional.add("conf", 1);
|
||||
|
||||
auto const printHelp = [&description]() {
|
||||
std::cout << "Clio server " << util::build::getClioFullVersionString() << "\n\n" << description;
|
||||
std::cout << "Clio server " << util::build::getClioFullVersionString() << "\n\n"
|
||||
<< description;
|
||||
};
|
||||
|
||||
po::variables_map parsed;
|
||||
try {
|
||||
po::store(po::command_line_parser(argc, argv).options(description).positional(positional).run(), parsed);
|
||||
po::store(
|
||||
po::command_line_parser(argc, argv).options(description).positional(positional).run(),
|
||||
parsed
|
||||
);
|
||||
po::notify(parsed);
|
||||
} catch (po::error const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl << std::endl;
|
||||
@@ -77,14 +81,18 @@ 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}};
|
||||
}
|
||||
|
||||
if (parsed.contains("config-description")) {
|
||||
std::filesystem::path const filePath = parsed["config-description"].as<std::string>();
|
||||
|
||||
auto const res = util::config::ClioConfigDescription::generateConfigDescriptionToFile(filePath);
|
||||
auto const res =
|
||||
util::config::ClioConfigDescription::generateConfigDescriptionToFile(filePath);
|
||||
if (res.has_value())
|
||||
return Action{Action::Exit{EXIT_SUCCESS}};
|
||||
|
||||
@@ -97,14 +105,20 @@ CliArgs::parse(int argc, char const* argv[])
|
||||
if (parsed.contains("migrate")) {
|
||||
auto const opt = parsed["migrate"].as<std::string>();
|
||||
if (opt == "status")
|
||||
return Action{Action::Migrate{.configPath = std::move(configPath), .subCmd = MigrateSubCmd::status()}};
|
||||
return Action{Action::Migrate{.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)}};
|
||||
return Action{Action::Migrate{
|
||||
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::status()
|
||||
}};
|
||||
return Action{Action::Migrate{
|
||||
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)
|
||||
}};
|
||||
}
|
||||
|
||||
if (parsed.contains("verify"))
|
||||
return Action{Action::VerifyConfig{.configPath = std::move(configPath)}};
|
||||
|
||||
return Action{Action::Run{.configPath = std::move(configPath), .useNgWebServer = parsed.contains("ng-web-server")}};
|
||||
return Action{Action::Run{
|
||||
.configPath = std::move(configPath), .useNgWebServer = parsed.contains("ng-web-server")
|
||||
}};
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
@@ -79,7 +79,8 @@ public:
|
||||
/**
|
||||
* @brief Apply a function to the action.
|
||||
*
|
||||
* @tparam Processors Action processors types. Must be callable with the action type and return int.
|
||||
* @tparam Processors Action processors types. Must be callable with the action type and
|
||||
* return int.
|
||||
* @param processors Action processors.
|
||||
* @return Exit code.
|
||||
*/
|
||||
|
||||
@@ -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"
|
||||
@@ -121,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);
|
||||
@@ -130,14 +136,15 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
auto const migrationInspector = migration::makeMigrationInspector(config_, backend);
|
||||
// Check if any migration is blocking Clio server starting.
|
||||
if (migrationInspector->isBlockingClio() and backend->hardFetchLedgerRangeNoThrow()) {
|
||||
LOG(util::LogService::error())
|
||||
<< "Existing Migration is blocking Clio, Please complete the database migration first.";
|
||||
LOG(util::LogService::error()) << "Existing Migration is blocking Clio, Please "
|
||||
"complete the database migration first.";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Manages clients subscribed to streams
|
||||
auto subscriptions = feed::SubscriptionManager::makeSubscriptionManager(config_, backend, amendmentCenter);
|
||||
auto subscriptions =
|
||||
feed::SubscriptionManager::makeSubscriptionManager(config_, backend, amendmentCenter);
|
||||
|
||||
// Tracks which ledgers have been validated by the network
|
||||
auto ledgers = etl::NetworkValidatedLedgers::makeValidatedLedgers();
|
||||
@@ -150,8 +157,11 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
config_, ioc, backend, subscriptions, std::make_unique<util::MTRandomGenerator>(), ledgers
|
||||
);
|
||||
|
||||
// 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);
|
||||
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only
|
||||
// publishes
|
||||
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);
|
||||
@@ -161,15 +171,19 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
);
|
||||
|
||||
using RPCEngineType = rpc::RPCEngine<rpc::Counters>;
|
||||
auto const rpcEngine =
|
||||
RPCEngineType::makeRPCEngine(config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
|
||||
auto const rpcEngine = RPCEngineType::makeRPCEngine(
|
||||
config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider
|
||||
);
|
||||
|
||||
if (useNgWebServer or config_.get<bool>("server.__ng_web_server")) {
|
||||
web::ng::RPCServerHandler<RPCEngineType> handler{config_, backend, rpcEngine, etl, dosGuard};
|
||||
web::ng::RPCServerHandler<RPCEngineType> handler{
|
||||
config_, backend, rpcEngine, etl, dosGuard
|
||||
};
|
||||
|
||||
auto expectedAdminVerifier = web::makeAdminVerificationStrategy(config_);
|
||||
if (not expectedAdminVerifier.has_value()) {
|
||||
LOG(util::LogService::error()) << "Error creating admin verifier: " << expectedAdminVerifier.error();
|
||||
LOG(util::LogService::error())
|
||||
<< "Error creating admin verifier: " << expectedAdminVerifier.error();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
auto const adminVerifier = std::move(expectedAdminVerifier).value();
|
||||
@@ -197,7 +211,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.
|
||||
@@ -209,11 +232,22 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
}
|
||||
|
||||
// Init the web server
|
||||
auto handler = std::make_shared<web::RPCServerHandler<RPCEngineType>>(config_, backend, rpcEngine, etl, dosGuard);
|
||||
auto handler = std::make_shared<web::RPCServerHandler<RPCEngineType>>(
|
||||
config_, backend, rpcEngine, etl, dosGuard
|
||||
);
|
||||
|
||||
auto const httpServer = web::makeHttpServer(config_, ioc, dosGuard, handler, cache);
|
||||
appStopper_.setOnStop(
|
||||
Stopper::makeOnStopCallback(*httpServer, *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
||||
Stopper::makeOnStopCallback(
|
||||
*httpServer,
|
||||
*balancer,
|
||||
*etl,
|
||||
*subscriptions,
|
||||
*backend,
|
||||
cacheSaver,
|
||||
clusterCommunicationService,
|
||||
ioc
|
||||
)
|
||||
);
|
||||
|
||||
// Blocks until stopped.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cluster/Concepts.hpp"
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "data/LedgerCacheSaver.hpp"
|
||||
#include "etl/ETLServiceInterface.hpp"
|
||||
@@ -38,7 +39,8 @@
|
||||
namespace app {
|
||||
|
||||
/**
|
||||
* @brief Application stopper class. On stop it will create a new thread to run all the shutdown tasks.
|
||||
* @brief Application stopper class. On stop it will create a new thread to run all the shutdown
|
||||
* tasks.
|
||||
*/
|
||||
class Stopper {
|
||||
boost::asio::io_context ctx_;
|
||||
@@ -82,10 +84,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,
|
||||
@@ -94,6 +100,7 @@ public:
|
||||
feed::SubscriptionManagerInterface& subscriptions,
|
||||
data::BackendInterface& backend,
|
||||
LedgerCacheSaverType& cacheSaver,
|
||||
ClusterCommunicationServiceType& clusterCommunicationService,
|
||||
boost::asio::io_context& ioc
|
||||
)
|
||||
{
|
||||
@@ -111,6 +118,8 @@ public:
|
||||
});
|
||||
coroutineGroup.asyncWait(yield);
|
||||
|
||||
clusterCommunicationService.stop();
|
||||
|
||||
etl.stop();
|
||||
LOG(util::LogService::info()) << "ETL stopped";
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ parseConfig(std::string_view configPath)
|
||||
|
||||
auto const json = ConfigFileJson::makeConfigFileJson(configPath);
|
||||
if (!json.has_value()) {
|
||||
std::cerr << "Error parsing json from config: " << configPath << "\n" << json.error().error << std::endl;
|
||||
std::cerr << "Error parsing json from config: " << configPath << "\n"
|
||||
<< json.error().error << std::endl;
|
||||
return false;
|
||||
}
|
||||
auto const errors = getClioConfig().parse(json.value());
|
||||
|
||||
@@ -51,9 +51,9 @@ OnConnectCheck::operator()(web::ng::Connection const& connection)
|
||||
{
|
||||
dosguard_.get().increment(connection.ip());
|
||||
if (not dosguard_.get().isOk(connection.ip())) {
|
||||
return std::unexpected{
|
||||
web::ng::Response{boost::beast::http::status::too_many_requests, "Too many requests", connection}
|
||||
};
|
||||
return std::unexpected{web::ng::Response{
|
||||
boost::beast::http::status::too_many_requests, "Too many requests", connection
|
||||
}};
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -80,7 +80,10 @@ DisconnectHook::operator()(web::ng::Connection const& connection)
|
||||
dosguard_.get().decrement(connection.ip());
|
||||
}
|
||||
|
||||
MetricsHandler::MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, rpc::WorkQueue& workQueue)
|
||||
MetricsHandler::MetricsHandler(
|
||||
std::shared_ptr<web::AdminVerificationStrategy> adminVerifier,
|
||||
rpc::WorkQueue& workQueue
|
||||
)
|
||||
: adminVerifier_{std::move(adminVerifier)}, workQueue_{std::ref(workQueue)}
|
||||
{
|
||||
}
|
||||
@@ -120,7 +123,9 @@ MetricsHandler::operator()(
|
||||
|
||||
if (!postSuccessful) {
|
||||
return web::ng::Response{
|
||||
boost::beast::http::status::too_many_requests, rpc::makeError(rpc::RippledError::rpcTOO_BUSY), request
|
||||
boost::beast::http::status::too_many_requests,
|
||||
rpc::makeError(rpc::RippledError::rpcTOO_BUSY),
|
||||
request
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,7 +182,9 @@ CacheStateHandler::operator()(
|
||||
if (cache_.get().isFull())
|
||||
return web::ng::Response{boost::beast::http::status::ok, kCACHE_CHECK_LOADED_HTML, request};
|
||||
|
||||
return web::ng::Response{boost::beast::http::status::service_unavailable, kCACHE_CHECK_NOT_LOADED_HTML, request};
|
||||
return web::ng::Response{
|
||||
boost::beast::http::status::service_unavailable, kCACHE_CHECK_NOT_LOADED_HTML, request
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
@@ -68,8 +68,8 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A function object that is called when the IP of a connection changes (usually if proxy detected).
|
||||
* This is used to update the DOS guard.
|
||||
* @brief A function object that is called when the IP of a connection changes (usually if proxy
|
||||
* detected). This is used to update the DOS guard.
|
||||
*/
|
||||
class IpChangeHook {
|
||||
std::reference_wrapper<web::dosguard::DOSGuardInterface> dosguard_;
|
||||
@@ -126,10 +126,14 @@ public:
|
||||
/**
|
||||
* @brief Construct a new MetricsHandler object
|
||||
*
|
||||
* @param adminVerifier The AdminVerificationStrategy to use for verifying the connection for admin access.
|
||||
* @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, rpc::WorkQueue& workQueue);
|
||||
MetricsHandler(
|
||||
std::shared_ptr<web::AdminVerificationStrategy> adminVerifier,
|
||||
rpc::WorkQueue& workQueue
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief The call of the function object.
|
||||
@@ -214,10 +218,14 @@ public:
|
||||
/**
|
||||
* @brief Construct a new RequestHandler object
|
||||
*
|
||||
* @param adminVerifier The AdminVerificationStrategy to use for verifying the connection for admin access.
|
||||
* @param adminVerifier The AdminVerificationStrategy to use for verifying the connection for
|
||||
* admin access.
|
||||
* @param rpcHandler The RPC handler to use for handling the request.
|
||||
*/
|
||||
RequestHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, RpcHandlerType& rpcHandler)
|
||||
RequestHandler(
|
||||
std::shared_ptr<web::AdminVerificationStrategy> adminVerifier,
|
||||
RpcHandlerType& rpcHandler
|
||||
)
|
||||
: adminVerifier_(std::move(adminVerifier)), rpcHandler_(rpcHandler)
|
||||
{
|
||||
}
|
||||
|
||||
136
src/cluster/Backend.cpp
Normal file
136
src/cluster/Backend.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
148
src/cluster/Backend.hpp
Normal file
148
src/cluster/Backend.hpp
Normal file
@@ -0,0 +1,148 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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,6 @@
|
||||
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,76 @@
|
||||
#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 updateTime = util::systemTpFromUtcStr(std::string(updateTimeStr), ClioNode::kTIME_FORMAT);
|
||||
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,45 @@ 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.
|
||||
std::chrono::system_clock::time_point updateTime; ///< The time the data about the node was last updated.
|
||||
using Uuid = std::shared_ptr<boost::uuids::uuid>;
|
||||
using CUuid = std::shared_ptr<boost::uuids::uuid const>;
|
||||
|
||||
// WriterRole writerRole;
|
||||
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
|
||||
|
||||
/**
|
||||
* @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,51 @@
|
||||
#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.
|
||||
* @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)"
|
||||
);
|
||||
|
||||
// TODO: Use util::async::CoroExecutionContext after https://github.com/XRPLF/clio/issues/1973 is implemented
|
||||
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 +88,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
|
||||
78
src/cluster/Metrics.hpp
Normal file
78
src/cluster/Metrics.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
106
src/cluster/WriterDecider.cpp
Normal file
106
src/cluster/WriterDecider.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
79
src/cluster/WriterDecider.hpp
Normal file
79
src/cluster/WriterDecider.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
109
src/cluster/impl/RepeatedTask.hpp
Normal file
109
src/cluster/impl/RepeatedTask.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <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
|
||||
|
||||
std::binary_semaphore cancelSemaphore{0};
|
||||
boost::asio::post(strand_, [this, &cancelSemaphore]() {
|
||||
timer_.cancel();
|
||||
cancelSemaphore.release();
|
||||
});
|
||||
cancelSemaphore.acquire();
|
||||
semaphore_.acquire();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cluster::impl
|
||||
@@ -57,10 +57,15 @@ supportedAmendments()
|
||||
}
|
||||
|
||||
bool
|
||||
lookupAmendment(auto const& allAmendments, std::vector<ripple::uint256> const& ledgerAmendments, std::string_view name)
|
||||
lookupAmendment(
|
||||
auto const& allAmendments,
|
||||
std::vector<ripple::uint256> const& ledgerAmendments,
|
||||
std::string_view name
|
||||
)
|
||||
{
|
||||
namespace rg = std::ranges;
|
||||
if (auto const am = rg::find(allAmendments, name, &data::Amendment::name); am != rg::end(allAmendments))
|
||||
if (auto const am = rg::find(allAmendments, name, &data::Amendment::name);
|
||||
am != rg::end(allAmendments))
|
||||
return rg::find(ledgerAmendments, am->feature) != rg::end(ledgerAmendments);
|
||||
return false;
|
||||
}
|
||||
@@ -70,9 +75,12 @@ lookupAmendment(auto const& allAmendments, std::vector<ripple::uint256> const& l
|
||||
namespace data {
|
||||
namespace impl {
|
||||
|
||||
WritingAmendmentKey::WritingAmendmentKey(std::string amendmentName) : AmendmentKey{std::move(amendmentName)}
|
||||
WritingAmendmentKey::WritingAmendmentKey(std::string amendmentName)
|
||||
: AmendmentKey{std::move(amendmentName)}
|
||||
{
|
||||
ASSERT(not supportedAmendments().contains(name), "Attempt to register the same amendment twice");
|
||||
ASSERT(
|
||||
not supportedAmendments().contains(name), "Attempt to register the same amendment twice"
|
||||
);
|
||||
supportedAmendments().insert(name);
|
||||
}
|
||||
|
||||
@@ -96,7 +104,8 @@ operator ripple::uint256() const
|
||||
return Amendment::getAmendmentId(name);
|
||||
}
|
||||
|
||||
AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const& backend) : backend_{backend}
|
||||
AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const& backend)
|
||||
: backend_{backend}
|
||||
{
|
||||
namespace rg = std::ranges;
|
||||
namespace vs = std::views;
|
||||
@@ -108,7 +117,8 @@ AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const&
|
||||
.name = name,
|
||||
.feature = Amendment::getAmendmentId(name),
|
||||
.isSupportedByXRPL = support != ripple::AmendmentSupport::Unsupported,
|
||||
.isSupportedByClio = rg::find(supportedAmendments(), name) != rg::end(supportedAmendments()),
|
||||
.isSupportedByClio =
|
||||
rg::find(supportedAmendments(), name) != rg::end(supportedAmendments()),
|
||||
.isRetired = support == ripple::AmendmentSupport::Retired
|
||||
};
|
||||
}),
|
||||
@@ -144,26 +154,44 @@ AmendmentCenter::isEnabled(AmendmentKey const& key, uint32_t seq) const
|
||||
}
|
||||
|
||||
bool
|
||||
AmendmentCenter::isEnabled(boost::asio::yield_context yield, AmendmentKey const& key, uint32_t seq) const
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<bool>
|
||||
AmendmentCenter::isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const
|
||||
AmendmentCenter::isEnabled(
|
||||
boost::asio::yield_context yield,
|
||||
std::vector<AmendmentKey> const& keys,
|
||||
uint32_t seq
|
||||
) const
|
||||
{
|
||||
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);
|
||||
@@ -172,7 +200,11 @@ AmendmentCenter::isEnabled(boost::asio::yield_context yield, std::vector<Amendme
|
||||
Amendment const&
|
||||
AmendmentCenter::getAmendment(AmendmentKey const& key) const
|
||||
{
|
||||
ASSERT(supported_.contains(key), "The amendment '{}' must be present in supported amendments list", key.name);
|
||||
ASSERT(
|
||||
supported_.contains(key),
|
||||
"The amendment '{}' must be present in supported amendments list",
|
||||
key.name
|
||||
);
|
||||
return supported_.at(key);
|
||||
}
|
||||
|
||||
@@ -192,7 +224,8 @@ std::optional<std::vector<ripple::uint256>>
|
||||
AmendmentCenter::fetchAmendmentsList(boost::asio::yield_context yield, uint32_t seq) const
|
||||
{
|
||||
// the amendments should always be present on the ledger
|
||||
auto const amendments = backend_->fetchLedgerObject(ripple::keylet::amendments().key, seq, yield);
|
||||
auto const amendments =
|
||||
backend_->fetchLedgerObject(ripple::keylet::amendments().key, seq, yield);
|
||||
if (not amendments.has_value())
|
||||
throw std::runtime_error("Amendments ledger object must be present in the database");
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ struct WritingAmendmentKey : AmendmentKey {
|
||||
*/
|
||||
struct Amendments {
|
||||
// NOTE: if Clio wants to report it supports an Amendment it should be listed here.
|
||||
// Whether an amendment is obsolete and/or supported by libxrpl is extracted directly from libxrpl.
|
||||
// If an amendment is in the list below it just means Clio did whatever changes needed to support it.
|
||||
// Most of the time it's going to be no changes at all.
|
||||
// Whether an amendment is obsolete and/or supported by libxrpl is extracted directly from
|
||||
// libxrpl. If an amendment is in the list below it just means Clio did whatever changes needed
|
||||
// to support it. Most of the time it's going to be no changes at all.
|
||||
|
||||
/** @cond */
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
@@ -177,6 +177,7 @@ struct Amendments {
|
||||
REGISTER(fix1512);
|
||||
REGISTER(fix1523);
|
||||
REGISTER(fix1528);
|
||||
REGISTER(fixBatchInnerSigs);
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
/** @endcond */
|
||||
};
|
||||
@@ -255,7 +256,11 @@ public:
|
||||
* @return A vector of bools representing enabled state for each of the given keys
|
||||
*/
|
||||
[[nodiscard]] std::vector<bool>
|
||||
isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const final;
|
||||
isEnabled(
|
||||
boost::asio::yield_context yield,
|
||||
std::vector<AmendmentKey> const& keys,
|
||||
uint32_t seq
|
||||
) const final;
|
||||
|
||||
/**
|
||||
* @brief Get an amendment
|
||||
|
||||
@@ -92,7 +92,11 @@ public:
|
||||
* @return A vector of bools representing enabled state for each of the given keys
|
||||
*/
|
||||
[[nodiscard]] virtual std::vector<bool>
|
||||
isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const = 0;
|
||||
isEnabled(
|
||||
boost::asio::yield_context yield,
|
||||
std::vector<AmendmentKey> const& keys,
|
||||
uint32_t seq
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get an amendment
|
||||
|
||||
@@ -41,7 +41,10 @@ std::vector<std::int64_t> const kHISTOGRAM_BUCKETS{1, 2, 5, 10, 20, 50, 100, 200
|
||||
std::int64_t
|
||||
durationInMillisecondsSince(std::chrono::steady_clock::time_point const startTime)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime).count();
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - startTime
|
||||
)
|
||||
.count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -144,7 +147,10 @@ BackendCounters::registerReadStarted(std::uint64_t const count)
|
||||
}
|
||||
|
||||
void
|
||||
BackendCounters::registerReadFinished(std::chrono::steady_clock::time_point const startTime, std::uint64_t const count)
|
||||
BackendCounters::registerReadFinished(
|
||||
std::chrono::steady_clock::time_point const startTime,
|
||||
std::uint64_t const count
|
||||
)
|
||||
{
|
||||
asyncReadCounters_.registerFinished(count);
|
||||
auto const duration = durationInMillisecondsSince(startTime);
|
||||
@@ -238,7 +244,8 @@ void
|
||||
BackendCounters::AsyncOperationCounters::registerError(std::uint64_t count)
|
||||
{
|
||||
ASSERT(
|
||||
pendingCounter_.get().value() >= static_cast<std::int64_t>(count), "Error operations can't be more than pending"
|
||||
pendingCounter_.get().value() >= static_cast<std::int64_t>(count),
|
||||
"Error operations can't be more than pending"
|
||||
);
|
||||
pendingCounter_.get() -= count;
|
||||
errorCounter_.get() += count;
|
||||
|
||||
@@ -46,7 +46,9 @@ concept SomeBackendCounters = requires(T a) {
|
||||
{ a.registerWriteFinished(std::chrono::steady_clock::time_point{}) } -> std::same_as<void>;
|
||||
{ a.registerWriteRetry() } -> std::same_as<void>;
|
||||
{ a.registerReadStarted(std::uint64_t{}) } -> std::same_as<void>;
|
||||
{ a.registerReadFinished(std::chrono::steady_clock::time_point{}, std::uint64_t{}) } -> std::same_as<void>;
|
||||
{
|
||||
a.registerReadFinished(std::chrono::steady_clock::time_point{}, std::uint64_t{})
|
||||
} -> std::same_as<void>;
|
||||
{ a.registerReadRetry(std::uint64_t{}) } -> std::same_as<void>;
|
||||
{ a.registerReadError(std::uint64_t{}) } -> std::same_as<void>;
|
||||
{ a.report() } -> std::same_as<boost::json::object>;
|
||||
|
||||
@@ -128,7 +128,8 @@ BackendInterface::fetchLedgerObjects(
|
||||
misses.push_back(keys[i]);
|
||||
}
|
||||
}
|
||||
LOG(log_.trace()) << "Cache hits = " << keys.size() - misses.size() << " - cache misses = " << misses.size();
|
||||
LOG(log_.trace()) << "Cache hits = " << keys.size() - misses.size()
|
||||
<< " - cache misses = " << misses.size();
|
||||
|
||||
if (!misses.empty()) {
|
||||
auto objs = doFetchLedgerObjects(misses, sequence, yield);
|
||||
@@ -192,7 +193,9 @@ BackendInterface::fetchBookOffers(
|
||||
ripple::uint256 const bookEnd = ripple::getQualityNext(book);
|
||||
ripple::uint256 uTipIndex = book;
|
||||
std::vector<ripple::uint256> keys;
|
||||
auto getMillis = [](auto diff) { return std::chrono::duration_cast<std::chrono::milliseconds>(diff).count(); };
|
||||
auto getMillis = [](auto diff) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
|
||||
};
|
||||
auto begin = std::chrono::system_clock::now();
|
||||
std::uint32_t numSucc = 0;
|
||||
std::uint32_t numPages = 0;
|
||||
@@ -233,20 +236,23 @@ BackendInterface::fetchBookOffers(
|
||||
auto mid = std::chrono::system_clock::now();
|
||||
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
||||
for (size_t i = 0; i < keys.size() && i < limit; ++i) {
|
||||
LOG(log_.trace()) << "Key = " << ripple::strHex(keys[i]) << " blob = " << ripple::strHex(objs[i])
|
||||
LOG(log_.trace()) << "Key = " << ripple::strHex(keys[i])
|
||||
<< " blob = " << ripple::strHex(objs[i])
|
||||
<< " ledgerSequence = " << ledgerSequence;
|
||||
ASSERT(!objs[i].empty(), "Ledger object can't be empty");
|
||||
page.offers.push_back({keys[i], objs[i]});
|
||||
}
|
||||
auto end = std::chrono::system_clock::now();
|
||||
LOG(log_.debug()) << "Fetching " << std::to_string(keys.size()) << " offers took "
|
||||
<< std::to_string(getMillis(mid - begin)) << " milliseconds. Fetching next dir took "
|
||||
<< std::to_string(succMillis) << " milliseconds. Fetched next dir " << std::to_string(numSucc)
|
||||
<< " times"
|
||||
<< " Fetching next page of dir took " << std::to_string(pageMillis) << " milliseconds"
|
||||
<< ". num pages = " << std::to_string(numPages) << ". Fetching all objects took "
|
||||
<< std::to_string(getMillis(end - mid))
|
||||
<< " milliseconds. total time = " << std::to_string(getMillis(end - begin)) << " milliseconds"
|
||||
<< std::to_string(getMillis(mid - begin))
|
||||
<< " milliseconds. Fetching next dir took " << std::to_string(succMillis)
|
||||
<< " milliseconds. Fetched next dir " << std::to_string(numSucc) << " times"
|
||||
<< " Fetching next page of dir took " << std::to_string(pageMillis)
|
||||
<< " milliseconds"
|
||||
<< ". num pages = " << std::to_string(numPages)
|
||||
<< ". Fetching all objects took " << std::to_string(getMillis(end - mid))
|
||||
<< " milliseconds. total time = " << std::to_string(getMillis(end - begin))
|
||||
<< " milliseconds"
|
||||
<< " book = " << ripple::strHex(book);
|
||||
|
||||
return page;
|
||||
@@ -273,7 +279,8 @@ BackendInterface::updateRange(uint32_t newMax)
|
||||
if (range_.has_value() and newMax < range_->maxSequence) {
|
||||
ASSERT(
|
||||
false,
|
||||
"Range shouldn't exist yet or newMax should be at least range->maxSequence. newMax = {}, "
|
||||
"Range shouldn't exist yet or newMax should be at least range->maxSequence. newMax = "
|
||||
"{}, "
|
||||
"range->maxSequence = {}",
|
||||
newMax,
|
||||
range_->maxSequence
|
||||
@@ -339,8 +346,8 @@ BackendInterface::fetchLedgerPage(
|
||||
if (!objects[i].empty()) {
|
||||
page.objects.push_back({keys[i], std::move(objects[i])});
|
||||
} else if (!outOfOrder) {
|
||||
LOG(log_.error()) << "Deleted or non-existent object in successor table. key = " << ripple::strHex(keys[i])
|
||||
<< " - seq = " << ledgerSequence;
|
||||
LOG(log_.error()) << "Deleted or non-existent object in successor table. key = "
|
||||
<< ripple::strHex(keys[i]) << " - seq = " << ledgerSequence;
|
||||
std::stringstream msg;
|
||||
for (size_t j = 0; j < objects.size(); ++j) {
|
||||
msg << " - " << ripple::strHex(keys[j]);
|
||||
|
||||
@@ -109,18 +109,23 @@ synchronous(FnType&& func)
|
||||
using R = typename boost::result_of<FnType(boost::asio::yield_context)>::type;
|
||||
if constexpr (!std::is_same_v<R, void>) {
|
||||
R res;
|
||||
util::spawn(ctx, [_ = boost::asio::make_work_guard(ctx), &func, &res](auto yield) { res = func(yield); });
|
||||
util::spawn(ctx, [_ = boost::asio::make_work_guard(ctx), &func, &res](auto yield) {
|
||||
res = func(yield);
|
||||
});
|
||||
|
||||
ctx.run();
|
||||
return res;
|
||||
} else {
|
||||
util::spawn(ctx, [_ = boost::asio::make_work_guard(ctx), &func](auto yield) { func(yield); });
|
||||
util::spawn(ctx, [_ = boost::asio::make_work_guard(ctx), &func](auto yield) {
|
||||
func(yield);
|
||||
});
|
||||
ctx.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Synchronously execute the given function object and retry until no DatabaseTimeout is thrown.
|
||||
* @brief Synchronously execute the given function object and retry until no DatabaseTimeout is
|
||||
* thrown.
|
||||
*
|
||||
* @tparam FnType The type of function object to execute
|
||||
* @param func The function object to execute
|
||||
@@ -225,7 +230,8 @@ public:
|
||||
fetchLedgerRange() const;
|
||||
|
||||
/**
|
||||
* @brief Fetch the specified number of account root object indexes by page, the accounts need to exist for seq.
|
||||
* @brief Fetch the specified number of account root object indexes by page, the accounts need
|
||||
* to exist for seq.
|
||||
*
|
||||
* @param number The number of accounts to fetch
|
||||
* @param pageSize The maximum number of accounts per page
|
||||
@@ -296,7 +302,10 @@ public:
|
||||
* @return A vector of TransactionAndMetadata matching the given hashes
|
||||
*/
|
||||
virtual std::vector<TransactionAndMetadata>
|
||||
fetchTransactions(std::vector<ripple::uint256> const& hashes, boost::asio::yield_context yield) const = 0;
|
||||
fetchTransactions(
|
||||
std::vector<ripple::uint256> const& hashes,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Fetches all transactions for a specific account.
|
||||
@@ -325,7 +334,10 @@ public:
|
||||
* @return Results as a vector of TransactionAndMetadata
|
||||
*/
|
||||
virtual std::vector<TransactionAndMetadata>
|
||||
fetchAllTransactionsInLedger(std::uint32_t ledgerSequence, boost::asio::yield_context yield) const = 0;
|
||||
fetchAllTransactionsInLedger(
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Fetches all transaction hashes from a specific ledger.
|
||||
@@ -335,7 +347,10 @@ public:
|
||||
* @return Hashes as ripple::uint256 in a vector
|
||||
*/
|
||||
virtual std::vector<ripple::uint256>
|
||||
fetchAllTransactionHashesInLedger(std::uint32_t ledgerSequence, boost::asio::yield_context yield) const = 0;
|
||||
fetchAllTransactionHashesInLedger(
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Fetches a specific NFT.
|
||||
@@ -346,7 +361,11 @@ public:
|
||||
* @return NFT object on success; nullopt otherwise
|
||||
*/
|
||||
virtual std::optional<NFT>
|
||||
fetchNFT(ripple::uint256 const& tokenID, std::uint32_t ledgerSequence, boost::asio::yield_context yield) const = 0;
|
||||
fetchNFT(
|
||||
ripple::uint256 const& tokenID,
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Fetches all transactions for a specific NFT.
|
||||
@@ -376,7 +395,8 @@ public:
|
||||
* @param limit Paging limit.
|
||||
* @param cursorIn Optional cursor to allow us to pick up from where we last left off.
|
||||
* @param yield Currently executing coroutine.
|
||||
* @return NFTs issued by this account, or this issuer/taxon combination if taxon is passed and an optional marker
|
||||
* @return NFTs issued by this account, or this issuer/taxon combination if taxon is passed and
|
||||
* an optional marker
|
||||
*/
|
||||
virtual NFTsAndCursor
|
||||
fetchNFTsByIssuer(
|
||||
@@ -410,8 +430,8 @@ public:
|
||||
/**
|
||||
* @brief Fetches a specific ledger object.
|
||||
*
|
||||
* Currently the real fetch happens in doFetchLedgerObject and fetchLedgerObject attempts to fetch from Cache first
|
||||
* and only calls out to the real DB if a cache miss occurred.
|
||||
* Currently the real fetch happens in doFetchLedgerObject and fetchLedgerObject attempts to
|
||||
* fetch from Cache first and only calls out to the real DB if a cache miss occurred.
|
||||
*
|
||||
* @param key The key of the object
|
||||
* @param sequence The ledger sequence to fetch for
|
||||
@@ -419,7 +439,11 @@ public:
|
||||
* @return The object as a Blob on success; nullopt otherwise
|
||||
*/
|
||||
std::optional<Blob>
|
||||
fetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
|
||||
fetchLedgerObject(
|
||||
ripple::uint256 const& key,
|
||||
std::uint32_t sequence,
|
||||
boost::asio::yield_context yield
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches a specific ledger object sequence.
|
||||
@@ -432,13 +456,18 @@ public:
|
||||
* @return The sequence in unit32_t on success; nullopt otherwise
|
||||
*/
|
||||
std::optional<std::uint32_t>
|
||||
fetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
|
||||
fetchLedgerObjectSeq(
|
||||
ripple::uint256 const& key,
|
||||
std::uint32_t sequence,
|
||||
boost::asio::yield_context yield
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches all ledger objects by their keys.
|
||||
*
|
||||
* Currently the real fetch happens in doFetchLedgerObjects and fetchLedgerObjects attempts to fetch from Cache
|
||||
* first and only calls out to the real DB for each of the keys that was not found in the cache.
|
||||
* Currently the real fetch happens in doFetchLedgerObjects and fetchLedgerObjects attempts to
|
||||
* fetch from Cache first and only calls out to the real DB for each of the keys that was not
|
||||
* found in the cache.
|
||||
*
|
||||
* @param keys A vector with the keys of the objects to fetch
|
||||
* @param sequence The ledger sequence to fetch for
|
||||
@@ -461,7 +490,11 @@ public:
|
||||
* @return The object as a Blob on success; nullopt otherwise
|
||||
*/
|
||||
virtual std::optional<Blob>
|
||||
doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const = 0;
|
||||
doFetchLedgerObject(
|
||||
ripple::uint256 const& key,
|
||||
std::uint32_t sequence,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief The database-specific implementation for fetching a ledger object sequence.
|
||||
@@ -531,13 +564,18 @@ public:
|
||||
* @return The successor on success; nullopt otherwise
|
||||
*/
|
||||
std::optional<LedgerObject>
|
||||
fetchSuccessorObject(ripple::uint256 key, std::uint32_t ledgerSequence, boost::asio::yield_context yield) const;
|
||||
fetchSuccessorObject(
|
||||
ripple::uint256 key,
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches the successor key.
|
||||
*
|
||||
* Thea real fetch happens in doFetchSuccessorKey. This function will attempt to lookup the successor in the cache
|
||||
* first and only if it's not found in the cache will it fetch from the actual DB.
|
||||
* Thea real fetch happens in doFetchSuccessorKey. This function will attempt to lookup the
|
||||
* successor in the cache first and only if it's not found in the cache will it fetch from the
|
||||
* actual DB.
|
||||
*
|
||||
* @param key The key to fetch for
|
||||
* @param ledgerSequence The ledger sequence to fetch for
|
||||
@@ -545,7 +583,11 @@ public:
|
||||
* @return The successor key on success; nullopt otherwise
|
||||
*/
|
||||
std::optional<ripple::uint256>
|
||||
fetchSuccessorKey(ripple::uint256 key, std::uint32_t ledgerSequence, boost::asio::yield_context yield) const;
|
||||
fetchSuccessorKey(
|
||||
ripple::uint256 key,
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Database-specific implementation of fetching the successor key
|
||||
@@ -556,7 +598,11 @@ public:
|
||||
* @return The successor on success; nullopt otherwise
|
||||
*/
|
||||
virtual std::optional<ripple::uint256>
|
||||
doFetchSuccessorKey(ripple::uint256 key, std::uint32_t ledgerSequence, boost::asio::yield_context yield) const = 0;
|
||||
doFetchSuccessorKey(
|
||||
ripple::uint256 key,
|
||||
std::uint32_t ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Fetches book offers.
|
||||
@@ -583,7 +629,10 @@ public:
|
||||
* @return The status of the migrator if found; nullopt otherwise
|
||||
*/
|
||||
virtual std::optional<std::string>
|
||||
fetchMigratorStatus(std::string const& migratorName, boost::asio::yield_context yield) const = 0;
|
||||
fetchMigratorStatus(
|
||||
std::string const& migratorName,
|
||||
boost::asio::yield_context yield
|
||||
) const = 0;
|
||||
|
||||
/** @brief Return type for fetchClioNodesData() method */
|
||||
using ClioNodesDataFetchResult =
|
||||
@@ -601,7 +650,8 @@ public:
|
||||
/**
|
||||
* @brief Synchronously fetches the ledger range from DB.
|
||||
*
|
||||
* This function just wraps hardFetchLedgerRange(boost::asio::yield_context) using synchronous(FnType&&).
|
||||
* This function just wraps hardFetchLedgerRange(boost::asio::yield_context) using
|
||||
* synchronous(FnType&&).
|
||||
*
|
||||
* @return The ledger range if available; nullopt otherwise
|
||||
*/
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
add_library(clio_data)
|
||||
target_sources(
|
||||
clio_data
|
||||
PRIVATE AmendmentCenter.cpp
|
||||
BackendCounters.cpp
|
||||
BackendInterface.cpp
|
||||
LedgerCache.cpp
|
||||
LedgerCacheSaver.cpp
|
||||
LedgerHeaderCache.cpp
|
||||
cassandra/impl/Future.cpp
|
||||
cassandra/impl/Cluster.cpp
|
||||
cassandra/impl/Batch.cpp
|
||||
cassandra/impl/Result.cpp
|
||||
cassandra/impl/Tuple.cpp
|
||||
cassandra/impl/SslContext.cpp
|
||||
cassandra/Handle.cpp
|
||||
cassandra/SettingsProvider.cpp
|
||||
impl/InputFile.cpp
|
||||
impl/LedgerCacheFile.cpp
|
||||
impl/OutputFile.cpp
|
||||
)
|
||||
target_sources(clio_data
|
||||
PRIVATE AmendmentCenter.cpp
|
||||
BackendCounters.cpp
|
||||
BackendInterface.cpp
|
||||
LedgerCache.cpp
|
||||
LedgerCacheSaver.cpp
|
||||
LedgerHeaderCache.cpp
|
||||
cassandra/impl/Future.cpp
|
||||
cassandra/impl/Cluster.cpp
|
||||
cassandra/impl/Batch.cpp
|
||||
cassandra/impl/Result.cpp
|
||||
cassandra/impl/Tuple.cpp
|
||||
cassandra/impl/SslContext.cpp
|
||||
cassandra/Handle.cpp
|
||||
cassandra/SettingsProvider.cpp
|
||||
impl/InputFile.cpp
|
||||
impl/LedgerCacheFile.cpp
|
||||
impl/OutputFile.cpp)
|
||||
|
||||
target_link_libraries(clio_data PUBLIC cassandra-cpp-driver::cassandra-cpp-driver clio_util)
|
||||
|
||||
@@ -102,10 +102,14 @@ public:
|
||||
this->waitForWritesToFinish();
|
||||
|
||||
if (!range_) {
|
||||
executor_.writeSync(schema_->updateLedgerRange, ledgerSequence_, false, ledgerSequence_);
|
||||
executor_.writeSync(
|
||||
schema_->updateLedgerRange, ledgerSequence_, false, ledgerSequence_
|
||||
);
|
||||
}
|
||||
|
||||
if (not this->executeSyncUpdate(schema_->updateLedgerRange.bind(ledgerSequence_, true, ledgerSequence_ - 1))) {
|
||||
if (not this->executeSyncUpdate(
|
||||
schema_->updateLedgerRange.bind(ledgerSequence_, true, ledgerSequence_ - 1)
|
||||
)) {
|
||||
LOG(log_.warn()) << "Update failed for ledger " << ledgerSequence_;
|
||||
return false;
|
||||
}
|
||||
@@ -139,7 +143,8 @@ public:
|
||||
r.bindAt(
|
||||
1,
|
||||
std::make_tuple(
|
||||
cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn)) : 0,
|
||||
cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn))
|
||||
: 0,
|
||||
cursorIn.value_or(ripple::uint256(0))
|
||||
)
|
||||
);
|
||||
@@ -170,9 +175,10 @@ public:
|
||||
selectNFTStatements.reserve(nftIDs.size());
|
||||
|
||||
std::transform(
|
||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTStatements), [&](auto const& nftID) {
|
||||
return schema_->selectNFT.bind(nftID, ledgerSequence);
|
||||
}
|
||||
std::cbegin(nftIDs),
|
||||
std::cend(nftIDs),
|
||||
std::back_inserter(selectNFTStatements),
|
||||
[&](auto const& nftID) { return schema_->selectNFT.bind(nftID, ledgerSequence); }
|
||||
);
|
||||
|
||||
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
||||
@@ -181,9 +187,10 @@ public:
|
||||
selectNFTURIStatements.reserve(nftIDs.size());
|
||||
|
||||
std::transform(
|
||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTURIStatements), [&](auto const& nftID) {
|
||||
return schema_->selectNFTURI.bind(nftID, ledgerSequence);
|
||||
}
|
||||
std::cbegin(nftIDs),
|
||||
std::cend(nftIDs),
|
||||
std::back_inserter(selectNFTURIStatements),
|
||||
[&](auto const& nftID) { return schema_->selectNFTURI.bind(nftID, ledgerSequence); }
|
||||
);
|
||||
|
||||
auto const nftUris = executor_.readEach(yield, selectNFTURIStatements);
|
||||
@@ -193,7 +200,8 @@ public:
|
||||
maybeRow.has_value()) {
|
||||
auto [seq, owner, isBurned] = *maybeRow;
|
||||
NFT nft(nftIDs[i], seq, owner, isBurned);
|
||||
if (auto const maybeUri = nftUris[i].template get<ripple::Blob>(); maybeUri.has_value())
|
||||
if (auto const maybeUri = nftUris[i].template get<ripple::Blob>();
|
||||
maybeUri.has_value())
|
||||
nft.uri = *maybeUri;
|
||||
ret.nfts.push_back(nft);
|
||||
}
|
||||
@@ -213,8 +221,9 @@ public:
|
||||
std::optional<ripple::AccountID> lastItem;
|
||||
|
||||
while (liveAccounts.size() < number) {
|
||||
Statement const statement = lastItem ? schema_->selectAccountFromToken.bind(*lastItem, Limit{pageSize})
|
||||
: schema_->selectAccountFromBeginning.bind(Limit{pageSize});
|
||||
Statement const statement = lastItem
|
||||
? schema_->selectAccountFromToken.bind(*lastItem, Limit{pageSize})
|
||||
: schema_->selectAccountFromBeginning.bind(Limit{pageSize});
|
||||
|
||||
auto const res = executor_.read(yield, statement);
|
||||
if (res) {
|
||||
|
||||
@@ -83,8 +83,15 @@ struct NFTTransactionsData {
|
||||
* @param meta The transaction metadata
|
||||
* @param txHash The transaction hash
|
||||
*/
|
||||
NFTTransactionsData(ripple::uint256 const& tokenID, ripple::TxMeta const& meta, ripple::uint256 const& txHash)
|
||||
: tokenID(tokenID), ledgerSequence(meta.getLgrSeq()), transactionIndex(meta.getIndex()), txHash(txHash)
|
||||
NFTTransactionsData(
|
||||
ripple::uint256 const& tokenID,
|
||||
ripple::TxMeta const& meta,
|
||||
ripple::uint256 const& txHash
|
||||
)
|
||||
: tokenID(tokenID)
|
||||
, ledgerSequence(meta.getLgrSeq())
|
||||
, transactionIndex(meta.getIndex())
|
||||
, txHash(txHash)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -94,11 +101,13 @@ struct NFTTransactionsData {
|
||||
*
|
||||
* Gets written to nf_tokens table and the like.
|
||||
*
|
||||
* The transaction index is only stored because we want to store only the final state of an NFT per ledger.
|
||||
* Since we pull this from transactions we keep track of which tx index created this so we can de-duplicate, as it is
|
||||
* possible for one ledger to have multiple txs that change the state of the same NFT.
|
||||
* The transaction index is only stored because we want to store only the final state of an NFT per
|
||||
* ledger. Since we pull this from transactions we keep track of which tx index created this so we
|
||||
* can de-duplicate, as it is possible for one ledger to have multiple txs that change the state of
|
||||
* the same NFT.
|
||||
*
|
||||
* We only set the uri if this is a mint tx, or if we are loading initial state from NFTokenPage objects.
|
||||
* We only set the uri if this is a mint tx, or if we are loading initial state from NFTokenPage
|
||||
* objects.
|
||||
*/
|
||||
struct NFTsData {
|
||||
ripple::uint256 tokenID;
|
||||
@@ -113,8 +122,9 @@ struct NFTsData {
|
||||
* @brief Construct a new NFTsData object
|
||||
*
|
||||
* @note This constructor is used when parsing an NFTokenMint tx
|
||||
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same ID, we must
|
||||
* explicitly record a null URI. For this reason, we _always_ write this field as a result of this tx.
|
||||
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same
|
||||
* ID, we must explicitly record a null URI. For this reason, we _always_ write this field as a
|
||||
* result of this tx.
|
||||
*
|
||||
* @param tokenID The token ID
|
||||
* @param owner The owner
|
||||
@@ -127,7 +137,11 @@ struct NFTsData {
|
||||
ripple::Blob const& uri,
|
||||
ripple::TxMeta const& meta
|
||||
)
|
||||
: tokenID(tokenID), ledgerSequence(meta.getLgrSeq()), transactionIndex(meta.getIndex()), owner(owner), uri(uri)
|
||||
: tokenID(tokenID)
|
||||
, ledgerSequence(meta.getLgrSeq())
|
||||
, transactionIndex(meta.getIndex())
|
||||
, owner(owner)
|
||||
, uri(uri)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -141,7 +155,12 @@ struct NFTsData {
|
||||
* @param meta The transaction metadata
|
||||
* @param isBurned Whether the NFT is burned
|
||||
*/
|
||||
NFTsData(ripple::uint256 const& tokenID, ripple::AccountID const& owner, ripple::TxMeta const& meta, bool isBurned)
|
||||
NFTsData(
|
||||
ripple::uint256 const& tokenID,
|
||||
ripple::AccountID const& owner,
|
||||
ripple::TxMeta const& meta,
|
||||
bool isBurned
|
||||
)
|
||||
: tokenID(tokenID)
|
||||
, ledgerSequence(meta.getLgrSeq())
|
||||
, transactionIndex(meta.getIndex())
|
||||
@@ -154,8 +173,9 @@ struct NFTsData {
|
||||
* @brief Construct a new NFTsData object
|
||||
*
|
||||
* @note This constructor is used when parsing an NFTokenPage directly from ledger state.
|
||||
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same ID, we must
|
||||
* explicitly record a null URI. For this reason, we _always_ write this field as a result of this tx.
|
||||
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same
|
||||
* ID, we must explicitly record a null URI. For this reason, we _always_ write this field as a
|
||||
* result of this tx.
|
||||
*
|
||||
* @param tokenID The token ID
|
||||
* @param ledgerSequence The ledger sequence
|
||||
|
||||
@@ -102,11 +102,17 @@ public:
|
||||
// This would be the first write to the table.
|
||||
// In this case, insert both min_sequence/max_sequence range into the table.
|
||||
if (not range_.has_value()) {
|
||||
executor_.writeSync(schema_->insertLedgerRange, /* isLatestLedger =*/false, ledgerSequence_);
|
||||
executor_.writeSync(schema_->insertLedgerRange, /* isLatestLedger =*/true, ledgerSequence_);
|
||||
executor_.writeSync(
|
||||
schema_->insertLedgerRange, /* isLatestLedger =*/false, ledgerSequence_
|
||||
);
|
||||
executor_.writeSync(
|
||||
schema_->insertLedgerRange, /* isLatestLedger =*/true, ledgerSequence_
|
||||
);
|
||||
}
|
||||
|
||||
if (not this->executeSyncUpdate(schema_->updateLedgerRange.bind(ledgerSequence_, true, ledgerSequence_ - 1))) {
|
||||
if (not this->executeSyncUpdate(
|
||||
schema_->updateLedgerRange.bind(ledgerSequence_, true, ledgerSequence_ - 1)
|
||||
)) {
|
||||
log_.warn() << "Update failed for ledger " << ledgerSequence_;
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +137,8 @@ public:
|
||||
nftIDs = fetchNFTIDsByTaxon(issuer, *taxon, limit, cursorIn, yield);
|
||||
} else {
|
||||
// Amazon Keyspaces Workflow for non-taxon queries
|
||||
auto const startTaxon = cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn)) : 0;
|
||||
auto const startTaxon =
|
||||
cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn)) : 0;
|
||||
auto const startTokenID = cursorIn.value_or(ripple::uint256(0));
|
||||
|
||||
Statement const firstQuery = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
|
||||
@@ -163,10 +170,10 @@ public:
|
||||
|
||||
/**
|
||||
* @brief (Unsupported in Keyspaces) Fetches account root object indexes by page.
|
||||
* @note Loading the cache by enumerating all accounts is currently unsupported by the AWS Keyspaces backend.
|
||||
* This function's logic relies on "PER PARTITION LIMIT 1", which Keyspaces does not support, and there is
|
||||
* no efficient alternative. This is acceptable as the cache is primarily loaded via diffs. Calling this
|
||||
* function will throw an exception.
|
||||
* @note Loading the cache by enumerating all accounts is currently unsupported by the AWS
|
||||
* Keyspaces backend. This function's logic relies on "PER PARTITION LIMIT 1", which Keyspaces
|
||||
* does not support, and there is no efficient alternative. This is acceptable as the cache is
|
||||
* primarily loaded via diffs. Calling this function will throw an exception.
|
||||
*
|
||||
* @param number The total number of accounts to fetch.
|
||||
* @param pageSize The maximum number of accounts per page.
|
||||
@@ -220,7 +227,8 @@ private:
|
||||
{
|
||||
std::vector<ripple::uint256> nftIDs;
|
||||
|
||||
auto const startTaxon = cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn)) : 0;
|
||||
auto const startTaxon =
|
||||
cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn)) : 0;
|
||||
auto const startTokenID = cursorIn.value_or(ripple::uint256(0));
|
||||
|
||||
Statement firstQuery = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
|
||||
@@ -250,7 +258,8 @@ private:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Takes a list of NFT IDs, fetches their full data, and assembles the final result with a cursor.
|
||||
* @brief Takes a list of NFT IDs, fetches their full data, and assembles the final result with
|
||||
* a cursor.
|
||||
*/
|
||||
NFTsAndCursor
|
||||
populateNFTsAndCreateCursor(
|
||||
@@ -273,17 +282,19 @@ private:
|
||||
std::vector<Statement> selectNFTStatements;
|
||||
selectNFTStatements.reserve(nftIDs.size());
|
||||
std::transform(
|
||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTStatements), [&](auto const& nftID) {
|
||||
return schema_->selectNFT.bind(nftID, ledgerSequence);
|
||||
}
|
||||
std::cbegin(nftIDs),
|
||||
std::cend(nftIDs),
|
||||
std::back_inserter(selectNFTStatements),
|
||||
[&](auto const& nftID) { return schema_->selectNFT.bind(nftID, ledgerSequence); }
|
||||
);
|
||||
|
||||
std::vector<Statement> selectNFTURIStatements;
|
||||
selectNFTURIStatements.reserve(nftIDs.size());
|
||||
std::transform(
|
||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTURIStatements), [&](auto const& nftID) {
|
||||
return schema_->selectNFTURI.bind(nftID, ledgerSequence);
|
||||
}
|
||||
std::cbegin(nftIDs),
|
||||
std::cend(nftIDs),
|
||||
std::back_inserter(selectNFTURIStatements),
|
||||
[&](auto const& nftID) { return schema_->selectNFTURI.bind(nftID, ledgerSequence); }
|
||||
);
|
||||
|
||||
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
||||
@@ -295,7 +306,8 @@ private:
|
||||
maybeRow.has_value()) {
|
||||
auto [seq, owner, isBurned] = *maybeRow;
|
||||
NFT nft(nftIDs[i], seq, owner, isBurned);
|
||||
if (auto const maybeUri = nftUris[i].template get<ripple::Blob>(); maybeUri.has_value())
|
||||
if (auto const maybeUri = nftUris[i].template get<ripple::Blob>();
|
||||
maybeUri.has_value())
|
||||
nft.uri = *maybeUri;
|
||||
ret.nfts.push_back(nft);
|
||||
}
|
||||
|
||||
@@ -254,7 +254,8 @@ LedgerCache::getSuccessorHitRate() const
|
||||
{
|
||||
if (successorReqCounter_.get().value() == 0u)
|
||||
return 1;
|
||||
return static_cast<float>(successorHitCounter_.get().value()) / successorReqCounter_.get().value();
|
||||
return static_cast<float>(successorHitCounter_.get().value()) /
|
||||
successorReqCounter_.get().value();
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
@@ -266,7 +267,9 @@ LedgerCache::saveToFile(std::string const& path) const
|
||||
|
||||
impl::LedgerCacheFile file{path};
|
||||
std::shared_lock const lock{mtx_};
|
||||
impl::LedgerCacheFile::DataView const data{.latestSeq = latestSeq_, .map = map_, .deleted = deleted_};
|
||||
impl::LedgerCacheFile::DataView const data{
|
||||
.latestSeq = latestSeq_, .map = map_, .deleted = deleted_
|
||||
};
|
||||
return file.write(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,26 +58,34 @@ public:
|
||||
|
||||
private:
|
||||
// counters for fetchLedgerObject(s) hit rate
|
||||
std::reference_wrapper<util::prometheus::CounterInt> objectReqCounter_{PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "ledger_objects"}}),
|
||||
"LedgerCache statistics"
|
||||
)};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> objectHitCounter_{PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "ledger_objects"}})
|
||||
)};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> objectReqCounter_{
|
||||
PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "ledger_objects"}}),
|
||||
"LedgerCache statistics"
|
||||
)
|
||||
};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> objectHitCounter_{
|
||||
PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "ledger_objects"}})
|
||||
)
|
||||
};
|
||||
|
||||
// counters for fetchSuccessorKey hit rate
|
||||
std::reference_wrapper<util::prometheus::CounterInt> successorReqCounter_{PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "successor_key"}}),
|
||||
"ledgerCache"
|
||||
)};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> successorHitCounter_{PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "successor_key"}})
|
||||
)};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> successorReqCounter_{
|
||||
PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "successor_key"}}),
|
||||
"ledgerCache"
|
||||
)
|
||||
};
|
||||
std::reference_wrapper<util::prometheus::CounterInt> successorHitCounter_{
|
||||
PrometheusService::counterInt(
|
||||
"ledger_cache_counter_total_number",
|
||||
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "successor_key"}})
|
||||
)
|
||||
};
|
||||
|
||||
CacheMap map_;
|
||||
CacheMap deleted_;
|
||||
@@ -96,7 +104,8 @@ private:
|
||||
"Whether ledger cache is disabled or not"
|
||||
)};
|
||||
|
||||
// temporary set to prevent background thread from writing already deleted data. not used when cache is full
|
||||
// temporary set to prevent background thread from writing already deleted data. not used when
|
||||
// cache is full
|
||||
std::unordered_set<ripple::uint256, ripple::hardened_hash<>> deletes_;
|
||||
|
||||
public:
|
||||
|
||||
@@ -126,9 +126,9 @@ public:
|
||||
/**
|
||||
* @brief Sets the full flag to true.
|
||||
*
|
||||
* This is used when cache loaded in its entirety at startup of the application. This can be either loaded from DB,
|
||||
* populated together with initial ledger download (on first run) or downloaded from a peer node (specified in
|
||||
* config).
|
||||
* This is used when cache loaded in its entirety at startup of the application. This can be
|
||||
* either loaded from DB, populated together with initial ledger download (on first run) or
|
||||
* downloaded from a peer node (specified in config).
|
||||
*/
|
||||
virtual void
|
||||
setFull() = 0;
|
||||
@@ -152,13 +152,15 @@ public:
|
||||
size() const = 0;
|
||||
|
||||
/**
|
||||
* @return A number representing the success rate of hitting an object in the cache versus missing it.
|
||||
* @return A number representing the success rate of hitting an object in the cache versus
|
||||
* missing it.
|
||||
*/
|
||||
virtual float
|
||||
getObjectHitRate() const = 0;
|
||||
|
||||
/**
|
||||
* @return A number representing the success rate of hitting a successor in the cache versus missing it.
|
||||
* @return A number representing the success rate of hitting a successor in the cache versus
|
||||
* missing it.
|
||||
*/
|
||||
virtual float
|
||||
getSuccessorHitRate() const = 0;
|
||||
|
||||
@@ -29,8 +29,13 @@
|
||||
|
||||
namespace data {
|
||||
|
||||
LedgerCacheSaver::LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache)
|
||||
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path")), cache_(cache)
|
||||
LedgerCacheSaver::LedgerCacheSaver(
|
||||
util::config::ClioConfigDefinition const& config,
|
||||
LedgerCacheInterface const& cache
|
||||
)
|
||||
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path"))
|
||||
, cache_(cache)
|
||||
, isAsync_(config.get<bool>("cache.file.async_save"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -49,13 +54,19 @@ LedgerCacheSaver::save()
|
||||
}
|
||||
|
||||
LOG(util::LogService::info()) << "Saving ledger cache to " << *cacheFilePath_;
|
||||
if (auto const [success, durationMs] = util::timed([&]() { return cache_.get().saveToFile(*cacheFilePath_); });
|
||||
if (auto const [success, durationMs] =
|
||||
util::timed([&]() { return cache_.get().saveToFile(*cacheFilePath_); });
|
||||
success.has_value()) {
|
||||
LOG(util::LogService::info()) << "Successfully saved ledger cache in " << durationMs << " ms";
|
||||
LOG(util::LogService::info())
|
||||
<< "Successfully saved ledger cache in " << durationMs << " ms";
|
||||
} else {
|
||||
LOG(util::LogService::error()) << "Error saving LedgerCache to file: " << success.error();
|
||||
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:
|
||||
/**
|
||||
@@ -61,7 +62,10 @@ public:
|
||||
* @param config The configuration object containing the cache file path setting
|
||||
* @param cache Reference to the ledger cache interface to be saved
|
||||
*/
|
||||
LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache);
|
||||
LedgerCacheSaver(
|
||||
util::config::ClioConfigDefinition const& config,
|
||||
LedgerCacheInterface const& cache
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Destructor that ensures the saving thread is properly joined.
|
||||
|
||||
@@ -81,8 +81,16 @@ struct TransactionAndMetadata {
|
||||
* @param ledgerSequence The ledger sequence
|
||||
* @param date The date
|
||||
*/
|
||||
TransactionAndMetadata(Blob transaction, Blob metadata, std::uint32_t ledgerSequence, std::uint32_t date)
|
||||
: transaction{std::move(transaction)}, metadata{std::move(metadata)}, ledgerSequence{ledgerSequence}, date{date}
|
||||
TransactionAndMetadata(
|
||||
Blob transaction,
|
||||
Blob metadata,
|
||||
std::uint32_t ledgerSequence,
|
||||
std::uint32_t date
|
||||
)
|
||||
: transaction{std::move(transaction)}
|
||||
, metadata{std::move(metadata)}
|
||||
, ledgerSequence{ledgerSequence}
|
||||
, date{date}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -192,7 +200,11 @@ struct NFT {
|
||||
ripple::AccountID const& owner,
|
||||
Blob uri,
|
||||
bool isBurned)
|
||||
: tokenID{tokenID}, ledgerSequence{ledgerSequence}, owner{owner}, uri{std::move(uri)}, isBurned{isBurned}
|
||||
: tokenID{tokenID}
|
||||
, ledgerSequence{ledgerSequence}
|
||||
, owner{owner}
|
||||
, uri{std::move(uri)}
|
||||
, isBurned{isBurned}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -204,7 +216,10 @@ struct NFT {
|
||||
* @param owner The owner
|
||||
* @param isBurned Whether the token is burned
|
||||
*/
|
||||
NFT(ripple::uint256 const& tokenID, std::uint32_t ledgerSequence, ripple::AccountID const& owner, bool isBurned)
|
||||
NFT(ripple::uint256 const& tokenID,
|
||||
std::uint32_t ledgerSequence,
|
||||
ripple::AccountID const& owner,
|
||||
bool isBurned)
|
||||
: NFT(tokenID, ledgerSequence, owner, {}, isBurned)
|
||||
{
|
||||
}
|
||||
@@ -212,8 +227,8 @@ struct NFT {
|
||||
/**
|
||||
* @brief Check if the NFT is the same as another
|
||||
*
|
||||
* Clearly two tokens are the same if they have the same ID, but this struct stores the state of a given
|
||||
* token at a given ledger sequence, so we also need to compare with ledgerSequence.
|
||||
* Clearly two tokens are the same if they have the same ID, but this struct stores the state of
|
||||
* a given token at a given ledger sequence, so we also need to compare with ledgerSequence.
|
||||
*
|
||||
* @param other The other NFT
|
||||
* @return true if they are the same; false otherwise
|
||||
@@ -293,7 +308,8 @@ struct AmendmentKey {
|
||||
* @brief Construct a new AmendmentKey
|
||||
* @param val Anything convertible to a string
|
||||
*/
|
||||
AmendmentKey(std::convertible_to<std::string> auto&& val) : name{std::forward<decltype(val)>(val)}
|
||||
AmendmentKey(std::convertible_to<std::string> auto&& val)
|
||||
: name{std::forward<decltype(val)>(val)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -315,8 +331,14 @@ struct AmendmentKey {
|
||||
operator<=>(AmendmentKey const& other) const = default;
|
||||
};
|
||||
|
||||
constexpr ripple::uint256 kFIRST_KEY{"0000000000000000000000000000000000000000000000000000000000000000"};
|
||||
constexpr ripple::uint256 kLAST_KEY{"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"};
|
||||
constexpr ripple::uint256 kHI192{"0000000000000000000000000000000000000000000000001111111111111111"};
|
||||
constexpr ripple::uint256 kFIRST_KEY{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
};
|
||||
constexpr ripple::uint256 kLAST_KEY{
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
};
|
||||
constexpr ripple::uint256 kHI192{
|
||||
"0000000000000000000000000000000000000000000000001111111111111111"
|
||||
};
|
||||
|
||||
} // namespace data
|
||||
|
||||
@@ -104,7 +104,11 @@ public:
|
||||
* @param cache The ledger cache
|
||||
* @param readOnly Whether the database should be in readonly mode
|
||||
*/
|
||||
CassandraBackendFamily(SettingsProviderType settingsProvider, data::LedgerCacheInterface& cache, bool readOnly)
|
||||
CassandraBackendFamily(
|
||||
SettingsProviderType settingsProvider,
|
||||
data::LedgerCacheInterface& cache,
|
||||
bool readOnly
|
||||
)
|
||||
: BackendInterface(cache)
|
||||
, settingsProvider_{std::move(settingsProvider)}
|
||||
, schema_{settingsProvider_}
|
||||
@@ -116,8 +120,8 @@ public:
|
||||
|
||||
if (not readOnly) {
|
||||
if (auto const res = handle_.execute(schema_.createKeyspace); not res.has_value()) {
|
||||
// on datastax, creation of keyspaces can be configured to only be done thru the admin
|
||||
// interface. this does not mean that the keyspace does not already exist tho.
|
||||
// on datastax, creation of keyspaces can be configured to only be done thru the
|
||||
// admin interface. this does not mean that the keyspace does not already exist tho.
|
||||
if (res.error().code() != CASS_ERROR_SERVER_UNAUTHORIZED)
|
||||
throw std::runtime_error("Could not create keyspace: " + res.error());
|
||||
}
|
||||
@@ -130,7 +134,8 @@ public:
|
||||
schema_.prepareStatements(handle_);
|
||||
} catch (std::runtime_error const& ex) {
|
||||
auto const error = fmt::format(
|
||||
"Failed to prepare the statements: {}; readOnly: {}. ReadOnly should be turned off or another Clio "
|
||||
"Failed to prepare the statements: {}; readOnly: {}. ReadOnly should be turned off "
|
||||
"or another Clio "
|
||||
"node with write access to DB should be started first.",
|
||||
ex.what(),
|
||||
readOnly
|
||||
@@ -169,8 +174,8 @@ public:
|
||||
auto cursor = txnCursor;
|
||||
if (cursor) {
|
||||
statement.bindAt(1, cursor->asTuple());
|
||||
LOG(log_.debug()) << "account = " << ripple::strHex(account) << " tuple = " << cursor->ledgerSequence
|
||||
<< cursor->transactionIndex;
|
||||
LOG(log_.debug()) << "account = " << ripple::strHex(account)
|
||||
<< " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
|
||||
} else {
|
||||
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
||||
auto const placeHolder = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
|
||||
@@ -195,7 +200,8 @@ public:
|
||||
auto numRows = results.numRows();
|
||||
LOG(log_.info()) << "num_rows = " << numRows;
|
||||
|
||||
for (auto [hash, data] : extract<ripple::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
|
||||
for (auto [hash, data] :
|
||||
extract<ripple::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
|
||||
hashes.push_back(hash);
|
||||
if (--numRows == 0) {
|
||||
LOG(log_.debug()) << "Setting cursor";
|
||||
@@ -251,7 +257,10 @@ public:
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerHeader>
|
||||
fetchLedgerBySequence(std::uint32_t const sequence, boost::asio::yield_context yield) const override
|
||||
fetchLedgerBySequence(
|
||||
std::uint32_t const sequence,
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
if (auto const lock = ledgerCache_.get(); lock.has_value() && lock->seq == sequence)
|
||||
return lock->ledger;
|
||||
@@ -259,7 +268,8 @@ public:
|
||||
auto const res = executor_.read(yield, schema_->selectLedgerBySeq, sequence);
|
||||
if (res) {
|
||||
if (auto const& result = res.value(); result) {
|
||||
if (auto const maybeValue = result.template get<std::vector<unsigned char>>(); maybeValue) {
|
||||
if (auto const maybeValue = result.template get<std::vector<unsigned char>>();
|
||||
maybeValue) {
|
||||
auto const header = util::deserializeHeader(ripple::makeSlice(*maybeValue));
|
||||
ledgerCache_.put(FetchLedgerCache::CacheEntry{header, sequence});
|
||||
return header;
|
||||
@@ -336,7 +346,10 @@ public:
|
||||
}
|
||||
|
||||
std::vector<TransactionAndMetadata>
|
||||
fetchAllTransactionsInLedger(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
|
||||
fetchAllTransactionsInLedger(
|
||||
std::uint32_t const ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
auto hashes = fetchAllTransactionHashesInLedger(ledgerSequence, yield);
|
||||
return fetchTransactions(hashes, yield);
|
||||
@@ -349,7 +362,8 @@ public:
|
||||
) const override
|
||||
{
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto const res = executor_.read(yield, schema_->selectAllTransactionHashesInLedger, ledgerSequence);
|
||||
auto const res =
|
||||
executor_.read(yield, schema_->selectAllTransactionHashesInLedger, ledgerSequence);
|
||||
|
||||
if (not res) {
|
||||
LOG(log_.error()) << "Could not fetch all transaction hashes: " << res.error();
|
||||
@@ -368,9 +382,12 @@ public:
|
||||
hashes.push_back(std::move(hash));
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
LOG(log_.debug()) << "Fetched " << hashes.size() << " transaction hashes from database in "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
|
||||
<< " milliseconds";
|
||||
LOG(
|
||||
log_.debug()
|
||||
) << "Fetched "
|
||||
<< hashes.size() << " transaction hashes from database in "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
|
||||
<< " milliseconds";
|
||||
|
||||
return hashes;
|
||||
}
|
||||
@@ -386,7 +403,8 @@ public:
|
||||
if (not res)
|
||||
return std::nullopt;
|
||||
|
||||
if (auto const maybeRow = res->template get<uint32_t, ripple::AccountID, bool>(); maybeRow) {
|
||||
if (auto const maybeRow = res->template get<uint32_t, ripple::AccountID, bool>();
|
||||
maybeRow) {
|
||||
auto [seq, owner, isBurned] = *maybeRow;
|
||||
auto result = std::make_optional<NFT>(tokenID, seq, owner, isBurned);
|
||||
|
||||
@@ -437,8 +455,8 @@ public:
|
||||
auto cursor = cursorIn;
|
||||
if (cursor) {
|
||||
statement.bindAt(1, cursor->asTuple());
|
||||
LOG(log_.debug()) << "token_id = " << ripple::strHex(tokenID) << " tuple = " << cursor->ledgerSequence
|
||||
<< cursor->transactionIndex;
|
||||
LOG(log_.debug()) << "token_id = " << ripple::strHex(tokenID)
|
||||
<< " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
|
||||
} else {
|
||||
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
||||
auto const placeHolder = forward ? 0 : std::numeric_limits<std::uint32_t>::max();
|
||||
@@ -461,7 +479,8 @@ public:
|
||||
auto numRows = results.numRows();
|
||||
LOG(log_.info()) << "num_rows = " << numRows;
|
||||
|
||||
for (auto [hash, data] : extract<ripple::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
|
||||
for (auto [hash, data] :
|
||||
extract<ripple::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
|
||||
hashes.push_back(hash);
|
||||
if (--numRows == 0) {
|
||||
LOG(log_.debug()) << "Setting cursor";
|
||||
@@ -495,7 +514,11 @@ public:
|
||||
) const override
|
||||
{
|
||||
auto const holderEntries = executor_.read(
|
||||
yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}
|
||||
yield,
|
||||
schema_->selectMPTHolders,
|
||||
mptID,
|
||||
cursorIn.value_or(ripple::AccountID(0)),
|
||||
Limit{limit}
|
||||
);
|
||||
|
||||
auto const& holderResults = holderEntries.value();
|
||||
@@ -513,7 +536,9 @@ public:
|
||||
|
||||
auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
|
||||
|
||||
auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) { return mpt.empty(); });
|
||||
auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) {
|
||||
return mpt.empty();
|
||||
});
|
||||
|
||||
mptObjects.erase(it, mptObjects.end());
|
||||
|
||||
@@ -531,7 +556,8 @@ public:
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence << ", key = " << ripple::to_string(key);
|
||||
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
|
||||
<< ", key = " << ripple::to_string(key);
|
||||
if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
|
||||
if (auto const result = res->template get<Blob>(); result) {
|
||||
if (result->size())
|
||||
@@ -553,7 +579,8 @@ public:
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence << ", key = " << ripple::to_string(key);
|
||||
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
|
||||
<< ", key = " << ripple::to_string(key);
|
||||
if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
|
||||
if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
|
||||
auto [_, seq] = result.value();
|
||||
@@ -571,7 +598,8 @@ public:
|
||||
fetchTransaction(ripple::uint256 const& hash, boost::asio::yield_context yield) const override
|
||||
{
|
||||
if (auto const res = executor_.read(yield, schema_->selectTransaction, hash); res) {
|
||||
if (auto const maybeValue = res->template get<Blob, Blob, uint32_t, uint32_t>(); maybeValue) {
|
||||
if (auto const maybeValue = res->template get<Blob, Blob, uint32_t, uint32_t>();
|
||||
maybeValue) {
|
||||
auto [transaction, meta, seq, date] = *maybeValue;
|
||||
return std::make_optional<TransactionAndMetadata>(transaction, meta, seq, date);
|
||||
}
|
||||
@@ -591,7 +619,8 @@ public:
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
if (auto const res = executor_.read(yield, schema_->selectSuccessor, key, ledgerSequence); res) {
|
||||
if (auto const res = executor_.read(yield, schema_->selectSuccessor, key, ledgerSequence);
|
||||
res) {
|
||||
if (auto const result = res->template get<ripple::uint256>(); result) {
|
||||
if (*result == kLAST_KEY)
|
||||
return std::nullopt;
|
||||
@@ -607,7 +636,10 @@ public:
|
||||
}
|
||||
|
||||
std::vector<TransactionAndMetadata>
|
||||
fetchTransactions(std::vector<ripple::uint256> const& hashes, boost::asio::yield_context yield) const override
|
||||
fetchTransactions(
|
||||
std::vector<ripple::uint256> const& hashes,
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
if (hashes.empty())
|
||||
return {};
|
||||
@@ -622,9 +654,10 @@ public:
|
||||
auto const timeDiff = util::timed([this, yield, &results, &hashes, &statements]() {
|
||||
// TODO: seems like a job for "hash IN (list of hashes)" instead?
|
||||
std::transform(
|
||||
std::cbegin(hashes), std::cend(hashes), std::back_inserter(statements), [this](auto const& hash) {
|
||||
return schema_->selectTransaction.bind(hash);
|
||||
}
|
||||
std::cbegin(hashes),
|
||||
std::cend(hashes),
|
||||
std::back_inserter(statements),
|
||||
[this](auto const& hash) { return schema_->selectTransaction.bind(hash); }
|
||||
);
|
||||
|
||||
auto const entries = executor_.readEach(yield, statements);
|
||||
@@ -633,7 +666,8 @@ public:
|
||||
std::cend(entries),
|
||||
std::back_inserter(results),
|
||||
[](auto const& res) -> TransactionAndMetadata {
|
||||
if (auto const maybeRow = res.template get<Blob, Blob, uint32_t, uint32_t>(); maybeRow)
|
||||
if (auto const maybeRow = res.template get<Blob, Blob, uint32_t, uint32_t>();
|
||||
maybeRow)
|
||||
return *maybeRow;
|
||||
|
||||
return {};
|
||||
@@ -642,8 +676,8 @@ public:
|
||||
});
|
||||
|
||||
ASSERT(numHashes == results.size(), "Number of hashes and results must match");
|
||||
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in " << timeDiff
|
||||
<< " milliseconds";
|
||||
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in "
|
||||
<< timeDiff << " milliseconds";
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -668,14 +702,18 @@ public:
|
||||
|
||||
// TODO: seems like a job for "key IN (list of keys)" instead?
|
||||
std::transform(
|
||||
std::cbegin(keys), std::cend(keys), std::back_inserter(statements), [this, &sequence](auto const& key) {
|
||||
return schema_->selectObject.bind(key, sequence);
|
||||
}
|
||||
std::cbegin(keys),
|
||||
std::cend(keys),
|
||||
std::back_inserter(statements),
|
||||
[this, &sequence](auto const& key) { return schema_->selectObject.bind(key, sequence); }
|
||||
);
|
||||
|
||||
auto const entries = executor_.readEach(yield, statements);
|
||||
std::transform(
|
||||
std::cbegin(entries), std::cend(entries), std::back_inserter(results), [](auto const& res) -> Blob {
|
||||
std::cbegin(entries),
|
||||
std::cend(entries),
|
||||
std::back_inserter(results),
|
||||
[](auto const& res) -> Blob {
|
||||
if (auto const maybeValue = res.template get<Blob>(); maybeValue)
|
||||
return *maybeValue;
|
||||
|
||||
@@ -688,34 +726,40 @@ public:
|
||||
}
|
||||
|
||||
std::vector<LedgerObject>
|
||||
fetchLedgerDiff(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
|
||||
fetchLedgerDiff(
|
||||
std::uint32_t const ledgerSequence,
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
auto const [keys, timeDiff] = util::timed([this, &ledgerSequence, yield]() -> std::vector<ripple::uint256> {
|
||||
auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
|
||||
if (not res) {
|
||||
LOG(log_.error()) << "Could not fetch ledger diff: " << res.error() << "; ledger = " << ledgerSequence;
|
||||
return {};
|
||||
}
|
||||
auto const [keys, timeDiff] =
|
||||
util::timed([this, &ledgerSequence, yield]() -> std::vector<ripple::uint256> {
|
||||
auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
|
||||
if (not res) {
|
||||
LOG(log_.error()) << "Could not fetch ledger diff: " << res.error()
|
||||
<< "; ledger = " << ledgerSequence;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const& results = res.value();
|
||||
if (not results) {
|
||||
LOG(log_.error()) << "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
|
||||
return {};
|
||||
}
|
||||
auto const& results = res.value();
|
||||
if (not results) {
|
||||
LOG(log_.error())
|
||||
<< "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ripple::uint256> resultKeys;
|
||||
for (auto [key] : extract<ripple::uint256>(results))
|
||||
resultKeys.push_back(key);
|
||||
std::vector<ripple::uint256> resultKeys;
|
||||
for (auto [key] : extract<ripple::uint256>(results))
|
||||
resultKeys.push_back(key);
|
||||
|
||||
return resultKeys;
|
||||
});
|
||||
return resultKeys;
|
||||
});
|
||||
|
||||
// one of the above errors must have happened
|
||||
if (keys.empty())
|
||||
return {};
|
||||
|
||||
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in " << timeDiff
|
||||
<< " milliseconds";
|
||||
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in "
|
||||
<< timeDiff << " milliseconds";
|
||||
|
||||
auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
||||
std::vector<LedgerObject> results;
|
||||
@@ -733,7 +777,10 @@ public:
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
fetchMigratorStatus(std::string const& migratorName, boost::asio::yield_context yield) const override
|
||||
fetchMigratorStatus(
|
||||
std::string const& migratorName,
|
||||
boost::asio::yield_context yield
|
||||
) const override
|
||||
{
|
||||
auto const res = executor_.read(yield, schema_->selectMigratorStatus, Text(migratorName));
|
||||
if (not res) {
|
||||
@@ -771,7 +818,8 @@ public:
|
||||
void
|
||||
doWriteLedgerObject(std::string&& key, std::uint32_t const seq, std::string&& blob) override
|
||||
{
|
||||
LOG(log_.trace()) << " Writing ledger object " << key.size() << ":" << seq << " [" << blob.size() << " bytes]";
|
||||
LOG(log_.trace()) << " Writing ledger object " << key.size() << ":" << seq << " ["
|
||||
<< blob.size() << " bytes]";
|
||||
|
||||
if (range_)
|
||||
executor_.write(schema_->insertDiff, seq, key);
|
||||
@@ -783,7 +831,8 @@ public:
|
||||
writeSuccessor(std::string&& key, std::uint32_t const seq, std::string&& successor) override
|
||||
{
|
||||
LOG(log_.trace()) << "Writing successor. key = " << key.size() << " bytes. "
|
||||
<< " seq = " << std::to_string(seq) << " successor = " << successor.size() << " bytes.";
|
||||
<< " seq = " << std::to_string(seq) << " successor = " << successor.size()
|
||||
<< " bytes.";
|
||||
ASSERT(!key.empty(), "Key must not be empty");
|
||||
ASSERT(!successor.empty(), "Successor must not be empty");
|
||||
|
||||
@@ -797,13 +846,15 @@ public:
|
||||
statements.reserve(data.size() * 10); // assume 10 transactions avg
|
||||
|
||||
for (auto& record : data) {
|
||||
std::ranges::transform(record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||
return schema_->insertAccountTx.bind(
|
||||
std::forward<decltype(account)>(account),
|
||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||
record.txHash
|
||||
);
|
||||
});
|
||||
std::ranges::transform(
|
||||
record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||
return schema_->insertAccountTx.bind(
|
||||
std::forward<decltype(account)>(account),
|
||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||
record.txHash
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
executor_.write(std::move(statements));
|
||||
@@ -815,13 +866,15 @@ public:
|
||||
std::vector<Statement> statements;
|
||||
statements.reserve(record.accounts.size());
|
||||
|
||||
std::ranges::transform(record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||
return schema_->insertAccountTx.bind(
|
||||
std::forward<decltype(account)>(account),
|
||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||
record.txHash
|
||||
);
|
||||
});
|
||||
std::ranges::transform(
|
||||
record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||
return schema_->insertAccountTx.bind(
|
||||
std::forward<decltype(account)>(account),
|
||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||
record.txHash
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
executor_.write(std::move(statements));
|
||||
}
|
||||
@@ -834,7 +887,9 @@ public:
|
||||
|
||||
std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
|
||||
return schema_->insertNFTTx.bind(
|
||||
record.tokenID, std::make_tuple(record.ledgerSequence, record.transactionIndex), record.txHash
|
||||
record.tokenID,
|
||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||
record.txHash
|
||||
);
|
||||
});
|
||||
|
||||
@@ -854,7 +909,12 @@ public:
|
||||
|
||||
executor_.write(schema_->insertLedgerTransaction, seq, hash);
|
||||
executor_.write(
|
||||
schema_->insertTransaction, std::move(hash), seq, date, std::move(transaction), std::move(metadata)
|
||||
schema_->insertTransaction,
|
||||
std::move(hash),
|
||||
seq,
|
||||
date,
|
||||
std::move(transaction),
|
||||
std::move(metadata)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -866,9 +926,9 @@ public:
|
||||
|
||||
for (NFTsData const& record : data) {
|
||||
if (!record.onlyUriChanged) {
|
||||
statements.push_back(
|
||||
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
|
||||
);
|
||||
statements.push_back(schema_->insertNFT.bind(
|
||||
record.tokenID, record.ledgerSequence, record.owner, record.isBurned
|
||||
));
|
||||
|
||||
// If `uri` is set (and it can be set to an empty uri), we know this
|
||||
// is a net-new NFT. That is, this NFT has not been seen before by
|
||||
@@ -881,15 +941,15 @@ public:
|
||||
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
|
||||
record.tokenID
|
||||
));
|
||||
statements.push_back(
|
||||
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
|
||||
);
|
||||
statements.push_back(schema_->insertNFTURI.bind(
|
||||
record.tokenID, record.ledgerSequence, record.uri.value()
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// only uri changed, we update the uri table only
|
||||
statements.push_back(
|
||||
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
|
||||
);
|
||||
statements.push_back(schema_->insertNFTURI.bind(
|
||||
record.tokenID, record.ledgerSequence, record.uri.value()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,14 +978,18 @@ public:
|
||||
writeMigratorStatus(std::string const& migratorName, std::string const& status) override
|
||||
{
|
||||
executor_.writeSync(
|
||||
schema_->insertMigratorStatus, data::cassandra::Text{migratorName}, data::cassandra::Text(status)
|
||||
schema_->insertMigratorStatus,
|
||||
data::cassandra::Text{migratorName},
|
||||
data::cassandra::Text(status)
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
writeNodeMessage(boost::uuids::uuid const& uuid, std::string message) override
|
||||
{
|
||||
executor_.writeSync(schema_->updateClioNodeMessage, data::cassandra::Text{std::move(message)}, uuid);
|
||||
executor_.writeSync(
|
||||
schema_->updateClioNodeMessage, data::cassandra::Text{std::move(message)}, uuid
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -78,12 +78,13 @@ concept SomeExecutionStrategy = requires(
|
||||
* @brief The requirements of a retry policy.
|
||||
*/
|
||||
template <typename T>
|
||||
concept SomeRetryPolicy = requires(T a, boost::asio::io_context ioc, CassandraError err, uint32_t attempt) {
|
||||
{ T(ioc) };
|
||||
{ a.shouldRetry(err) } -> std::same_as<bool>;
|
||||
{
|
||||
a.retry([]() {})
|
||||
} -> std::same_as<void>;
|
||||
};
|
||||
concept SomeRetryPolicy =
|
||||
requires(T a, boost::asio::io_context ioc, CassandraError err, uint32_t attempt) {
|
||||
{ T(ioc) };
|
||||
{ a.shouldRetry(err) } -> std::same_as<bool>;
|
||||
{
|
||||
a.retry([]() {})
|
||||
} -> std::same_as<void>;
|
||||
};
|
||||
|
||||
} // namespace data::cassandra
|
||||
|
||||
@@ -105,9 +105,9 @@ public:
|
||||
bool
|
||||
isTimeout() const
|
||||
{
|
||||
return code_ == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE or code_ == CASS_ERROR_LIB_REQUEST_TIMED_OUT or
|
||||
code_ == CASS_ERROR_SERVER_UNAVAILABLE or code_ == CASS_ERROR_SERVER_OVERLOADED or
|
||||
code_ == CASS_ERROR_SERVER_READ_TIMEOUT;
|
||||
return code_ == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE or
|
||||
code_ == CASS_ERROR_LIB_REQUEST_TIMED_OUT or code_ == CASS_ERROR_SERVER_UNAVAILABLE or
|
||||
code_ == CASS_ERROR_SERVER_OVERLOADED or code_ == CASS_ERROR_SERVER_READ_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,8 @@ Handle::Handle(Settings clusterSettings) : cluster_{clusterSettings}
|
||||
{
|
||||
}
|
||||
|
||||
Handle::Handle(std::string_view contactPoints) : Handle{Settings::defaultSettings().withContactPoints(contactPoints)}
|
||||
Handle::Handle(std::string_view contactPoints)
|
||||
: Handle{Settings::defaultSettings().withContactPoints(contactPoints)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -85,7 +86,9 @@ Handle::FutureType
|
||||
Handle::asyncReconnect(std::string_view keyspace) const
|
||||
{
|
||||
if (auto rc = asyncDisconnect().await(); not rc) // sync
|
||||
throw std::logic_error("Reconnect to keyspace '" + std::string{keyspace} + "' failed: " + rc.error());
|
||||
throw std::logic_error(
|
||||
"Reconnect to keyspace '" + std::string{keyspace} + "' failed: " + rc.error()
|
||||
);
|
||||
return asyncConnect(keyspace);
|
||||
}
|
||||
|
||||
@@ -123,7 +126,10 @@ Handle::asyncExecute(StatementType const& statement) const
|
||||
}
|
||||
|
||||
Handle::FutureWithCallbackType
|
||||
Handle::asyncExecute(StatementType const& statement, std::function<void(ResultOrErrorType)>&& cb) const
|
||||
Handle::asyncExecute(
|
||||
StatementType const& statement,
|
||||
std::function<void(ResultOrErrorType)>&& cb
|
||||
) const
|
||||
{
|
||||
return Handle::FutureWithCallbackType{cass_session_execute(session_, statement), std::move(cb)};
|
||||
}
|
||||
@@ -147,9 +153,14 @@ Handle::execute(std::vector<StatementType> const& statements) const
|
||||
}
|
||||
|
||||
Handle::FutureWithCallbackType
|
||||
Handle::asyncExecute(std::vector<StatementType> const& statements, std::function<void(ResultOrErrorType)>&& cb) const
|
||||
Handle::asyncExecute(
|
||||
std::vector<StatementType> const& statements,
|
||||
std::function<void(ResultOrErrorType)>&& cb
|
||||
) const
|
||||
{
|
||||
return Handle::FutureWithCallbackType{cass_session_execute_batch(session_, Batch{statements}), std::move(cb)};
|
||||
return Handle::FutureWithCallbackType{
|
||||
cass_session_execute_batch(session_, Batch{statements}), std::move(cb)
|
||||
};
|
||||
}
|
||||
|
||||
Handle::PreparedStatementType
|
||||
|
||||
@@ -293,14 +293,18 @@ public:
|
||||
execute(std::vector<StatementType> const& statements) const;
|
||||
|
||||
/**
|
||||
* @brief Execute a batch of (bound or simple) statements asynchronously with a completion callback.
|
||||
* @brief Execute a batch of (bound or simple) statements asynchronously with a completion
|
||||
* callback.
|
||||
*
|
||||
* @param statements The statements to execute
|
||||
* @param cb The callback to execute when data is ready
|
||||
* @return A future that holds onto the callback provided
|
||||
*/
|
||||
[[nodiscard]] FutureWithCallbackType
|
||||
asyncExecute(std::vector<StatementType> const& statements, std::function<void(ResultOrErrorType)>&& cb) const;
|
||||
asyncExecute(
|
||||
std::vector<StatementType> const& statements,
|
||||
std::function<void(ResultOrErrorType)>&& cb
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Prepare a statement.
|
||||
@@ -314,8 +318,8 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Extracts the results into series of std::tuple<Types...> by creating a simple wrapper with an STL input
|
||||
* iterator inside.
|
||||
* @brief Extracts the results into series of std::tuple<Types...> by creating a simple wrapper with
|
||||
* an STL input iterator inside.
|
||||
*
|
||||
* You can call .begin() and .end() in order to iterate as usual.
|
||||
* This also means that you can use it in a range-based for or with some algorithms.
|
||||
|
||||
@@ -43,9 +43,14 @@ namespace data::cassandra {
|
||||
* @return The qualified table name
|
||||
*/
|
||||
template <SomeSettingsProvider SettingsProviderType>
|
||||
[[nodiscard]] std::string inline qualifiedTableName(SettingsProviderType const& provider, std::string_view name)
|
||||
[[nodiscard]] std::string inline qualifiedTableName(
|
||||
SettingsProviderType const& provider,
|
||||
std::string_view name
|
||||
)
|
||||
{
|
||||
return fmt::format("{}.{}{}", provider.getKeyspace(), provider.getTablePrefix().value_or(""), name);
|
||||
return fmt::format(
|
||||
"{}.{}{}", provider.getKeyspace(), provider.getTablePrefix().value_or(""), name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +70,8 @@ public:
|
||||
*
|
||||
* @param settingsProvider The settings provider
|
||||
*/
|
||||
explicit Schema(SettingsProviderType const& settingsProvider) : settingsProvider_{std::cref(settingsProvider)}
|
||||
explicit Schema(SettingsProviderType const& settingsProvider)
|
||||
: settingsProvider_{std::cref(settingsProvider)}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -61,12 +61,18 @@ SettingsProvider::parseOptionalCertificate() const
|
||||
auto const path = std::filesystem::path(certPath.asString());
|
||||
std::ifstream fileStream(path.string(), std::ios::in);
|
||||
if (!fileStream) {
|
||||
throw std::system_error(errno, std::generic_category(), "Opening certificate " + path.string());
|
||||
throw std::system_error(
|
||||
errno, std::generic_category(), "Opening certificate " + path.string()
|
||||
);
|
||||
}
|
||||
|
||||
std::string contents(std::istreambuf_iterator<char>{fileStream}, std::istreambuf_iterator<char>{});
|
||||
std::string contents(
|
||||
std::istreambuf_iterator<char>{fileStream}, std::istreambuf_iterator<char>{}
|
||||
);
|
||||
if (fileStream.bad()) {
|
||||
throw std::system_error(errno, std::generic_category(), "Reading certificate " + path.string());
|
||||
throw std::system_error(
|
||||
errno, std::generic_category(), "Reading certificate " + path.string()
|
||||
);
|
||||
}
|
||||
|
||||
return contents;
|
||||
@@ -82,7 +88,8 @@ SettingsProvider::parseSettings() const
|
||||
|
||||
// all config values used in settings is under "database.cassandra" prefix
|
||||
if (config_.getValueView("secure_connect_bundle").hasValue()) {
|
||||
auto const bundle = Settings::SecureConnectionBundle{(config_.get<std::string>("secure_connect_bundle"))};
|
||||
auto const bundle =
|
||||
Settings::SecureConnectionBundle{(config_.get<std::string>("secure_connect_bundle"))};
|
||||
settings.connectionInfo = bundle;
|
||||
} else {
|
||||
Settings::ContactPoints out;
|
||||
@@ -101,12 +108,14 @@ SettingsProvider::parseSettings() const
|
||||
|
||||
if (config_.getValueView("connect_timeout").hasValue()) {
|
||||
auto const connectTimeoutSecond = config_.get<uint32_t>("connect_timeout");
|
||||
settings.connectionTimeout = std::chrono::milliseconds{connectTimeoutSecond * util::kMILLISECONDS_PER_SECOND};
|
||||
settings.connectionTimeout =
|
||||
std::chrono::milliseconds{connectTimeoutSecond * util::kMILLISECONDS_PER_SECOND};
|
||||
}
|
||||
|
||||
if (config_.getValueView("request_timeout").hasValue()) {
|
||||
auto const requestTimeoutSecond = config_.get<uint32_t>("request_timeout");
|
||||
settings.requestTimeout = std::chrono::milliseconds{requestTimeoutSecond * util::kMILLISECONDS_PER_SECOND};
|
||||
settings.requestTimeout =
|
||||
std::chrono::milliseconds{requestTimeoutSecond * util::kMILLISECONDS_PER_SECOND};
|
||||
}
|
||||
|
||||
settings.certificate = parseOptionalCertificate();
|
||||
|
||||
@@ -52,7 +52,8 @@ template <
|
||||
typename StatementType,
|
||||
typename HandleType = Handle,
|
||||
SomeRetryPolicy RetryPolicyType = ExponentialBackoffRetryPolicy>
|
||||
class AsyncExecutor : public std::enable_shared_from_this<AsyncExecutor<StatementType, HandleType, RetryPolicyType>> {
|
||||
class AsyncExecutor : public std::enable_shared_from_this<
|
||||
AsyncExecutor<StatementType, HandleType, RetryPolicyType>> {
|
||||
using FutureWithCallbackType = typename HandleType::FutureWithCallbackType;
|
||||
using CallbackType = std::function<void(typename HandleType::ResultOrErrorType)>;
|
||||
using RetryCallbackType = std::function<void()>;
|
||||
@@ -92,7 +93,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
auto ptr = std::make_shared<EnableMakeShared>(ioc, std::move(data), std::move(onComplete), std::move(onRetry));
|
||||
auto ptr = std::make_shared<EnableMakeShared>(
|
||||
ioc, std::move(data), std::move(onComplete), std::move(onRetry)
|
||||
);
|
||||
ptr->execute(handle);
|
||||
}
|
||||
|
||||
@@ -103,7 +106,10 @@ private:
|
||||
CallbackType&& onComplete,
|
||||
RetryCallbackType&& onRetry
|
||||
)
|
||||
: data_{std::move(data)}, retryPolicy_{ioc}, onComplete_{std::move(onComplete)}, onRetry_{std::move(onRetry)}
|
||||
: data_{std::move(data)}
|
||||
, retryPolicy_{ioc}
|
||||
, onComplete_{std::move(onComplete)}
|
||||
, onRetry_{std::move(onRetry)}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ namespace data::cassandra::impl {
|
||||
* UNLOGGED: For performance. Sends many separate updates in one network trip to be fast.
|
||||
* Use this for bulk-loading unrelated data, but know there's NO all-or-nothing guarantee.
|
||||
*
|
||||
* More info here: https://docs.datastax.com/en/developer/cpp-driver-dse/1.10/features/basics/batches/index.html
|
||||
* More info here:
|
||||
* https://docs.datastax.com/en/developer/cpp-driver-dse/1.10/features/basics/batches/index.html
|
||||
*/
|
||||
Batch::Batch(std::vector<Statement> const& statements)
|
||||
: ManagedObject{cass_batch_new(CASS_BATCH_TYPE_UNLOGGED), kBATCH_DELETER}
|
||||
|
||||
@@ -44,7 +44,8 @@ Cluster::Cluster(Settings const& settings) : ManagedObject{cass_cluster_new(), k
|
||||
using std::to_string;
|
||||
|
||||
cass_cluster_set_token_aware_routing(*this, cass_true);
|
||||
if (auto const rc = cass_cluster_set_protocol_version(*this, CASS_PROTOCOL_VERSION_V4); rc != CASS_OK) {
|
||||
if (auto const rc = cass_cluster_set_protocol_version(*this, CASS_PROTOCOL_VERSION_V4);
|
||||
rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Error setting cassandra protocol version to v4: {}", cass_error_desc(rc))
|
||||
);
|
||||
@@ -52,7 +53,11 @@ Cluster::Cluster(Settings const& settings) : ManagedObject{cass_cluster_new(), k
|
||||
|
||||
if (auto const rc = cass_cluster_set_num_threads_io(*this, settings.threads); rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Error setting cassandra io threads to {}: {}", settings.threads, cass_error_desc(rc))
|
||||
fmt::format(
|
||||
"Error setting cassandra io threads to {}: {}",
|
||||
settings.threads,
|
||||
cass_error_desc(rc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,24 +67,36 @@ Cluster::Cluster(Settings const& settings) : ManagedObject{cass_cluster_new(), k
|
||||
|
||||
// TODO: AWS keyspace reads should be local_one to save cost
|
||||
if (settings.provider == cassandra::impl::Provider::Keyspace) {
|
||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM); rc != CASS_OK) {
|
||||
throw std::runtime_error(fmt::format("Error setting keyspace consistency: {}", cass_error_desc(rc)));
|
||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM);
|
||||
rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Error setting keyspace consistency: {}", cass_error_desc(rc))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_QUORUM); rc != CASS_OK) {
|
||||
throw std::runtime_error(fmt::format("Error setting cassandra consistency: {}", cass_error_desc(rc)));
|
||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_QUORUM);
|
||||
rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Error setting cassandra consistency: {}", cass_error_desc(rc))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const rc = cass_cluster_set_core_connections_per_host(*this, settings.coreConnectionsPerHost);
|
||||
if (auto const rc =
|
||||
cass_cluster_set_core_connections_per_host(*this, settings.coreConnectionsPerHost);
|
||||
rc != CASS_OK) {
|
||||
throw std::runtime_error(fmt::format("Could not set core connections per host: {}", cass_error_desc(rc)));
|
||||
throw std::runtime_error(
|
||||
fmt::format("Could not set core connections per host: {}", cass_error_desc(rc))
|
||||
);
|
||||
}
|
||||
|
||||
auto const queueSize =
|
||||
settings.queueSizeIO.value_or(settings.maxWriteRequestsOutstanding + settings.maxReadRequestsOutstanding);
|
||||
auto const queueSize = settings.queueSizeIO.value_or(
|
||||
settings.maxWriteRequestsOutstanding + settings.maxReadRequestsOutstanding
|
||||
);
|
||||
if (auto const rc = cass_cluster_set_queue_size_io(*this, queueSize); rc != CASS_OK) {
|
||||
throw std::runtime_error(fmt::format("Could not set queue size for IO per host: {}", cass_error_desc(rc)));
|
||||
throw std::runtime_error(
|
||||
fmt::format("Could not set queue size for IO per host: {}", cass_error_desc(rc))
|
||||
);
|
||||
}
|
||||
|
||||
setupConnection(settings);
|
||||
@@ -111,7 +128,9 @@ Cluster::setupContactPoints(Settings::ContactPoints const& points)
|
||||
auto throwErrorIfNeeded = [](CassError rc, std::string const& label, std::string const& value) {
|
||||
if (rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Cassandra: Error setting {} [{}]: {}", label, value, cass_error_desc(rc))
|
||||
fmt::format(
|
||||
"Cassandra: Error setting {} [{}]: {}", label, value, cass_error_desc(rc)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -132,8 +151,12 @@ void
|
||||
Cluster::setupSecureBundle(Settings::SecureConnectionBundle const& bundle)
|
||||
{
|
||||
LOG(log_.debug()) << "Attempt connection using secure bundle";
|
||||
if (auto const rc = cass_cluster_set_cloud_secure_connection_bundle(*this, bundle.bundle.data()); rc != CASS_OK) {
|
||||
throw std::runtime_error("Failed to connect using secure connection bundle " + bundle.bundle);
|
||||
if (auto const rc =
|
||||
cass_cluster_set_cloud_secure_connection_bundle(*this, bundle.bundle.data());
|
||||
rc != CASS_OK) {
|
||||
throw std::runtime_error(
|
||||
"Failed to connect using secure connection bundle " + bundle.bundle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +178,9 @@ Cluster::setupCredentials(Settings const& settings)
|
||||
return;
|
||||
|
||||
LOG(log_.debug()) << "Set credentials; username: " << settings.username.value();
|
||||
cass_cluster_set_credentials(*this, settings.username.value().c_str(), settings.password.value().c_str());
|
||||
cass_cluster_set_credentials(
|
||||
*this, settings.username.value().c_str(), settings.password.value().c_str()
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace data::cassandra::impl
|
||||
|
||||
@@ -79,7 +79,8 @@ struct Settings {
|
||||
bool enableLog = false;
|
||||
|
||||
/** @brief Connect timeout specified in milliseconds */
|
||||
std::chrono::milliseconds connectionTimeout = std::chrono::milliseconds{kDEFAULT_CONNECTION_TIMEOUT};
|
||||
std::chrono::milliseconds connectionTimeout =
|
||||
std::chrono::milliseconds{kDEFAULT_CONNECTION_TIMEOUT};
|
||||
|
||||
/** @brief Request timeout specified in milliseconds */
|
||||
std::chrono::milliseconds requestTimeout = std::chrono::milliseconds{0}; // no timeout at all
|
||||
@@ -106,25 +107,31 @@ struct Settings {
|
||||
Provider provider = kDEFAULT_PROVIDER;
|
||||
|
||||
/** @brief Size of the IO queue */
|
||||
std::optional<uint32_t> queueSizeIO = std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<uint32_t> queueSizeIO =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/** @brief SSL certificate */
|
||||
std::optional<std::string> certificate = std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::string> certificate =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/** @brief Username/login */
|
||||
std::optional<std::string> username = std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::string> username =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/** @brief Password to match the `username` */
|
||||
std::optional<std::string> password = std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::string> password =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/**
|
||||
* @brief Creates a new Settings object as a copy of the current one with overridden contact points.
|
||||
* @brief Creates a new Settings object as a copy of the current one with overridden contact
|
||||
* points.
|
||||
*/
|
||||
Settings
|
||||
withContactPoints(std::string_view contactPoints)
|
||||
{
|
||||
auto tmp = *this;
|
||||
tmp.connectionInfo = ContactPoints{.contactPoints = std::string{contactPoints}, .port = std::nullopt};
|
||||
tmp.connectionInfo =
|
||||
ContactPoints{.contactPoints = std::string{contactPoints}, .port = std::nullopt};
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
@@ -267,8 +267,8 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Non-blocking query execution used for writing data. Contrast with write, this method does not execute
|
||||
* the statements in a batch.
|
||||
* @brief Non-blocking query execution used for writing data. Contrast with write, this method
|
||||
* does not execute the statements in a batch.
|
||||
*
|
||||
* Retries forever with retry policy specified by @ref AsyncExecutor.
|
||||
*
|
||||
@@ -278,7 +278,9 @@ public:
|
||||
void
|
||||
writeEach(std::vector<StatementType>&& statements)
|
||||
{
|
||||
std::ranges::for_each(std::move(statements), [this](auto& statement) { this->write(std::move(statement)); });
|
||||
std::ranges::for_each(std::move(statements), [this](auto& statement) {
|
||||
this->write(std::move(statement));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,7 +330,9 @@ public:
|
||||
future.emplace(handle_.get().asyncExecute(statements, [sself](auto&& res) mutable {
|
||||
boost::asio::post(
|
||||
boost::asio::get_associated_executor(*sself),
|
||||
[sself, res = std::forward<decltype(res)>(res)]() mutable { sself->complete(std::move(res)); }
|
||||
[sself, res = std::forward<decltype(res)>(res)]() mutable {
|
||||
sself->complete(std::move(res));
|
||||
}
|
||||
);
|
||||
}));
|
||||
};
|
||||
@@ -381,7 +385,9 @@ public:
|
||||
future.emplace(handle_.get().asyncExecute(statement, [sself](auto&& res) mutable {
|
||||
boost::asio::post(
|
||||
boost::asio::get_associated_executor(*sself),
|
||||
[sself, res = std::forward<decltype(res)>(res)]() mutable { sself->complete(std::move(res)); }
|
||||
[sself, res = std::forward<decltype(res)>(res)]() mutable {
|
||||
sself->complete(std::move(res));
|
||||
}
|
||||
);
|
||||
}));
|
||||
};
|
||||
@@ -431,19 +437,23 @@ public:
|
||||
futures.reserve(numOutstanding);
|
||||
counters_->registerReadStarted(statements.size());
|
||||
|
||||
auto init = [this, &statements, &futures, &errorsCount, &numOutstanding]<typename Self>(Self& self) {
|
||||
auto init = [this, &statements, &futures, &errorsCount, &numOutstanding]<typename Self>(
|
||||
Self& self
|
||||
) {
|
||||
auto sself = std::make_shared<Self>(std::move(self));
|
||||
auto executionHandler = [&errorsCount, &numOutstanding, sself](auto const& res) mutable {
|
||||
if (not res)
|
||||
++errorsCount;
|
||||
auto executionHandler =
|
||||
[&errorsCount, &numOutstanding, sself](auto const& res) mutable {
|
||||
if (not res)
|
||||
++errorsCount;
|
||||
|
||||
// when all async operations complete unblock the result
|
||||
if (--numOutstanding == 0) {
|
||||
boost::asio::post(boost::asio::get_associated_executor(*sself), [sself]() mutable {
|
||||
sself->complete();
|
||||
});
|
||||
}
|
||||
};
|
||||
// when all async operations complete unblock the result
|
||||
if (--numOutstanding == 0) {
|
||||
boost::asio::post(
|
||||
boost::asio::get_associated_executor(*sself),
|
||||
[sself]() mutable { sself->complete(); }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
std::transform(
|
||||
std::cbegin(statements),
|
||||
@@ -461,7 +471,9 @@ public:
|
||||
numReadRequestsOutstanding_ -= statements.size();
|
||||
|
||||
if (errorsCount > 0) {
|
||||
ASSERT(errorsCount <= statements.size(), "Errors number cannot exceed statements number");
|
||||
ASSERT(
|
||||
errorsCount <= statements.size(), "Errors number cannot exceed statements number"
|
||||
);
|
||||
counters_->registerReadError(errorsCount);
|
||||
counters_->registerReadFinished(startTime, statements.size() - errorsCount);
|
||||
throw DatabaseTimeout{};
|
||||
@@ -471,7 +483,8 @@ public:
|
||||
std::vector<ResultType> results;
|
||||
results.reserve(futures.size());
|
||||
|
||||
// it's safe to call blocking get on futures here as we already waited for the coroutine to resume above.
|
||||
// it's safe to call blocking get on futures here as we already waited for the coroutine to
|
||||
// resume above.
|
||||
std::transform(
|
||||
std::make_move_iterator(std::begin(futures)),
|
||||
std::make_move_iterator(std::end(futures)),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user