mirror of
https://github.com/XRPLF/clio.git
synced 2026-03-03 19:22:47 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d6caf8a7 | ||
|
|
b9ee5187b6 | ||
|
|
e43b678872 | ||
|
|
ef0a765e07 | ||
|
|
f9c89264da | ||
|
|
16030d1d81 | ||
|
|
1220d632b5 | ||
|
|
e9052bcd80 | ||
|
|
c1f6a6eb31 | ||
|
|
af736717fc | ||
|
|
2d6f82c27f | ||
|
|
6ba58f42f0 | ||
|
|
480264ff8f | ||
|
|
584d2bb5f2 | ||
|
|
9d3dbce73b | ||
|
|
8b6f65f0b7 | ||
|
|
91aba853af | ||
|
|
af03a2fbe4 | ||
|
|
61c47c8efd | ||
|
|
8f36b1d5ca | ||
|
|
9fd15eb08b | ||
|
|
cf77a10555 | ||
|
|
437168aa13 | ||
|
|
8a7c6b0aa4 | ||
|
|
93a344c3fa | ||
|
|
59d07fab64 | ||
|
|
3bb3e0b9f9 | ||
|
|
a72e5a180f | ||
|
|
0ebbaaadef | ||
|
|
b983aea15d | ||
|
|
63e7f9a72b | ||
|
|
eebee4d671 | ||
|
|
a6d5f94470 | ||
|
|
2b473c8613 |
@@ -22,7 +22,7 @@ BreakBeforeBinaryOperators: false
|
|||||||
BreakBeforeBraces: WebKit
|
BreakBeforeBraces: WebKit
|
||||||
BreakBeforeTernaryOperators: true
|
BreakBeforeTernaryOperators: true
|
||||||
BreakConstructorInitializersBeforeComma: true
|
BreakConstructorInitializersBeforeComma: true
|
||||||
ColumnLimit: 120
|
ColumnLimit: 100
|
||||||
CommentPragmas: "^ IWYU pragma:"
|
CommentPragmas: "^ IWYU pragma:"
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
ConstructorInitializerIndentWidth: 4
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
|||||||
@@ -2,15 +2,6 @@ _help_parse: Options affecting listfile parsing
|
|||||||
parse:
|
parse:
|
||||||
_help_additional_commands:
|
_help_additional_commands:
|
||||||
- Specify structure for custom cmake functions
|
- Specify structure for custom cmake functions
|
||||||
additional_commands:
|
|
||||||
foo:
|
|
||||||
flags:
|
|
||||||
- BAR
|
|
||||||
- BAZ
|
|
||||||
kwargs:
|
|
||||||
HEADERS: "*"
|
|
||||||
SOURCES: "*"
|
|
||||||
DEPENDS: "*"
|
|
||||||
_help_override_spec:
|
_help_override_spec:
|
||||||
- Override configurations per-command where available
|
- Override configurations per-command where available
|
||||||
override_spec: {}
|
override_spec: {}
|
||||||
@@ -30,7 +21,7 @@ format:
|
|||||||
line_width: 120
|
line_width: 120
|
||||||
_help_tab_size:
|
_help_tab_size:
|
||||||
- How many spaces to tab for indent
|
- How many spaces to tab for indent
|
||||||
tab_size: 2
|
tab_size: 4
|
||||||
_help_use_tabchars:
|
_help_use_tabchars:
|
||||||
- If true, lines are indented using tab characters (utf-8
|
- If true, lines are indented using tab characters (utf-8
|
||||||
- 0x09) instead of <tab_size> space characters (utf-8 0x20).
|
- 0x09) instead of <tab_size> space characters (utf-8 0x20).
|
||||||
@@ -70,19 +61,19 @@ format:
|
|||||||
_help_dangle_parens:
|
_help_dangle_parens:
|
||||||
- If a statement is wrapped to more than one line, than dangle
|
- If a statement is wrapped to more than one line, than dangle
|
||||||
- the closing parenthesis on its own line.
|
- the closing parenthesis on its own line.
|
||||||
dangle_parens: true
|
dangle_parens: false
|
||||||
_help_dangle_align:
|
_help_dangle_align:
|
||||||
- If the trailing parenthesis must be 'dangled' on its on
|
- If the trailing parenthesis must be 'dangled' on its on
|
||||||
- "line, then align it to this reference: `prefix`: the start"
|
- "line, then align it to this reference: `prefix`: the start"
|
||||||
- "of the statement, `prefix-indent`: the start of the"
|
- "of the statement, `prefix-indent`: the start of the"
|
||||||
- "statement, plus one indentation level, `child`: align to"
|
- "statement, plus one indentation level, `child`: align to"
|
||||||
- the column of the arguments
|
- the column of the arguments
|
||||||
dangle_align: prefix
|
dangle_align: prefix
|
||||||
_help_min_prefix_chars:
|
_help_min_prefix_chars:
|
||||||
- If the statement spelling length (including space and
|
- If the statement spelling length (including space and
|
||||||
- parenthesis) is smaller than this amount, then force reject
|
- parenthesis) is smaller than this amount, then force reject
|
||||||
- nested layouts.
|
- nested layouts.
|
||||||
min_prefix_chars: 4
|
min_prefix_chars: 18
|
||||||
_help_max_prefix_chars:
|
_help_max_prefix_chars:
|
||||||
- If the statement spelling length (including space and
|
- If the statement spelling length (including space and
|
||||||
- parenthesis) is larger than the tab width by more than this
|
- parenthesis) is larger than the tab width by more than this
|
||||||
@@ -127,7 +118,7 @@ _help_markup: Options affecting comment reflow and formatting.
|
|||||||
markup:
|
markup:
|
||||||
_help_bullet_char:
|
_help_bullet_char:
|
||||||
- What character to use for bulleted lists
|
- What character to use for bulleted lists
|
||||||
bullet_char: "*"
|
bullet_char: "-"
|
||||||
_help_enum_char:
|
_help_enum_char:
|
||||||
- What character to use as punctuation after numerals in an
|
- What character to use as punctuation after numerals in an
|
||||||
- enumerated list
|
- enumerated list
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: ${{ inputs.push_image == 'true' && inputs.dockerhub_repo != '' }}
|
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:
|
with:
|
||||||
username: ${{ env.DOCKERHUB_USER }}
|
username: ${{ env.DOCKERHUB_USER }}
|
||||||
password: ${{ env.DOCKERHUB_PW }}
|
password: ${{ env.DOCKERHUB_PW }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: ${{ inputs.push_image == 'true' }}
|
if: ${{ inputs.push_image == 'true' }}
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
2
.github/actions/code-coverage/action.yml
vendored
2
.github/actions/code-coverage/action.yml
vendored
@@ -24,7 +24,7 @@ runs:
|
|||||||
-j8 --exclude-throw-branches
|
-j8 --exclude-throw-branches
|
||||||
|
|
||||||
- name: Archive coverage report
|
- name: Archive coverage report
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: coverage-report.xml
|
name: coverage-report.xml
|
||||||
path: build/coverage_report.xml
|
path: build/coverage_report.xml
|
||||||
|
|||||||
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,11 +48,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Download Clio binary from artifact
|
- name: Download Clio binary from artifact
|
||||||
if: ${{ inputs.artifact_name != null }}
|
if: ${{ inputs.artifact_name != null }}
|
||||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.artifact_name }}
|
name: ${{ inputs.artifact_name }}
|
||||||
path: ./docker/clio/artifact/
|
path: ./docker/clio/artifact/
|
||||||
|
|||||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -23,6 +23,7 @@ on:
|
|||||||
- "cmake/**"
|
- "cmake/**"
|
||||||
- "src/**"
|
- "src/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
|
- "benchmarks/**"
|
||||||
|
|
||||||
- docs/config-description.md
|
- docs/config-description.md
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -100,9 +101,9 @@ jobs:
|
|||||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_server_Linux_Release_gcc
|
name: clio_server_Linux_Release_gcc
|
||||||
|
|
||||||
|
|||||||
10
.github/workflows/check-libxrpl.yml
vendored
10
.github/workflows/check-libxrpl.yml
vendored
@@ -24,12 +24,12 @@ jobs:
|
|||||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: false
|
enable_ccache: false
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
run: strip build/clio_tests
|
run: strip build/clio_tests
|
||||||
|
|
||||||
- name: Upload clio_tests
|
- name: Upload clio_tests
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_tests_check_libxrpl
|
name: clio_tests_check_libxrpl
|
||||||
path: build/clio_tests
|
path: build/clio_tests
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
image: ghcr.io/xrplf/clio-ci:14342e087ceb8b593027198bf9ef06a43833c696
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_tests_check_libxrpl
|
name: clio_tests_check_libxrpl
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Create an issue
|
- name: Create an issue
|
||||||
uses: ./.github/actions/create-issue
|
uses: ./.github/actions/create-issue
|
||||||
|
|||||||
6
.github/workflows/clang-tidy.yml
vendored
6
.github/workflows/clang-tidy.yml
vendored
@@ -39,12 +39,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: false
|
enable_ccache: false
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create PR with fixes
|
- name: Create PR with fixes
|
||||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
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:
|
env:
|
||||||
GH_REPO: ${{ github.repository }}
|
GH_REPO: ${{ github.repository }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
lfs: true
|
lfs: true
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: false
|
enable_ccache: false
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -169,7 +169,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Create an issue
|
- name: Create an issue
|
||||||
uses: ./.github/actions/create-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:
|
jobs:
|
||||||
auto-update:
|
auto-update:
|
||||||
uses: XRPLF/actions/.github/workflows/pre-commit-autoupdate.yml@ad4ab1ae5a54a4bab0e87294c31fc0729f788b2b
|
uses: XRPLF/actions/.github/workflows/pre-commit-autoupdate.yml@8b19d3462e52cd8ea4d76b4c8d0f7533e7469c15
|
||||||
with:
|
with:
|
||||||
sign_commit: true
|
sign_commit: true
|
||||||
committer: "Clio CI <skuznetsov@ripple.com>"
|
committer: "Clio CI <skuznetsov@ripple.com>"
|
||||||
|
|||||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-hooks:
|
run-hooks:
|
||||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@282890f46d6921249d5659dd38babcb0bd8aef48
|
uses: XRPLF/actions/.github/workflows/pre-commit.yml@320be44621ca2a080f05aeb15817c44b84518108
|
||||||
with:
|
with:
|
||||||
runs_on: heavy
|
runs_on: heavy
|
||||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:14342e087ceb8b593027198bf9ef06a43833c696" }'
|
||||||
|
|||||||
20
.github/workflows/reusable-build.yml
vendored
20
.github/workflows/reusable-build.yml
vendored
@@ -88,14 +88,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Cleanup workspace
|
- name: Cleanup workspace
|
||||||
if: ${{ runner.os == 'macOS' }}
|
if: ${{ runner.os == 'macOS' }}
|
||||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: ${{ inputs.download_ccache }}
|
enable_ccache: ${{ inputs.download_ccache }}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore ccache cache
|
- name: Restore ccache cache
|
||||||
if: ${{ inputs.download_ccache && github.ref != 'refs/heads/develop' }}
|
if: ${{ inputs.download_ccache && github.ref != 'refs/heads/develop' }}
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ steps.cache_key.outputs.key }}
|
key: ${{ steps.cache_key.outputs.key }}
|
||||||
@@ -151,7 +151,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload build time analyze report
|
- name: Upload build time analyze report
|
||||||
if: ${{ inputs.analyze_build_time }}
|
if: ${{ inputs.analyze_build_time }}
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build_time_report.txt
|
path: build_time_report.txt
|
||||||
@@ -164,7 +164,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save ccache cache
|
- name: Save ccache cache
|
||||||
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' }}
|
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' }}
|
||||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ steps.cache_key.outputs.key }}
|
key: ${{ steps.cache_key.outputs.key }}
|
||||||
@@ -179,28 +179,28 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload clio_server
|
- name: Upload clio_server
|
||||||
if: ${{ inputs.upload_clio_server && !inputs.code_coverage && !inputs.analyze_build_time }}
|
if: ${{ inputs.upload_clio_server && !inputs.code_coverage && !inputs.analyze_build_time }}
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_server_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_server_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build/clio_server
|
path: build/clio_server
|
||||||
|
|
||||||
- name: Upload clio_tests
|
- name: Upload clio_tests
|
||||||
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build/clio_tests
|
path: build/clio_tests
|
||||||
|
|
||||||
- name: Upload clio_integration_tests
|
- name: Upload clio_integration_tests
|
||||||
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
if: ${{ !inputs.code_coverage && !inputs.analyze_build_time && !inputs.package }}
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build/clio_integration_tests
|
path: build/clio_integration_tests
|
||||||
|
|
||||||
- name: Upload Clio Linux package
|
- name: Upload Clio Linux package
|
||||||
if: ${{ inputs.package }}
|
if: ${{ inputs.package }}
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_deb_package_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_deb_package_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
path: build/*.deb
|
path: build/*.deb
|
||||||
|
|||||||
10
.github/workflows/reusable-release.yml
vendored
10
.github/workflows/reusable-release.yml
vendored
@@ -55,16 +55,16 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: false
|
enable_ccache: false
|
||||||
|
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
path: release_artifacts
|
path: release_artifacts
|
||||||
pattern: clio_server_*
|
pattern: clio_server_*
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Prepare release artifacts
|
- name: Prepare release artifacts
|
||||||
run: .github/scripts/prepare-release-artifacts.sh release_artifacts
|
run: .github/scripts/prepare-release-artifacts.sh release_artifacts
|
||||||
|
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
path: release_artifacts
|
path: release_artifacts
|
||||||
pattern: clio_deb_package_*
|
pattern: clio_deb_package_*
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
git-cliff "${BASE_COMMIT}..HEAD" --ignore-tags "nightly|-b|-rc" >> "${RUNNER_TEMP}/release_notes.md"
|
git-cliff "${BASE_COMMIT}..HEAD" --ignore-tags "nightly|-b|-rc" >> "${RUNNER_TEMP}/release_notes.md"
|
||||||
|
|
||||||
- name: Upload release notes
|
- name: Upload release notes
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: release_notes_${{ inputs.version }}
|
name: release_notes_${{ inputs.version }}
|
||||||
path: "${RUNNER_TEMP}/release_notes.md"
|
path: "${RUNNER_TEMP}/release_notes.md"
|
||||||
|
|||||||
68
.github/workflows/reusable-test.yml
vendored
68
.github/workflows/reusable-test.yml
vendored
@@ -45,54 +45,29 @@ jobs:
|
|||||||
|
|
||||||
if: ${{ inputs.run_unit_tests }}
|
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:
|
steps:
|
||||||
- name: Cleanup workspace
|
- name: Cleanup workspace
|
||||||
if: ${{ runner.os == 'macOS' }}
|
if: ${{ runner.os == 'macOS' }}
|
||||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
|
|
||||||
- name: Make clio_tests executable
|
- name: Make clio_tests executable
|
||||||
run: chmod +x ./clio_tests
|
run: chmod +x ./clio_tests
|
||||||
|
|
||||||
- name: Run clio_tests (regular)
|
- name: Run clio_tests
|
||||||
if: ${{ env.SANITIZER_IGNORE_ERRORS == 'false' }}
|
continue-on-error: true
|
||||||
|
id: run_clio_tests
|
||||||
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
|
- 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
|
uses: ./.github/actions/create-issue
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
@@ -100,10 +75,13 @@ jobs:
|
|||||||
labels: "bug"
|
labels: "bug"
|
||||||
title: "[${{ inputs.conan_profile }}] reported issues"
|
title: "[${{ inputs.conan_profile }}] reported issues"
|
||||||
body: >
|
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 }}/
|
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:
|
integration_tests:
|
||||||
name: Integration testing
|
name: Integration testing
|
||||||
@@ -124,13 +102,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Cleanup workspace
|
- name: Cleanup workspace
|
||||||
if: ${{ runner.os == 'macOS' }}
|
if: ${{ runner.os == 'macOS' }}
|
||||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||||
|
|
||||||
- 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' }}
|
if: ${{ runner.os == 'macOS' }}
|
||||||
timeout-minutes: 3
|
|
||||||
run: |
|
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 \
|
docker run \
|
||||||
--detach \
|
--detach \
|
||||||
--name scylladb \
|
--name scylladb \
|
||||||
@@ -142,11 +126,15 @@ jobs:
|
|||||||
--memory 16G \
|
--memory 16G \
|
||||||
scylladb/scylla
|
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
|
until [ "$(docker inspect -f '{{.State.Health.Status}}' scylladb)" == "healthy" ]; do
|
||||||
sleep 5
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
name: clio_integration_tests_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download report artifact
|
- name: Download report artifact
|
||||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||||
with:
|
with:
|
||||||
name: coverage-report.xml
|
name: coverage-report.xml
|
||||||
path: build
|
path: build
|
||||||
|
|||||||
1
.github/workflows/sanitizers.yml
vendored
1
.github/workflows/sanitizers.yml
vendored
@@ -15,7 +15,6 @@ on:
|
|||||||
- ".github/actions/**"
|
- ".github/actions/**"
|
||||||
- "!.github/actions/build-docker-image/**"
|
- "!.github/actions/build-docker-image/**"
|
||||||
- "!.github/actions/create-issue/**"
|
- "!.github/actions/create-issue/**"
|
||||||
- .github/scripts/execute-tests-under-sanitizer.sh
|
|
||||||
|
|
||||||
- CMakeLists.txt
|
- CMakeLists.txt
|
||||||
- conanfile.py
|
- conanfile.py
|
||||||
|
|||||||
24
.github/workflows/update-docker-ci.yml
vendored
24
.github/workflows/update-docker-ci.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
|||||||
needs: repo
|
needs: repo
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
needs: repo
|
needs: repo
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
needs: [repo, gcc-amd64, gcc-arm64]
|
needs: [repo, gcc-amd64, gcc-arm64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -153,7 +153,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' }}
|
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:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USER }}
|
username: ${{ secrets.DOCKERHUB_USER }}
|
||||||
password: ${{ secrets.DOCKERHUB_PW }}
|
password: ${{ secrets.DOCKERHUB_PW }}
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
needs: repo
|
needs: repo
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -219,7 +219,7 @@ jobs:
|
|||||||
needs: [repo, gcc-merge]
|
needs: [repo, gcc-merge]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -250,7 +250,7 @@ jobs:
|
|||||||
needs: [repo, gcc-merge]
|
needs: [repo, gcc-merge]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -281,7 +281,7 @@ jobs:
|
|||||||
needs: [repo, tools-amd64, tools-arm64]
|
needs: [repo, tools-amd64, tools-arm64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
@@ -294,7 +294,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -316,7 +316,7 @@ jobs:
|
|||||||
needs: [repo, tools-merge]
|
needs: [repo, tools-merge]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: ./.github/actions/build-docker-image
|
- uses: ./.github/actions/build-docker-image
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -338,7 +338,7 @@ jobs:
|
|||||||
needs: [repo, gcc-merge, clang, tools-merge]
|
needs: [repo, gcc-merge, clang, tools-merge]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: ./.github/actions/build-docker-image
|
- uses: ./.github/actions/build-docker-image
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
6
.github/workflows/upload-conan-deps.yml
vendored
6
.github/workflows/upload-conan-deps.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Calculate conan matrix
|
- name: Calculate conan matrix
|
||||||
id: set-matrix
|
id: set-matrix
|
||||||
@@ -75,10 +75,10 @@ jobs:
|
|||||||
CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Prepare runner
|
- name: Prepare runner
|
||||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||||
with:
|
with:
|
||||||
enable_ccache: false
|
enable_ccache: false
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,8 +4,11 @@
|
|||||||
.build
|
.build
|
||||||
.cache
|
.cache
|
||||||
.vscode
|
.vscode
|
||||||
|
.zed
|
||||||
.python-version
|
.python-version
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.sanitizer-report
|
.sanitizer-report
|
||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
config.json
|
config.json
|
||||||
|
CLAUDE.md
|
||||||
|
.claude/**
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ repos:
|
|||||||
|
|
||||||
# Autoformat: YAML, JSON, Markdown, etc.
|
# Autoformat: YAML, JSON, Markdown, etc.
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: 14abee445aea04b39069c19b4bd54efff6775819 # frozen: v3.7.4
|
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ repos:
|
|||||||
]
|
]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 831207fd435b47aeffdf6af853097e64322b4d44 # frozen: 25.12.0
|
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ repos:
|
|||||||
language: script
|
language: script
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: 75ca4ad908dc4a99f57921f29b7e6c1521e10b26 # frozen: v21.1.8
|
rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
args: [--style=file]
|
args: [--style=file]
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
project(clio VERSION ${CLIO_VERSION} HOMEPAGE_URL "https://github.com/XRPLF/clio"
|
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 ====================================== #
|
# =========================== Options ====================================== #
|
||||||
option(verbose "Verbose build" FALSE)
|
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)
|
target_include_directories(clio_options INTERFACE ${CMAKE_SOURCE_DIR}/src)
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Clio tweaks and checks
|
# Clio tweaks and checks
|
||||||
@@ -58,36 +57,36 @@ add_subdirectory(src)
|
|||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
|
||||||
if (benchmark)
|
if (benchmark)
|
||||||
add_subdirectory(benchmarks)
|
add_subdirectory(benchmarks)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Enable selected sanitizer if enabled via `san`
|
# Enable selected sanitizer if enabled via `san`
|
||||||
if (san)
|
if (san)
|
||||||
set(SUPPORTED_SANITIZERS "address" "thread" "memory" "undefined")
|
set(SUPPORTED_SANITIZERS "address" "thread" "memory" "undefined")
|
||||||
if (NOT san IN_LIST SUPPORTED_SANITIZERS)
|
if (NOT san IN_LIST SUPPORTED_SANITIZERS)
|
||||||
message(FATAL_ERROR "Error: Unsupported sanitizer '${san}'. Supported values are: ${SUPPORTED_SANITIZERS}.")
|
message(FATAL_ERROR "Error: Unsupported sanitizer '${san}'. Supported values are: ${SUPPORTED_SANITIZERS}.")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Sanitizers recommend minimum of -O1 for reasonable performance so we enable it for debug builds
|
# Sanitizers recommend minimum of -O1 for reasonable performance so we enable it for debug builds
|
||||||
set(SAN_OPTIMIZATION_FLAG "")
|
set(SAN_OPTIMIZATION_FLAG "")
|
||||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
set(SAN_OPTIMIZATION_FLAG -O1)
|
set(SAN_OPTIMIZATION_FLAG -O1)
|
||||||
endif ()
|
endif ()
|
||||||
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
||||||
|
|
||||||
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Generate `docs` target for doxygen documentation if enabled Note: use `make docs` to generate the documentation
|
# Generate `docs` target for doxygen documentation if enabled Note: use `make docs` to generate the documentation
|
||||||
if (docs)
|
if (docs)
|
||||||
add_subdirectory(docs)
|
add_subdirectory(docs)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include(install/install)
|
include(install/install)
|
||||||
if (package)
|
if (package)
|
||||||
include(ClioPackage)
|
include(ClioPackage)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (snapshot)
|
if (snapshot)
|
||||||
add_subdirectory(tools/snapshot)
|
add_subdirectory(tools/snapshot)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
add_executable(clio_benchmark)
|
add_executable(clio_benchmark)
|
||||||
|
|
||||||
target_sources(
|
target_sources(clio_benchmark
|
||||||
clio_benchmark
|
PRIVATE # Common
|
||||||
PRIVATE # Common
|
Main.cpp
|
||||||
Main.cpp
|
Playground.cpp
|
||||||
Playground.cpp
|
# ExecutionContext
|
||||||
# ExecutionContext
|
util/async/ExecutionContextBenchmarks.cpp
|
||||||
util/async/ExecutionContextBenchmarks.cpp
|
# Logger
|
||||||
# Logger
|
util/log/LoggerBenchmark.cpp
|
||||||
util/log/LoggerBenchmark.cpp
|
# WorkQueue
|
||||||
# WorkQueue
|
rpc/WorkQueueBenchmarks.cpp)
|
||||||
rpc/WorkQueueBenchmarks.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
include(deps/gbench)
|
include(deps/gbench)
|
||||||
|
|
||||||
target_include_directories(clio_benchmark PRIVATE .)
|
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})
|
set_target_properties(clio_benchmark PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
|
|
||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
#include <boost/asio/thread_pool.hpp>
|
|
||||||
#include <boost/json/object.hpp>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -53,12 +51,15 @@ auto const kCONFIG = ClioConfigDefinition{
|
|||||||
{"log.channels.[].channel", Array{ConfigValue{ConfigType::String}}},
|
{"log.channels.[].channel", Array{ConfigValue{ConfigType::String}}},
|
||||||
{"log.channels.[].level", Array{ConfigValue{ConfigType::String}}},
|
{"log.channels.[].level", Array{ConfigValue{ConfigType::String}}},
|
||||||
{"log.level", ConfigValue{ConfigType::String}.defaultValue("info")},
|
{"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.is_async", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||||
{"log.enable_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
{"log.enable_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
|
||||||
{"log.directory", ConfigValue{ConfigType::String}.optional()},
|
{"log.directory", ConfigValue{ConfigType::String}.optional()},
|
||||||
{"log.rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048).withConstraint(gValidateUint32)},
|
{"log.rotation_size",
|
||||||
{"log.directory_max_files", ConfigValue{ConfigType::Integer}.defaultValue(25).withConstraint(gValidateUint32)},
|
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")},
|
{"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("none")},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,9 +127,14 @@ benchmarkWorkQueue(benchmark::State& state)
|
|||||||
ASSERT(totalQueued <= itemsPerClient * clientThreads, "Queued more than requested");
|
ASSERT(totalQueued <= itemsPerClient * clientThreads, "Queued more than requested");
|
||||||
|
|
||||||
if (maxQueueSize == 0) {
|
if (maxQueueSize == 0) {
|
||||||
ASSERT(totalQueued == itemsPerClient * clientThreads, "Queued exactly the expected amount");
|
ASSERT(
|
||||||
|
totalQueued == itemsPerClient * clientThreads, "Queued exactly the expected amount"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
ASSERT(totalQueued >= std::min(maxQueueSize, itemsPerClient * clientThreads), "Queued less than expected");
|
ASSERT(
|
||||||
|
totalQueued >= std::min(maxQueueSize, itemsPerClient * clientThreads),
|
||||||
|
"Queued less than expected"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ uniqueLogDir()
|
|||||||
{
|
{
|
||||||
auto const epochTime = std::chrono::high_resolution_clock::now().time_since_epoch();
|
auto const epochTime = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||||
auto const tmpDir = std::filesystem::temp_directory_path();
|
auto const tmpDir = std::filesystem::temp_directory_path();
|
||||||
std::string const dirName =
|
std::string const dirName = fmt::format(
|
||||||
fmt::format("logs_{}", std::chrono::duration_cast<std::chrono::microseconds>(epochTime).count());
|
"logs_{}", std::chrono::duration_cast<std::chrono::microseconds>(epochTime).count()
|
||||||
|
);
|
||||||
return tmpDir / "clio_benchmark" / dirName;
|
return tmpDir / "clio_benchmark" / dirName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +109,8 @@ benchmarkConcurrentFileLogging(benchmark::State& state)
|
|||||||
channel, fileSink, spdlog::thread_pool(), spdlog::async_overflow_policy::block
|
channel, fileSink, spdlog::thread_pool(), spdlog::async_overflow_policy::block
|
||||||
);
|
);
|
||||||
spdlog::register_logger(logger);
|
spdlog::register_logger(logger);
|
||||||
Logger const threadLogger = BenchmarkLoggingInitializer::getLogger(std::move(logger));
|
Logger const threadLogger =
|
||||||
|
BenchmarkLoggingInitializer::getLogger(std::move(logger));
|
||||||
|
|
||||||
barrier.arrive_and_wait();
|
barrier.arrive_and_wait();
|
||||||
|
|
||||||
@@ -124,13 +126,16 @@ benchmarkConcurrentFileLogging(benchmark::State& state)
|
|||||||
spdlog::shutdown();
|
spdlog::shutdown();
|
||||||
|
|
||||||
auto const end = std::chrono::high_resolution_clock::now();
|
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);
|
std::filesystem::remove_all(logDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const totalMessages = numThreads * messagesPerThread;
|
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["Threads"] = numThreads;
|
||||||
state.counters["MessagesPerThread"] = messagesPerThread;
|
state.counters["MessagesPerThread"] = messagesPerThread;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
find_program(CCACHE_PATH "ccache")
|
find_program(CCACHE_PATH "ccache")
|
||||||
if (CCACHE_PATH)
|
if (CCACHE_PATH)
|
||||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}")
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}")
|
||||||
message(STATUS "Using ccache: ${CCACHE_PATH}")
|
message(STATUS "Using ccache: ${CCACHE_PATH}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)
|
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)
|
||||||
message(FATAL_ERROR "Clang 16+ required for building clio")
|
message(FATAL_ERROR "Clang 16+ required for building clio")
|
||||||
endif ()
|
endif ()
|
||||||
set(is_clang TRUE)
|
set(is_clang TRUE)
|
||||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15)
|
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15)
|
||||||
message(FATAL_ERROR "AppleClang 15+ required for building clio")
|
message(FATAL_ERROR "AppleClang 15+ required for building clio")
|
||||||
endif ()
|
endif ()
|
||||||
set(is_appleclang TRUE)
|
set(is_appleclang TRUE)
|
||||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12)
|
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12)
|
||||||
message(FATAL_ERROR "GCC 12+ required for building clio")
|
message(FATAL_ERROR "GCC 12+ required for building clio")
|
||||||
endif ()
|
endif ()
|
||||||
set(is_gcc TRUE)
|
set(is_gcc TRUE)
|
||||||
else ()
|
else ()
|
||||||
message(FATAL_ERROR "Supported compilers: AppleClang 15+, Clang 16+, GCC 12+")
|
message(FATAL_ERROR "Supported compilers: AppleClang 15+, Clang 16+, GCC 12+")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (san)
|
if (san)
|
||||||
string(TOLOWER ${san} san)
|
string(TOLOWER ${san} san)
|
||||||
set(SAN_FLAG "-fsanitize=${san}")
|
set(SAN_FLAG "-fsanitize=${san}")
|
||||||
set(SAN_LIB "")
|
set(SAN_LIB "")
|
||||||
if (is_gcc)
|
if (is_gcc)
|
||||||
if (san STREQUAL "address")
|
if (san STREQUAL "address")
|
||||||
set(SAN_LIB "asan")
|
set(SAN_LIB "asan")
|
||||||
elseif (san STREQUAL "thread")
|
elseif (san STREQUAL "thread")
|
||||||
set(SAN_LIB "tsan")
|
set(SAN_LIB "tsan")
|
||||||
elseif (san STREQUAL "memory")
|
elseif (san STREQUAL "memory")
|
||||||
set(SAN_LIB "msan")
|
set(SAN_LIB "msan")
|
||||||
elseif (san STREQUAL "undefined")
|
elseif (san STREQUAL "undefined")
|
||||||
set(SAN_LIB "ubsan")
|
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 ()
|
||||||
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 ()
|
endif ()
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
if (lint)
|
if (lint)
|
||||||
|
|
||||||
# Find clang-tidy binary
|
# Find clang-tidy binary
|
||||||
if (DEFINED ENV{CLIO_CLANG_TIDY_BIN})
|
if (DEFINED ENV{CLIO_CLANG_TIDY_BIN})
|
||||||
set(_CLANG_TIDY_BIN $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})
|
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")
|
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 ()
|
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)
|
if (NOT _CLANG_TIDY_BIN)
|
||||||
message(
|
message(FATAL_ERROR "clang-tidy binary not found. Please set the CLIO_CLANG_TIDY_BIN environment variable or install clang-tidy."
|
||||||
FATAL_ERROR
|
)
|
||||||
"clang-tidy binary not found. Please set the CLIO_CLANG_TIDY_BIN environment variable or install clang-tidy."
|
endif ()
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Support for https://github.com/matus-chochlik/ctcache
|
# Support for https://github.com/matus-chochlik/ctcache
|
||||||
find_program(CLANG_TIDY_CACHE_PATH NAMES "clang-tidy-cache")
|
find_program(CLANG_TIDY_CACHE_PATH NAMES "clang-tidy-cache")
|
||||||
if (CLANG_TIDY_CACHE_PATH)
|
if (CLANG_TIDY_CACHE_PATH)
|
||||||
set(_CLANG_TIDY_CMD "${CLANG_TIDY_CACHE_PATH};${_CLANG_TIDY_BIN}"
|
set(_CLANG_TIDY_CMD "${CLANG_TIDY_CACHE_PATH};${_CLANG_TIDY_BIN}"
|
||||||
CACHE STRING "A combined command to run clang-tidy with caching wrapper"
|
CACHE STRING "A combined command to run clang-tidy with caching wrapper")
|
||||||
)
|
else ()
|
||||||
else ()
|
set(_CLANG_TIDY_CMD "${_CLANG_TIDY_BIN}")
|
||||||
set(_CLANG_TIDY_CMD "${_CLANG_TIDY_BIN}")
|
endif ()
|
||||||
endif ()
|
|
||||||
|
|
||||||
set(CMAKE_CXX_CLANG_TIDY "${_CLANG_TIDY_CMD};--quiet")
|
set(CMAKE_CXX_CLANG_TIDY "${_CLANG_TIDY_CMD};--quiet")
|
||||||
message(STATUS "Using clang-tidy: ${CMAKE_CXX_CLANG_TIDY}")
|
message(STATUS "Using clang-tidy: ${CMAKE_CXX_CLANG_TIDY}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -1,47 +1,41 @@
|
|||||||
find_package(Git REQUIRED)
|
find_package(Git REQUIRED)
|
||||||
|
|
||||||
if (DEFINED ENV{GITHUB_BRANCH_NAME})
|
if (DEFINED ENV{GITHUB_BRANCH_NAME})
|
||||||
set(GIT_BUILD_BRANCH $ENV{GITHUB_BRANCH_NAME})
|
set(GIT_BUILD_BRANCH $ENV{GITHUB_BRANCH_NAME})
|
||||||
set(GIT_COMMIT_HASH $ENV{GITHUB_HEAD_SHA})
|
set(GIT_COMMIT_HASH $ENV{GITHUB_HEAD_SHA})
|
||||||
else ()
|
else ()
|
||||||
set(GIT_COMMAND branch --show-current)
|
set(GIT_COMMAND branch --show-current)
|
||||||
execute_process(
|
execute_process(COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_BUILD_BRANCH
|
OUTPUT_VARIABLE GIT_BUILD_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
|
||||||
)
|
|
||||||
|
|
||||||
set(GIT_COMMAND rev-parse HEAD)
|
set(GIT_COMMAND rev-parse HEAD)
|
||||||
execute_process(
|
execute_process(COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
COMMAND ${GIT_EXECUTABLE} ${GIT_COMMAND} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH
|
OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
|
||||||
)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
execute_process(
|
execute_process(COMMAND date +%Y%m%d%H%M%S WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE BUILD_DATE
|
||||||
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)
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY
|
|
||||||
)
|
|
||||||
|
|
||||||
message(STATUS "Git branch: ${GIT_BUILD_BRANCH}")
|
message(STATUS "Git branch: ${GIT_BUILD_BRANCH}")
|
||||||
message(STATUS "Git commit hash: ${GIT_COMMIT_HASH}")
|
message(STATUS "Git commit hash: ${GIT_COMMIT_HASH}")
|
||||||
message(STATUS "Build date: ${BUILD_DATE}")
|
message(STATUS "Build date: ${BUILD_DATE}")
|
||||||
|
|
||||||
if (DEFINED ENV{FORCE_CLIO_VERSION} AND NOT "$ENV{FORCE_CLIO_VERSION}" STREQUAL "")
|
if (DEFINED ENV{FORCE_CLIO_VERSION} AND NOT "$ENV{FORCE_CLIO_VERSION}" STREQUAL "")
|
||||||
message(STATUS "Using explicitly provided '${FORCE_CLIO_VERSION}' as Clio version")
|
message(STATUS "Using explicitly provided '${FORCE_CLIO_VERSION}' as Clio version")
|
||||||
|
|
||||||
set(CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
set(CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
||||||
set(DOC_CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
set(DOC_CLIO_VERSION "$ENV{FORCE_CLIO_VERSION}")
|
||||||
else ()
|
else ()
|
||||||
message(STATUS "Using 'YYYYMMDDHMS-<branch>-<git short rev>' as Clio version")
|
message(STATUS "Using 'YYYYMMDDHMS-<branch>-<git short rev>' as Clio version")
|
||||||
|
|
||||||
string(SUBSTRING ${GIT_COMMIT_HASH} 0 7 GIT_COMMIT_HASH_SHORT)
|
string(SUBSTRING ${GIT_COMMIT_HASH} 0 7 GIT_COMMIT_HASH_SHORT)
|
||||||
|
|
||||||
set(CLIO_VERSION "${BUILD_DATE}-${GIT_BUILD_BRANCH}-${GIT_COMMIT_HASH_SHORT}")
|
set(CLIO_VERSION "${BUILD_DATE}-${GIT_BUILD_BRANCH}-${GIT_COMMIT_HASH_SHORT}")
|
||||||
set(DOC_CLIO_VERSION "develop")
|
set(DOC_CLIO_VERSION "develop")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||||
set(CLIO_VERSION "${CLIO_VERSION}+DEBUG")
|
set(CLIO_VERSION "${CLIO_VERSION}+DEBUG")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
message(STATUS "Build version: ${CLIO_VERSION}")
|
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)
|
find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
|
||||||
|
|
||||||
if (DEFINED CODE_COVERAGE_GCOV_TOOL)
|
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})
|
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")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
else ()
|
else ()
|
||||||
find_program(LLVMCOV_PATH llvm-cov)
|
find_program(LLVMCOV_PATH llvm-cov)
|
||||||
endif ()
|
endif ()
|
||||||
if (LLVMCOV_PATH)
|
if (LLVMCOV_PATH)
|
||||||
set(GCOV_TOOL "${LLVMCOV_PATH} gcov")
|
set(GCOV_TOOL "${LLVMCOV_PATH} gcov")
|
||||||
endif ()
|
endif ()
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
||||||
find_program(GCOV_PATH gcov)
|
find_program(GCOV_PATH gcov)
|
||||||
set(GCOV_TOOL "${GCOV_PATH}")
|
set(GCOV_TOOL "${GCOV_PATH}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Check supported compiler (Clang, GNU and Flang)
|
# Check supported compiler (Clang, GNU and Flang)
|
||||||
get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
|
get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
|
||||||
foreach (LANG ${LANGUAGES})
|
foreach (LANG ${LANGUAGES})
|
||||||
if ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
if ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||||
if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
|
if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
|
||||||
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
|
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 ()
|
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 ()
|
endforeach ()
|
||||||
|
|
||||||
set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "")
|
set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "")
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
|
check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
|
||||||
if (HAVE_cxx_fprofile_abs_path)
|
if (HAVE_cxx_fprofile_abs_path)
|
||||||
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||||
endif ()
|
endif ()
|
||||||
include(CheckCCompilerFlag)
|
include(CheckCCompilerFlag)
|
||||||
check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
|
check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
|
||||||
if (HAVE_c_fprofile_abs_path)
|
if (HAVE_c_fprofile_abs_path)
|
||||||
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(CMAKE_Fortran_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS}
|
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}
|
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}
|
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_EXE_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used for linking binaries during coverage builds." FORCE)
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE ""
|
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE ""
|
||||||
CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE
|
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
|
||||||
mark_as_advanced(
|
CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE)
|
||||||
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)
|
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR 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)
|
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")
|
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||||
link_libraries(gcov)
|
link_libraries(gcov)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Defines a target for running and collection code coverage information Builds dependencies, runs the given executable
|
# 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
|
# with CMake 3.4+) ) The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the GCVOR
|
||||||
# command.
|
# command.
|
||||||
function (setup_target_for_coverage_gcovr)
|
function (setup_target_for_coverage_gcovr)
|
||||||
set(options NONE)
|
set(options NONE)
|
||||||
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
|
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
|
||||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||||
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||||
|
|
||||||
if (NOT GCOV_TOOL)
|
if (NOT GCOV_TOOL)
|
||||||
message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...")
|
message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (NOT GCOVR_PATH)
|
if (NOT GCOVR_PATH)
|
||||||
message(FATAL_ERROR "Could not find gcovr tool! Aborting...")
|
message(FATAL_ERROR "Could not find gcovr tool! Aborting...")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
|
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
|
||||||
if (DEFINED Coverage_BASE_DIRECTORY)
|
if (DEFINED Coverage_BASE_DIRECTORY)
|
||||||
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
|
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)
|
|
||||||
else ()
|
else ()
|
||||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
|
set(BASEDIR ${PROJECT_SOURCE_DIR})
|
||||||
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}")
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
message(STATUS "Command to generate gcovr coverage data: ")
|
if (NOT DEFINED Coverage_FORMAT)
|
||||||
string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}")
|
set(Coverage_FORMAT xml)
|
||||||
message(STATUS "${GCOVR_CMD_SPACED}")
|
endif ()
|
||||||
endif ()
|
|
||||||
|
|
||||||
add_custom_target(
|
if ("--output" IN_LIST GCOVR_ADDITIONAL_ARGS)
|
||||||
${Coverage_NAME}
|
message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...")
|
||||||
COMMAND ${GCOVR_EXEC_TESTS_CMD}
|
else ()
|
||||||
COMMAND ${GCOVR_FOLDER_CMD}
|
if ((Coverage_FORMAT STREQUAL "html-details") OR (Coverage_FORMAT STREQUAL "html-nested"))
|
||||||
COMMAND ${GCOVR_CMD}
|
set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html)
|
||||||
BYPRODUCTS ${GCOVR_OUTPUT_FILE}
|
set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME})
|
||||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
elseif (Coverage_FORMAT STREQUAL "html-single")
|
||||||
DEPENDS ${Coverage_DEPENDENCIES}
|
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html)
|
||||||
VERBATIM # Protect arguments to commands
|
elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL "json-details")
|
||||||
COMMENT "Running gcovr to produce code coverage report."
|
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
|
if ((Coverage_FORMAT STREQUAL "cobertura") OR (Coverage_FORMAT STREQUAL "xml"))
|
||||||
add_custom_command(
|
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura "${GCOVR_OUTPUT_FILE}")
|
||||||
TARGET ${Coverage_NAME} POST_BUILD COMMAND ;
|
list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty)
|
||||||
COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}"
|
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
|
endfunction () # setup_target_for_coverage_gcovr
|
||||||
|
|
||||||
function (append_coverage_compiler_flags)
|
function (append_coverage_compiler_flags)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
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_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
|
||||||
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_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}")
|
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
|
||||||
endfunction () # append_coverage_compiler_flags
|
endfunction () # append_coverage_compiler_flags
|
||||||
|
|
||||||
# Setup coverage for specific library
|
# Setup coverage for specific library
|
||||||
function (append_coverage_compiler_flags_to_target name mode)
|
function (append_coverage_compiler_flags_to_target name mode)
|
||||||
separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}")
|
separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}")
|
||||||
target_compile_options(${name} ${mode} ${_flag_list})
|
target_compile_options(${name} ${mode} ${_flag_list})
|
||||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||||
target_link_libraries(${name} ${mode} gcov)
|
target_link_libraries(${name} ${mode} gcov)
|
||||||
endif ()
|
endif ()
|
||||||
endfunction ()
|
endfunction ()
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
|
|||||||
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||||
|
|
||||||
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT})
|
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT})
|
||||||
add_custom_target(
|
add_custom_target(docs
|
||||||
docs
|
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
||||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
COMMENT "Generating API documentation with Doxygen"
|
||||||
COMMENT "Generating API documentation with Doxygen"
|
VERBATIM)
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
if (DEFINED CMAKE_LINKER_TYPE)
|
if (DEFINED CMAKE_LINKER_TYPE)
|
||||||
message(STATUS "Custom linker is already set: ${CMAKE_LINKER_TYPE}")
|
message(STATUS "Custom linker is already set: ${CMAKE_LINKER_TYPE}")
|
||||||
return()
|
return()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
find_program(MOLD_PATH mold)
|
find_program(MOLD_PATH mold)
|
||||||
|
|
||||||
if (MOLD_PATH AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if (MOLD_PATH AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
message(STATUS "Using Mold linker: ${MOLD_PATH}")
|
message(STATUS "Using Mold linker: ${MOLD_PATH}")
|
||||||
set(CMAKE_LINKER_TYPE MOLD)
|
set(CMAKE_LINKER_TYPE MOLD)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -31,52 +31,54 @@ set(COMPILER_FLAGS
|
|||||||
# -Wduplicated-cond -Wlogical-op -Wuseless-cast ) endif ()
|
# -Wduplicated-cond -Wlogical-op -Wuseless-cast ) endif ()
|
||||||
|
|
||||||
if (is_clang)
|
if (is_clang)
|
||||||
list(APPEND COMPILER_FLAGS -Wshadow # gcc is to aggressive with shadowing
|
list(APPEND COMPILER_FLAGS -Wshadow # gcc is to aggressive with shadowing
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78147
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78147
|
||||||
)
|
)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (is_appleclang)
|
if (is_appleclang)
|
||||||
list(APPEND COMPILER_FLAGS -Wreorder-init-list)
|
list(APPEND COMPILER_FLAGS -Wreorder-init-list)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (san)
|
if (san)
|
||||||
# When building with sanitizers some compilers will actually produce extra warnings/errors. We don't want this yet, at
|
# When building with sanitizers some compilers will actually produce extra warnings/errors. We don't want this yet,
|
||||||
# least not until we have fixed all runtime issues reported by the sanitizers. Once that is done we can start removing
|
# at least not until we have fixed all runtime issues reported by the sanitizers. Once that is done we can start
|
||||||
# some of these and trying to fix it in our codebase. We can never remove all of below because most of them are
|
# removing some of these and trying to fix it in our codebase. We can never remove all of below because most of them
|
||||||
# reported from deep inside libraries like boost or libxrpl.
|
# are reported from deep inside libraries like boost or libxrpl.
|
||||||
#
|
#
|
||||||
# TODO: Address in https://github.com/XRPLF/clio/issues/1885
|
# TODO: Address in https://github.com/XRPLF/clio/issues/1885
|
||||||
list(
|
list(APPEND
|
||||||
APPEND
|
COMPILER_FLAGS
|
||||||
COMPILER_FLAGS
|
-Wno-error=tsan # Disables treating TSAN warnings as errors
|
||||||
-Wno-error=tsan # Disables treating TSAN warnings as errors
|
-Wno-tsan # Disables TSAN warnings (thread-safety analysis)
|
||||||
-Wno-tsan # Disables TSAN warnings (thread-safety analysis)
|
-Wno-uninitialized # Disables warnings about uninitialized variables (AddressSanitizer,
|
||||||
-Wno-uninitialized # Disables warnings about uninitialized variables (AddressSanitizer, UndefinedBehaviorSanitizer,
|
# UndefinedBehaviorSanitizer, etc.)
|
||||||
# etc.)
|
-Wno-stringop-overflow # Disables warnings about potential string operation overflows (AddressSanitizer)
|
||||||
-Wno-stringop-overflow # Disables warnings about potential string operation overflows (AddressSanitizer)
|
-Wno-unsafe-buffer-usage # Disables warnings about unsafe memory operations (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-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-function # Disables warnings about unused functions (LeakSanitizer, memory-related issues)
|
-Wno-unused-but-set-variable # Disables warnings about unused variables (MemorySanitizer)
|
||||||
-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-analysis # Disables warnings related to thread safety usage (ThreadSanitizer)
|
-Wno-thread-safety # 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-sign-compare # Disables warnings about signed/unsigned comparison (UndefinedBehaviorSanitizer)
|
-Wno-nonnull # Disables warnings related to null pointer dereferencing (UndefinedBehaviorSanitizer)
|
||||||
-Wno-nonnull # Disables warnings related to null pointer dereferencing (UndefinedBehaviorSanitizer)
|
-Wno-address # Disables warnings about address-related issues (UndefinedBehaviorSanitizer)
|
||||||
-Wno-address # Disables warnings about address-related issues (UndefinedBehaviorSanitizer)
|
-Wno-array-bounds # Disables array bounds checks (UndefinedBehaviorSanitizer)
|
||||||
-Wno-array-bounds # Disables array bounds checks (UndefinedBehaviorSanitizer)
|
)
|
||||||
)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# See https://github.com/cpp-best-practices/cppbestpractices/blob/master/02-Use_the_Tools_Available.md#gcc--clang for
|
# See https://github.com/cpp-best-practices/cppbestpractices/blob/master/02-Use_the_Tools_Available.md#gcc--clang for
|
||||||
# the flags description
|
# the flags description
|
||||||
|
|
||||||
if (time_trace)
|
if (time_trace)
|
||||||
if (is_clang OR is_appleclang)
|
if (is_clang OR is_appleclang)
|
||||||
list(APPEND COMPILER_FLAGS -ftime-trace)
|
list(APPEND COMPILER_FLAGS -ftime-trace)
|
||||||
else ()
|
else ()
|
||||||
message(FATAL_ERROR "Clang or AppleClang is required to use `-ftime-trace`")
|
message(FATAL_ERROR "Clang or AppleClang is required to use `-ftime-trace`")
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_compile_options(clio_options INTERFACE ${COMPILER_FLAGS})
|
target_compile_options(clio_options INTERFACE ${COMPILER_FLAGS})
|
||||||
|
|
||||||
|
# Add debug symbols for all builds, including Release. This is needed to get useful stack traces in production.
|
||||||
|
target_compile_options(clio_options INTERFACE -g)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ include(CheckIncludeFileCXX)
|
|||||||
|
|
||||||
check_include_file_cxx("source_location" SOURCE_LOCATION_AVAILABLE)
|
check_include_file_cxx("source_location" SOURCE_LOCATION_AVAILABLE)
|
||||||
if (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 ()
|
endif ()
|
||||||
|
|
||||||
check_include_file_cxx("experimental/source_location" EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
|
check_include_file_cxx("experimental/source_location" EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
|
||||||
if (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 ()
|
endif ()
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
if ("${san}" STREQUAL "")
|
if ("${san}" STREQUAL "")
|
||||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_LINK)
|
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_LINK)
|
||||||
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_USE_BACKTRACE)
|
target_compile_definitions(clio_options INTERFACE BOOST_STACKTRACE_USE_BACKTRACE)
|
||||||
find_package(libbacktrace REQUIRED CONFIG)
|
find_package(libbacktrace REQUIRED CONFIG)
|
||||||
else ()
|
else ()
|
||||||
# Some sanitizers (TSAN and ASAN for sure) can't be used with libbacktrace because they have their own backtracing
|
# 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.
|
# capabilities and there are conflicts. In any case, this makes sure Clio code knows that backtrace is not
|
||||||
# See relevant conan profiles for sanitizers where we disable stacktrace in Boost explicitly.
|
# available. See relevant conan profiles for sanitizers where we disable stacktrace in Boost explicitly.
|
||||||
target_compile_definitions(clio_options INTERFACE CLIO_WITHOUT_STACKTRACE)
|
target_compile_definitions(clio_options INTERFACE CLIO_WITHOUT_STACKTRACE)
|
||||||
message(STATUS "Sanitizer enabled, disabling stacktrace")
|
message(STATUS "Sanitizer enabled, disabling stacktrace")
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
find_package(spdlog REQUIRED)
|
find_package(spdlog REQUIRED)
|
||||||
|
|
||||||
if (NOT TARGET spdlog::spdlog)
|
if (NOT TARGET spdlog::spdlog)
|
||||||
message(FATAL_ERROR "spdlog::spdlog target not found")
|
message(FATAL_ERROR "spdlog::spdlog target not found")
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
21
conan.lock
21
conan.lock
@@ -3,15 +3,15 @@
|
|||||||
"requires": [
|
"requires": [
|
||||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||||
"xrpl/3.0.0#534d3f65a336109eee929b88962bae4e%1765375071.547",
|
"xrpl/3.1.0#3d408ab8c8020014fa7dd52bc7cc7ea8%1769706825.165",
|
||||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||||
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1767636069.964",
|
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1768312128.781",
|
||||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
|
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
|
||||||
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
|
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
|
||||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||||
"openssl/1.1.1w#a8f0792d7c5121b954578a7149d23e03%1756223730.729",
|
"openssl/1.1.1w#a8f0792d7c5121b954578a7149d23e03%1756223730.729",
|
||||||
"nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1765850143.957",
|
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
|
||||||
"minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1754325004.374",
|
"minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1754325004.374",
|
||||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||||
"libuv/1.46.0#dc28c1f653fa197f00db5b577a6f6011%1754325003.592",
|
"libuv/1.46.0#dc28c1f653fa197f00db5b577a6f6011%1754325003.592",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||||
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
|
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
|
||||||
"http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1754325001.385",
|
"http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1754325001.385",
|
||||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1755784855.585",
|
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
|
||||||
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
|
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
|
||||||
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
||||||
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
|
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
|
||||||
@@ -40,17 +40,20 @@
|
|||||||
],
|
],
|
||||||
"python_requires": [],
|
"python_requires": [],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"boost/1.83.0": [
|
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88": [
|
||||||
|
null,
|
||||||
|
"protobuf/3.21.12"
|
||||||
|
],
|
||||||
|
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1": [
|
||||||
null,
|
null,
|
||||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
|
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
|
||||||
],
|
],
|
||||||
"protobuf/3.21.12": [
|
|
||||||
null,
|
|
||||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88"
|
|
||||||
],
|
|
||||||
"lz4/1.9.4": [
|
"lz4/1.9.4": [
|
||||||
"lz4/1.10.0"
|
"lz4/1.10.0"
|
||||||
],
|
],
|
||||||
|
"boost/1.90.0": [
|
||||||
|
"boost/1.83.0"
|
||||||
|
],
|
||||||
"sqlite3/3.44.2": [
|
"sqlite3/3.44.2": [
|
||||||
"sqlite3/3.49.1"
|
"sqlite3/3.49.1"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class ClioConan(ConanFile):
|
|||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
requires = [
|
requires = [
|
||||||
"boost/1.83.0",
|
|
||||||
"cassandra-cpp-driver/2.17.0",
|
"cassandra-cpp-driver/2.17.0",
|
||||||
"fmt/12.1.0",
|
"fmt/12.1.0",
|
||||||
"grpc/1.50.1",
|
"grpc/1.50.1",
|
||||||
@@ -20,7 +19,7 @@ class ClioConan(ConanFile):
|
|||||||
"openssl/1.1.1w",
|
"openssl/1.1.1w",
|
||||||
"protobuf/3.21.12",
|
"protobuf/3.21.12",
|
||||||
"spdlog/1.17.0",
|
"spdlog/1.17.0",
|
||||||
"xrpl/3.0.0",
|
"xrpl/3.1.0",
|
||||||
"zlib/1.3.1",
|
"zlib/1.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -43,6 +42,7 @@ class ClioConan(ConanFile):
|
|||||||
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
|
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
|
||||||
|
|
||||||
def requirements(self):
|
def requirements(self):
|
||||||
|
self.requires("boost/1.83.0", force=True)
|
||||||
self.requires("gtest/1.17.0")
|
self.requires("gtest/1.17.0")
|
||||||
self.requires("benchmark/1.9.4")
|
self.requires("benchmark/1.9.4")
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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/).
|
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_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp Stopper.cpp WebHandlers.cpp)
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
clio_app
|
clio_app
|
||||||
PUBLIC clio_cluster
|
PUBLIC clio_cluster
|
||||||
clio_etl
|
clio_etl
|
||||||
clio_feed
|
clio_feed
|
||||||
clio_migration
|
clio_migration
|
||||||
clio_rpc
|
clio_rpc
|
||||||
clio_web
|
clio_web
|
||||||
PRIVATE Boost::program_options
|
PRIVATE Boost::program_options)
|
||||||
)
|
|
||||||
|
|||||||
@@ -58,12 +58,16 @@ CliArgs::parse(int argc, char const* argv[])
|
|||||||
positional.add("conf", 1);
|
positional.add("conf", 1);
|
||||||
|
|
||||||
auto const printHelp = [&description]() {
|
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;
|
po::variables_map parsed;
|
||||||
try {
|
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);
|
po::notify(parsed);
|
||||||
} catch (po::error const& e) {
|
} catch (po::error const& e) {
|
||||||
std::cerr << "Error: " << e.what() << std::endl << std::endl;
|
std::cerr << "Error: " << e.what() << std::endl << std::endl;
|
||||||
@@ -87,7 +91,8 @@ CliArgs::parse(int argc, char const* argv[])
|
|||||||
if (parsed.contains("config-description")) {
|
if (parsed.contains("config-description")) {
|
||||||
std::filesystem::path const filePath = parsed["config-description"].as<std::string>();
|
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())
|
if (res.has_value())
|
||||||
return Action{Action::Exit{EXIT_SUCCESS}};
|
return Action{Action::Exit{EXIT_SUCCESS}};
|
||||||
|
|
||||||
@@ -99,15 +104,22 @@ CliArgs::parse(int argc, char const* argv[])
|
|||||||
|
|
||||||
if (parsed.contains("migrate")) {
|
if (parsed.contains("migrate")) {
|
||||||
auto const opt = parsed["migrate"].as<std::string>();
|
auto const opt = parsed["migrate"].as<std::string>();
|
||||||
if (opt == "status")
|
if (opt == "status") {
|
||||||
return Action{Action::Migrate{.configPath = std::move(configPath), .subCmd = MigrateSubCmd::status()}};
|
return Action{Action::Migrate{
|
||||||
return Action{Action::Migrate{.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)}};
|
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::status()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
return Action{Action::Migrate{
|
||||||
|
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.contains("verify"))
|
if (parsed.contains("verify"))
|
||||||
return Action{Action::VerifyConfig{.configPath = std::move(configPath)}};
|
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
|
} // namespace app
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Apply a function to the action.
|
* @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.
|
* @param processors Action processors.
|
||||||
* @return Exit code.
|
* @return Exit code.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
#include "etl/ETLService.hpp"
|
#include "etl/ETLService.hpp"
|
||||||
#include "etl/LoadBalancer.hpp"
|
#include "etl/LoadBalancer.hpp"
|
||||||
#include "etl/NetworkValidatedLedgers.hpp"
|
#include "etl/NetworkValidatedLedgers.hpp"
|
||||||
|
#include "etl/SystemState.hpp"
|
||||||
|
#include "etl/WriterState.hpp"
|
||||||
#include "feed/SubscriptionManager.hpp"
|
#include "feed/SubscriptionManager.hpp"
|
||||||
#include "migration/MigrationInspectorFactory.hpp"
|
#include "migration/MigrationInspectorFactory.hpp"
|
||||||
#include "rpc/Counters.hpp"
|
#include "rpc/Counters.hpp"
|
||||||
@@ -121,7 +123,11 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
// Interface to the database
|
// Interface to the database
|
||||||
auto backend = data::makeBackend(config_, cache);
|
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();
|
clusterCommunicationService.run();
|
||||||
|
|
||||||
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
|
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);
|
auto const migrationInspector = migration::makeMigrationInspector(config_, backend);
|
||||||
// Check if any migration is blocking Clio server starting.
|
// Check if any migration is blocking Clio server starting.
|
||||||
if (migrationInspector->isBlockingClio() and backend->hardFetchLedgerRangeNoThrow()) {
|
if (migrationInspector->isBlockingClio() and backend->hardFetchLedgerRangeNoThrow()) {
|
||||||
LOG(util::LogService::error())
|
LOG(util::LogService::error()) << "Existing Migration is blocking Clio, Please "
|
||||||
<< "Existing Migration is blocking Clio, Please complete the database migration first.";
|
"complete the database migration first.";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manages clients subscribed to streams
|
// 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
|
// Tracks which ledgers have been validated by the network
|
||||||
auto ledgers = etl::NetworkValidatedLedgers::makeValidatedLedgers();
|
auto ledgers = etl::NetworkValidatedLedgers::makeValidatedLedgers();
|
||||||
@@ -150,8 +157,11 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
config_, ioc, backend, subscriptions, std::make_unique<util::MTRandomGenerator>(), ledgers
|
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
|
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only
|
||||||
auto etl = etl::ETLService::makeETLService(config_, ctx, backend, subscriptions, balancer, ledgers);
|
// publishes
|
||||||
|
auto etl = etl::ETLService::makeETLService(
|
||||||
|
config_, std::move(systemState), ctx, backend, subscriptions, balancer, ledgers
|
||||||
|
);
|
||||||
|
|
||||||
auto workQueue = rpc::WorkQueue::makeWorkQueue(config_);
|
auto workQueue = rpc::WorkQueue::makeWorkQueue(config_);
|
||||||
auto counters = rpc::Counters::makeCounters(workQueue);
|
auto counters = rpc::Counters::makeCounters(workQueue);
|
||||||
@@ -161,15 +171,19 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
);
|
);
|
||||||
|
|
||||||
using RPCEngineType = rpc::RPCEngine<rpc::Counters>;
|
using RPCEngineType = rpc::RPCEngine<rpc::Counters>;
|
||||||
auto const rpcEngine =
|
auto const rpcEngine = RPCEngineType::makeRPCEngine(
|
||||||
RPCEngineType::makeRPCEngine(config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
|
config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider
|
||||||
|
);
|
||||||
|
|
||||||
if (useNgWebServer or config_.get<bool>("server.__ng_web_server")) {
|
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_);
|
auto expectedAdminVerifier = web::makeAdminVerificationStrategy(config_);
|
||||||
if (not expectedAdminVerifier.has_value()) {
|
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;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
auto const adminVerifier = std::move(expectedAdminVerifier).value();
|
auto const adminVerifier = std::move(expectedAdminVerifier).value();
|
||||||
@@ -197,7 +211,16 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
appStopper_.setOnStop(
|
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.
|
// Blocks until stopped.
|
||||||
@@ -209,11 +232,22 @@ ClioApplication::run(bool const useNgWebServer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init the web server
|
// 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);
|
auto const httpServer = web::makeHttpServer(config_, ioc, dosGuard, handler, cache);
|
||||||
appStopper_.setOnStop(
|
appStopper_.setOnStop(
|
||||||
Stopper::makeOnStopCallback(*httpServer, *balancer, *etl, *subscriptions, *backend, cacheSaver, ioc)
|
Stopper::makeOnStopCallback(
|
||||||
|
*httpServer,
|
||||||
|
*balancer,
|
||||||
|
*etl,
|
||||||
|
*subscriptions,
|
||||||
|
*backend,
|
||||||
|
cacheSaver,
|
||||||
|
clusterCommunicationService,
|
||||||
|
ioc
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Blocks until stopped.
|
// Blocks until stopped.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "cluster/Concepts.hpp"
|
||||||
#include "data/BackendInterface.hpp"
|
#include "data/BackendInterface.hpp"
|
||||||
#include "data/LedgerCacheSaver.hpp"
|
#include "data/LedgerCacheSaver.hpp"
|
||||||
#include "etl/ETLServiceInterface.hpp"
|
#include "etl/ETLServiceInterface.hpp"
|
||||||
@@ -38,7 +39,8 @@
|
|||||||
namespace app {
|
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 {
|
class Stopper {
|
||||||
boost::asio::io_context ctx_;
|
boost::asio::io_context ctx_;
|
||||||
@@ -82,10 +84,14 @@ public:
|
|||||||
* @param subscriptions The subscription manager to stop.
|
* @param subscriptions The subscription manager to stop.
|
||||||
* @param backend The backend to stop.
|
* @param backend The backend to stop.
|
||||||
* @param cacheSaver The ledger cache saver
|
* @param cacheSaver The ledger cache saver
|
||||||
|
* @param clusterCommunicationService The cluster communication service to stop.
|
||||||
* @param ioc The io_context to stop.
|
* @param ioc The io_context to stop.
|
||||||
* @return The callback to be called on application stop.
|
* @return The callback to be called on application stop.
|
||||||
*/
|
*/
|
||||||
template <web::SomeServer ServerType, data::SomeLedgerCacheSaver LedgerCacheSaverType>
|
template <
|
||||||
|
web::SomeServer ServerType,
|
||||||
|
data::SomeLedgerCacheSaver LedgerCacheSaverType,
|
||||||
|
cluster::SomeClusterCommunicationService ClusterCommunicationServiceType>
|
||||||
static std::function<void(boost::asio::yield_context)>
|
static std::function<void(boost::asio::yield_context)>
|
||||||
makeOnStopCallback(
|
makeOnStopCallback(
|
||||||
ServerType& server,
|
ServerType& server,
|
||||||
@@ -94,6 +100,7 @@ public:
|
|||||||
feed::SubscriptionManagerInterface& subscriptions,
|
feed::SubscriptionManagerInterface& subscriptions,
|
||||||
data::BackendInterface& backend,
|
data::BackendInterface& backend,
|
||||||
LedgerCacheSaverType& cacheSaver,
|
LedgerCacheSaverType& cacheSaver,
|
||||||
|
ClusterCommunicationServiceType& clusterCommunicationService,
|
||||||
boost::asio::io_context& ioc
|
boost::asio::io_context& ioc
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -111,6 +118,8 @@ public:
|
|||||||
});
|
});
|
||||||
coroutineGroup.asyncWait(yield);
|
coroutineGroup.asyncWait(yield);
|
||||||
|
|
||||||
|
clusterCommunicationService.stop();
|
||||||
|
|
||||||
etl.stop();
|
etl.stop();
|
||||||
LOG(util::LogService::info()) << "ETL stopped";
|
LOG(util::LogService::info()) << "ETL stopped";
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ parseConfig(std::string_view configPath)
|
|||||||
|
|
||||||
auto const json = ConfigFileJson::makeConfigFileJson(configPath);
|
auto const json = ConfigFileJson::makeConfigFileJson(configPath);
|
||||||
if (!json.has_value()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
auto const errors = getClioConfig().parse(json.value());
|
auto const errors = getClioConfig().parse(json.value());
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ OnConnectCheck::operator()(web::ng::Connection const& connection)
|
|||||||
{
|
{
|
||||||
dosguard_.get().increment(connection.ip());
|
dosguard_.get().increment(connection.ip());
|
||||||
if (not dosguard_.get().isOk(connection.ip())) {
|
if (not dosguard_.get().isOk(connection.ip())) {
|
||||||
return std::unexpected{
|
return std::unexpected{web::ng::Response{
|
||||||
web::ng::Response{boost::beast::http::status::too_many_requests, "Too many requests", connection}
|
boost::beast::http::status::too_many_requests, "Too many requests", connection
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -80,7 +80,10 @@ DisconnectHook::operator()(web::ng::Connection const& connection)
|
|||||||
dosguard_.get().decrement(connection.ip());
|
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)}
|
: adminVerifier_{std::move(adminVerifier)}, workQueue_{std::ref(workQueue)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -120,7 +123,9 @@ MetricsHandler::operator()(
|
|||||||
|
|
||||||
if (!postSuccessful) {
|
if (!postSuccessful) {
|
||||||
return web::ng::Response{
|
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())
|
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::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
|
} // 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).
|
* @brief A function object that is called when the IP of a connection changes (usually if proxy
|
||||||
* This is used to update the DOS guard.
|
* detected). This is used to update the DOS guard.
|
||||||
*/
|
*/
|
||||||
class IpChangeHook {
|
class IpChangeHook {
|
||||||
std::reference_wrapper<web::dosguard::DOSGuardInterface> dosguard_;
|
std::reference_wrapper<web::dosguard::DOSGuardInterface> dosguard_;
|
||||||
@@ -126,10 +126,14 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Construct a new MetricsHandler object
|
* @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.
|
* @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.
|
* @brief The call of the function object.
|
||||||
@@ -214,10 +218,14 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Construct a new RequestHandler object
|
* @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.
|
* @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)
|
: 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)
|
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)
|
target_link_libraries(clio_cluster PRIVATE clio_util clio_data)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "cluster/ClioNode.hpp"
|
#include "cluster/ClioNode.hpp"
|
||||||
|
|
||||||
|
#include "etl/WriterState.hpp"
|
||||||
#include "util/TimeUtils.hpp"
|
#include "util/TimeUtils.hpp"
|
||||||
|
|
||||||
#include <boost/json/conversion.hpp>
|
#include <boost/json/conversion.hpp>
|
||||||
@@ -26,39 +27,76 @@
|
|||||||
#include <boost/json/value.hpp>
|
#include <boost/json/value.hpp>
|
||||||
#include <boost/uuid/uuid.hpp>
|
#include <boost/uuid/uuid.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace cluster {
|
namespace cluster {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Fields {
|
struct JsonFields {
|
||||||
static constexpr std::string_view const kUPDATE_TIME = "update_time";
|
static constexpr std::string_view const kUPDATE_TIME = "update_time";
|
||||||
|
static constexpr std::string_view const kDB_ROLE = "db_role";
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // 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
|
void
|
||||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ClioNode const& node)
|
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ClioNode const& node)
|
||||||
{
|
{
|
||||||
jv = {
|
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
|
ClioNode
|
||||||
tag_invoke(boost::json::value_to_tag<ClioNode>, boost::json::value const& jv)
|
tag_invoke(boost::json::value_to_tag<ClioNode>, boost::json::value const& jv)
|
||||||
{
|
{
|
||||||
auto const& updateTimeStr = jv.as_object().at(Fields::kUPDATE_TIME).as_string();
|
auto const& updateTimeStr = jv.as_object().at(JsonFields::kUPDATE_TIME).as_string();
|
||||||
auto const updateTime = util::systemTpFromUtcStr(std::string(updateTimeStr), ClioNode::kTIME_FORMAT);
|
auto const updateTime =
|
||||||
|
util::systemTpFromUtcStr(std::string(updateTimeStr), ClioNode::kTIME_FORMAT);
|
||||||
if (!updateTime.has_value()) {
|
if (!updateTime.has_value()) {
|
||||||
throw std::runtime_error("Failed to parse update time");
|
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
|
} // namespace cluster
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "etl/WriterState.hpp"
|
||||||
|
|
||||||
#include <boost/json/conversion.hpp>
|
#include <boost/json/conversion.hpp>
|
||||||
#include <boost/json/value.hpp>
|
#include <boost/json/value.hpp>
|
||||||
#include <boost/uuid/uuid.hpp>
|
#include <boost/uuid/uuid.hpp>
|
||||||
@@ -37,16 +39,45 @@ struct ClioNode {
|
|||||||
*/
|
*/
|
||||||
static constexpr char const* kTIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ";
|
static constexpr char const* kTIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ";
|
||||||
|
|
||||||
// enum class WriterRole {
|
/**
|
||||||
// ReadOnly,
|
* @brief Database role of a node in the cluster.
|
||||||
// NotWriter,
|
*
|
||||||
// Writer
|
* Roles are used to coordinate which node writes to the database:
|
||||||
// };
|
* - ReadOnly: Node is configured to never write (strict read-only mode)
|
||||||
|
* - NotWriter: Node can write but is currently not the designated writer
|
||||||
|
* - Writer: Node is actively writing to the database
|
||||||
|
* - Fallback: Node is using the fallback writer decision mechanism
|
||||||
|
*
|
||||||
|
* When any node in the cluster is in Fallback mode, the entire cluster switches
|
||||||
|
* from the cluster communication mechanism to the slower but more reliable
|
||||||
|
* database-based conflict detection mechanism.
|
||||||
|
*/
|
||||||
|
enum class DbRole {
|
||||||
|
ReadOnly = 0,
|
||||||
|
LoadingCache = 1,
|
||||||
|
NotWriter = 2,
|
||||||
|
Writer = 3,
|
||||||
|
Fallback = 4,
|
||||||
|
MAX = 4
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<boost::uuids::uuid> uuid; ///< The UUID of the node.
|
using Uuid = std::shared_ptr<boost::uuids::uuid>;
|
||||||
std::chrono::system_clock::time_point updateTime; ///< The time the data about the node was last updated.
|
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
|
void
|
||||||
|
|||||||
@@ -19,98 +19,37 @@
|
|||||||
|
|
||||||
#include "cluster/ClusterCommunicationService.hpp"
|
#include "cluster/ClusterCommunicationService.hpp"
|
||||||
|
|
||||||
#include "cluster/ClioNode.hpp"
|
|
||||||
#include "data/BackendInterface.hpp"
|
#include "data/BackendInterface.hpp"
|
||||||
#include "util/Assert.hpp"
|
#include "etl/WriterState.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 <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <latch>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr auto kTOTAL_WORKERS = 2uz; // 1 reading and 1 writing worker (coroutines)
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace cluster {
|
namespace cluster {
|
||||||
|
|
||||||
ClusterCommunicationService::ClusterCommunicationService(
|
ClusterCommunicationService::ClusterCommunicationService(
|
||||||
std::shared_ptr<data::BackendInterface> backend,
|
std::shared_ptr<data::BackendInterface> backend,
|
||||||
|
std::unique_ptr<etl::WriterStateInterface> writerState,
|
||||||
std::chrono::steady_clock::duration readInterval,
|
std::chrono::steady_clock::duration readInterval,
|
||||||
std::chrono::steady_clock::duration writeInterval
|
std::chrono::steady_clock::duration writeInterval
|
||||||
)
|
)
|
||||||
: backend_(std::move(backend))
|
: backend_(ctx_, std::move(backend), writerState->clone(), readInterval, writeInterval)
|
||||||
, readInterval_(readInterval)
|
, writerDecider_(ctx_, std::move(writerState))
|
||||||
, 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{}
|
|
||||||
}}
|
|
||||||
{
|
{
|
||||||
nodesInClusterMetric_.set(1); // The node always sees itself
|
|
||||||
isHealthy_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ClusterCommunicationService::run()
|
ClusterCommunicationService::run()
|
||||||
{
|
{
|
||||||
ASSERT(not running_ and not stopped_, "Can only be ran once");
|
backend_.subscribeToNewState([this](auto&&... args) {
|
||||||
running_ = true;
|
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_) {
|
|
||||||
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) {
|
||||||
util::spawn(strand_, [this](boost::asio::yield_context yield) {
|
writerDecider_.onNewState(std::forward<decltype(args)>(args)...);
|
||||||
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_.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
ClusterCommunicationService::~ClusterCommunicationService()
|
ClusterCommunicationService::~ClusterCommunicationService()
|
||||||
@@ -121,107 +60,7 @@ ClusterCommunicationService::~ClusterCommunicationService()
|
|||||||
void
|
void
|
||||||
ClusterCommunicationService::stop()
|
ClusterCommunicationService::stop()
|
||||||
{
|
{
|
||||||
if (stopped_)
|
backend_.stop();
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cluster
|
} // namespace cluster
|
||||||
|
|||||||
@@ -19,13 +19,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "cluster/ClioNode.hpp"
|
#include "cluster/Backend.hpp"
|
||||||
#include "cluster/ClusterCommunicationServiceInterface.hpp"
|
#include "cluster/Concepts.hpp"
|
||||||
|
#include "cluster/Metrics.hpp"
|
||||||
|
#include "cluster/WriterDecider.hpp"
|
||||||
#include "data/BackendInterface.hpp"
|
#include "data/BackendInterface.hpp"
|
||||||
#include "util/log/Logger.hpp"
|
#include "etl/WriterState.hpp"
|
||||||
#include "util/prometheus/Bool.hpp"
|
|
||||||
#include "util/prometheus/Gauge.hpp"
|
|
||||||
#include "util/prometheus/Prometheus.hpp"
|
|
||||||
|
|
||||||
#include <boost/asio/cancellation_signal.hpp>
|
#include <boost/asio/cancellation_signal.hpp>
|
||||||
#include <boost/asio/spawn.hpp>
|
#include <boost/asio/spawn.hpp>
|
||||||
@@ -33,67 +32,51 @@
|
|||||||
#include <boost/asio/thread_pool.hpp>
|
#include <boost/asio/thread_pool.hpp>
|
||||||
#include <boost/uuid/uuid.hpp>
|
#include <boost/uuid/uuid.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <latch>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace cluster {
|
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 {
|
class ClusterCommunicationService : public ClusterCommunicationServiceTag {
|
||||||
util::prometheus::GaugeInt& nodesInClusterMetric_ = PrometheusService::gaugeInt(
|
// TODO: Use util::async::CoroExecutionContext after https://github.com/XRPLF/clio/issues/1973
|
||||||
"cluster_nodes_total_number",
|
// is implemented
|
||||||
{},
|
|
||||||
"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
|
|
||||||
boost::asio::thread_pool ctx_{1};
|
boost::asio::thread_pool ctx_{1};
|
||||||
boost::asio::strand<boost::asio::thread_pool::executor_type> strand_ = boost::asio::make_strand(ctx_);
|
Backend backend_;
|
||||||
|
Metrics metrics_;
|
||||||
util::Logger log_{"ClusterCommunication"};
|
WriterDecider writerDecider_;
|
||||||
|
|
||||||
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_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr std::chrono::milliseconds kDEFAULT_READ_INTERVAL{2100};
|
static constexpr std::chrono::milliseconds kDEFAULT_READ_INTERVAL{1000};
|
||||||
static constexpr std::chrono::milliseconds kDEFAULT_WRITE_INTERVAL{1200};
|
static constexpr std::chrono::milliseconds kDEFAULT_WRITE_INTERVAL{1000};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new Cluster Communication Service object.
|
* @brief Construct a new Cluster Communication Service object.
|
||||||
*
|
*
|
||||||
* @param backend The backend to use for communication.
|
* @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 readInterval The interval to read messages from the cluster.
|
||||||
* @param writeInterval The interval to write messages to the cluster.
|
* @param writeInterval The interval to write messages to the cluster.
|
||||||
*/
|
*/
|
||||||
ClusterCommunicationService(
|
ClusterCommunicationService(
|
||||||
std::shared_ptr<data::BackendInterface> backend,
|
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 readInterval = kDEFAULT_READ_INTERVAL,
|
||||||
std::chrono::steady_clock::duration writeInterval = kDEFAULT_WRITE_INTERVAL
|
std::chrono::steady_clock::duration writeInterval = kDEFAULT_WRITE_INTERVAL
|
||||||
);
|
);
|
||||||
|
|
||||||
~ClusterCommunicationService() override;
|
~ClusterCommunicationService() override;
|
||||||
|
|
||||||
|
ClusterCommunicationService(ClusterCommunicationService&&) = delete;
|
||||||
|
ClusterCommunicationService(ClusterCommunicationService const&) = delete;
|
||||||
|
ClusterCommunicationService&
|
||||||
|
operator=(ClusterCommunicationService&&) = delete;
|
||||||
|
ClusterCommunicationService&
|
||||||
|
operator=(ClusterCommunicationService const&) = delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start the service.
|
* @brief Start the service.
|
||||||
*/
|
*/
|
||||||
@@ -105,44 +88,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
stop();
|
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
|
} // 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 "cluster/ClioNode.hpp"
|
||||||
|
|
||||||
#include <expected>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace cluster {
|
namespace cluster {
|
||||||
|
|
||||||
/**
|
Metrics::Metrics()
|
||||||
* @brief Interface for the cluster communication service.
|
{
|
||||||
*/
|
nodesInClusterMetric_.set(1); // The node always sees itself
|
||||||
class ClusterCommunicationServiceInterface {
|
isHealthy_ = true;
|
||||||
public:
|
}
|
||||||
virtual ~ClusterCommunicationServiceInterface() = default;
|
|
||||||
|
|
||||||
/**
|
void
|
||||||
* @brief Get the data of the current node.
|
Metrics::onNewState(ClioNode::CUuid, std::shared_ptr<Backend::ClusterData const> clusterData)
|
||||||
*
|
{
|
||||||
* @return The data of the current node.
|
if (clusterData->has_value()) {
|
||||||
*/
|
isHealthy_ = true;
|
||||||
[[nodiscard]] virtual ClioNode
|
nodesInClusterMetric_.set(clusterData->value().size());
|
||||||
selfData() const = 0;
|
} else {
|
||||||
|
isHealthy_ = false;
|
||||||
/**
|
nodesInClusterMetric_.set(1);
|
||||||
* @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;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace cluster
|
} // 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
|
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;
|
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 rg::find(ledgerAmendments, am->feature) != rg::end(ledgerAmendments);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -70,9 +75,12 @@ lookupAmendment(auto const& allAmendments, std::vector<ripple::uint256> const& l
|
|||||||
namespace data {
|
namespace data {
|
||||||
namespace impl {
|
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);
|
supportedAmendments().insert(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +104,8 @@ operator ripple::uint256() const
|
|||||||
return Amendment::getAmendmentId(name);
|
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 rg = std::ranges;
|
||||||
namespace vs = std::views;
|
namespace vs = std::views;
|
||||||
@@ -108,7 +117,8 @@ AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const&
|
|||||||
.name = name,
|
.name = name,
|
||||||
.feature = Amendment::getAmendmentId(name),
|
.feature = Amendment::getAmendmentId(name),
|
||||||
.isSupportedByXRPL = support != ripple::AmendmentSupport::Unsupported,
|
.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
|
.isRetired = support == ripple::AmendmentSupport::Retired
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -144,19 +154,28 @@ AmendmentCenter::isEnabled(AmendmentKey const& key, uint32_t seq) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
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
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments)
|
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments)
|
||||||
return lookupAmendment(all_, *listAmendments, key);
|
return lookupAmendment(all_, *listAmendments, key);
|
||||||
} catch (std::runtime_error const&) {
|
} 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; // Some old ledger does not contain Amendments ledger object so do best we
|
||||||
|
// can for now
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<bool>
|
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;
|
namespace rg = std::ranges;
|
||||||
|
|
||||||
@@ -181,7 +200,11 @@ AmendmentCenter::isEnabled(boost::asio::yield_context yield, std::vector<Amendme
|
|||||||
Amendment const&
|
Amendment const&
|
||||||
AmendmentCenter::getAmendment(AmendmentKey const& key) 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);
|
return supported_.at(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +224,8 @@ std::optional<std::vector<ripple::uint256>>
|
|||||||
AmendmentCenter::fetchAmendmentsList(boost::asio::yield_context yield, uint32_t seq) const
|
AmendmentCenter::fetchAmendmentsList(boost::asio::yield_context yield, uint32_t seq) const
|
||||||
{
|
{
|
||||||
// the amendments should always be present on the ledger
|
// 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())
|
if (not amendments.has_value())
|
||||||
throw std::runtime_error("Amendments ledger object must be present in the database");
|
throw std::runtime_error("Amendments ledger object must be present in the database");
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ struct WritingAmendmentKey : AmendmentKey {
|
|||||||
*/
|
*/
|
||||||
struct Amendments {
|
struct Amendments {
|
||||||
// NOTE: if Clio wants to report it supports an Amendment it should be listed here.
|
// 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.
|
// Whether an amendment is obsolete and/or supported by libxrpl is extracted directly from
|
||||||
// If an amendment is in the list below it just means Clio did whatever changes needed to support it.
|
// libxrpl. If an amendment is in the list below it just means Clio did whatever changes needed
|
||||||
// Most of the time it's going to be no changes at all.
|
// to support it. Most of the time it's going to be no changes at all.
|
||||||
|
|
||||||
/** @cond */
|
/** @cond */
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
// NOLINTBEGIN(readability-identifier-naming)
|
||||||
@@ -177,6 +177,7 @@ struct Amendments {
|
|||||||
REGISTER(fix1512);
|
REGISTER(fix1512);
|
||||||
REGISTER(fix1523);
|
REGISTER(fix1523);
|
||||||
REGISTER(fix1528);
|
REGISTER(fix1528);
|
||||||
|
REGISTER(fixBatchInnerSigs);
|
||||||
// NOLINTEND(readability-identifier-naming)
|
// NOLINTEND(readability-identifier-naming)
|
||||||
/** @endcond */
|
/** @endcond */
|
||||||
};
|
};
|
||||||
@@ -255,7 +256,11 @@ public:
|
|||||||
* @return A vector of bools representing enabled state for each of the given keys
|
* @return A vector of bools representing enabled state for each of the given keys
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::vector<bool>
|
[[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
|
* @brief Get an amendment
|
||||||
|
|||||||
@@ -92,7 +92,11 @@ public:
|
|||||||
* @return A vector of bools representing enabled state for each of the given keys
|
* @return A vector of bools representing enabled state for each of the given keys
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] virtual std::vector<bool>
|
[[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
|
* @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
|
std::int64_t
|
||||||
durationInMillisecondsSince(std::chrono::steady_clock::time_point const startTime)
|
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
|
} // namespace
|
||||||
@@ -144,7 +147,10 @@ BackendCounters::registerReadStarted(std::uint64_t const count)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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);
|
asyncReadCounters_.registerFinished(count);
|
||||||
auto const duration = durationInMillisecondsSince(startTime);
|
auto const duration = durationInMillisecondsSince(startTime);
|
||||||
@@ -238,7 +244,8 @@ void
|
|||||||
BackendCounters::AsyncOperationCounters::registerError(std::uint64_t count)
|
BackendCounters::AsyncOperationCounters::registerError(std::uint64_t count)
|
||||||
{
|
{
|
||||||
ASSERT(
|
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;
|
pendingCounter_.get() -= count;
|
||||||
errorCounter_.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.registerWriteFinished(std::chrono::steady_clock::time_point{}) } -> std::same_as<void>;
|
||||||
{ a.registerWriteRetry() } -> std::same_as<void>;
|
{ a.registerWriteRetry() } -> std::same_as<void>;
|
||||||
{ a.registerReadStarted(std::uint64_t{}) } -> 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.registerReadRetry(std::uint64_t{}) } -> std::same_as<void>;
|
||||||
{ a.registerReadError(std::uint64_t{}) } -> std::same_as<void>;
|
{ a.registerReadError(std::uint64_t{}) } -> std::same_as<void>;
|
||||||
{ a.report() } -> std::same_as<boost::json::object>;
|
{ a.report() } -> std::same_as<boost::json::object>;
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ BackendInterface::fetchLedgerObjects(
|
|||||||
misses.push_back(keys[i]);
|
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()) {
|
if (!misses.empty()) {
|
||||||
auto objs = doFetchLedgerObjects(misses, sequence, yield);
|
auto objs = doFetchLedgerObjects(misses, sequence, yield);
|
||||||
@@ -192,7 +193,9 @@ BackendInterface::fetchBookOffers(
|
|||||||
ripple::uint256 const bookEnd = ripple::getQualityNext(book);
|
ripple::uint256 const bookEnd = ripple::getQualityNext(book);
|
||||||
ripple::uint256 uTipIndex = book;
|
ripple::uint256 uTipIndex = book;
|
||||||
std::vector<ripple::uint256> keys;
|
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();
|
auto begin = std::chrono::system_clock::now();
|
||||||
std::uint32_t numSucc = 0;
|
std::uint32_t numSucc = 0;
|
||||||
std::uint32_t numPages = 0;
|
std::uint32_t numPages = 0;
|
||||||
@@ -233,20 +236,23 @@ BackendInterface::fetchBookOffers(
|
|||||||
auto mid = std::chrono::system_clock::now();
|
auto mid = std::chrono::system_clock::now();
|
||||||
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
||||||
for (size_t i = 0; i < keys.size() && i < limit; ++i) {
|
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;
|
<< " ledgerSequence = " << ledgerSequence;
|
||||||
ASSERT(!objs[i].empty(), "Ledger object can't be empty");
|
ASSERT(!objs[i].empty(), "Ledger object can't be empty");
|
||||||
page.offers.push_back({keys[i], objs[i]});
|
page.offers.push_back({keys[i], objs[i]});
|
||||||
}
|
}
|
||||||
auto end = std::chrono::system_clock::now();
|
auto end = std::chrono::system_clock::now();
|
||||||
LOG(log_.debug()) << "Fetching " << std::to_string(keys.size()) << " offers took "
|
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(getMillis(mid - begin))
|
||||||
<< std::to_string(succMillis) << " milliseconds. Fetched next dir " << std::to_string(numSucc)
|
<< " milliseconds. Fetching next dir took " << std::to_string(succMillis)
|
||||||
<< " times"
|
<< " milliseconds. Fetched next dir " << std::to_string(numSucc) << " times"
|
||||||
<< " Fetching next page of dir took " << std::to_string(pageMillis) << " milliseconds"
|
<< " Fetching next page of dir took " << std::to_string(pageMillis)
|
||||||
<< ". num pages = " << std::to_string(numPages) << ". Fetching all objects took "
|
<< " milliseconds"
|
||||||
<< std::to_string(getMillis(end - mid))
|
<< ". num pages = " << std::to_string(numPages)
|
||||||
<< " milliseconds. total time = " << std::to_string(getMillis(end - begin)) << " milliseconds"
|
<< ". Fetching all objects took " << std::to_string(getMillis(end - mid))
|
||||||
|
<< " milliseconds. total time = " << std::to_string(getMillis(end - begin))
|
||||||
|
<< " milliseconds"
|
||||||
<< " book = " << ripple::strHex(book);
|
<< " book = " << ripple::strHex(book);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
@@ -273,7 +279,8 @@ BackendInterface::updateRange(uint32_t newMax)
|
|||||||
if (range_.has_value() and newMax < range_->maxSequence) {
|
if (range_.has_value() and newMax < range_->maxSequence) {
|
||||||
ASSERT(
|
ASSERT(
|
||||||
false,
|
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 = {}",
|
"range->maxSequence = {}",
|
||||||
newMax,
|
newMax,
|
||||||
range_->maxSequence
|
range_->maxSequence
|
||||||
@@ -339,8 +346,8 @@ BackendInterface::fetchLedgerPage(
|
|||||||
if (!objects[i].empty()) {
|
if (!objects[i].empty()) {
|
||||||
page.objects.push_back({keys[i], std::move(objects[i])});
|
page.objects.push_back({keys[i], std::move(objects[i])});
|
||||||
} else if (!outOfOrder) {
|
} else if (!outOfOrder) {
|
||||||
LOG(log_.error()) << "Deleted or non-existent object in successor table. key = " << ripple::strHex(keys[i])
|
LOG(log_.error()) << "Deleted or non-existent object in successor table. key = "
|
||||||
<< " - seq = " << ledgerSequence;
|
<< ripple::strHex(keys[i]) << " - seq = " << ledgerSequence;
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
for (size_t j = 0; j < objects.size(); ++j) {
|
for (size_t j = 0; j < objects.size(); ++j) {
|
||||||
msg << " - " << ripple::strHex(keys[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;
|
using R = typename boost::result_of<FnType(boost::asio::yield_context)>::type;
|
||||||
if constexpr (!std::is_same_v<R, void>) {
|
if constexpr (!std::is_same_v<R, void>) {
|
||||||
R res;
|
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();
|
ctx.run();
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} 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();
|
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
|
* @tparam FnType The type of function object to execute
|
||||||
* @param func The function object to execute
|
* @param func The function object to execute
|
||||||
@@ -225,7 +230,8 @@ public:
|
|||||||
fetchLedgerRange() const;
|
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 number The number of accounts to fetch
|
||||||
* @param pageSize The maximum number of accounts per page
|
* @param pageSize The maximum number of accounts per page
|
||||||
@@ -296,7 +302,10 @@ public:
|
|||||||
* @return A vector of TransactionAndMetadata matching the given hashes
|
* @return A vector of TransactionAndMetadata matching the given hashes
|
||||||
*/
|
*/
|
||||||
virtual std::vector<TransactionAndMetadata>
|
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.
|
* @brief Fetches all transactions for a specific account.
|
||||||
@@ -325,7 +334,10 @@ public:
|
|||||||
* @return Results as a vector of TransactionAndMetadata
|
* @return Results as a vector of TransactionAndMetadata
|
||||||
*/
|
*/
|
||||||
virtual std::vector<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.
|
* @brief Fetches all transaction hashes from a specific ledger.
|
||||||
@@ -335,7 +347,10 @@ public:
|
|||||||
* @return Hashes as ripple::uint256 in a vector
|
* @return Hashes as ripple::uint256 in a vector
|
||||||
*/
|
*/
|
||||||
virtual std::vector<ripple::uint256>
|
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.
|
* @brief Fetches a specific NFT.
|
||||||
@@ -346,7 +361,11 @@ public:
|
|||||||
* @return NFT object on success; nullopt otherwise
|
* @return NFT object on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
virtual std::optional<NFT>
|
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.
|
* @brief Fetches all transactions for a specific NFT.
|
||||||
@@ -376,7 +395,8 @@ public:
|
|||||||
* @param limit Paging limit.
|
* @param limit Paging limit.
|
||||||
* @param cursorIn Optional cursor to allow us to pick up from where we last left off.
|
* @param cursorIn Optional cursor to allow us to pick up from where we last left off.
|
||||||
* @param yield Currently executing coroutine.
|
* @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
|
virtual NFTsAndCursor
|
||||||
fetchNFTsByIssuer(
|
fetchNFTsByIssuer(
|
||||||
@@ -410,8 +430,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Fetches a specific ledger object.
|
* @brief Fetches a specific ledger object.
|
||||||
*
|
*
|
||||||
* Currently the real fetch happens in doFetchLedgerObject and fetchLedgerObject attempts to fetch from Cache first
|
* Currently the real fetch happens in doFetchLedgerObject and fetchLedgerObject attempts to
|
||||||
* and only calls out to the real DB if a cache miss occurred.
|
* 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 key The key of the object
|
||||||
* @param sequence The ledger sequence to fetch for
|
* @param sequence The ledger sequence to fetch for
|
||||||
@@ -419,7 +439,11 @@ public:
|
|||||||
* @return The object as a Blob on success; nullopt otherwise
|
* @return The object as a Blob on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
std::optional<Blob>
|
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.
|
* @brief Fetches a specific ledger object sequence.
|
||||||
@@ -432,13 +456,18 @@ public:
|
|||||||
* @return The sequence in unit32_t on success; nullopt otherwise
|
* @return The sequence in unit32_t on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
std::optional<std::uint32_t>
|
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.
|
* @brief Fetches all ledger objects by their keys.
|
||||||
*
|
*
|
||||||
* Currently the real fetch happens in doFetchLedgerObjects and fetchLedgerObjects attempts to fetch from Cache
|
* Currently the real fetch happens in doFetchLedgerObjects and fetchLedgerObjects attempts to
|
||||||
* first and only calls out to the real DB for each of the keys that was not found in the cache.
|
* 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 keys A vector with the keys of the objects to fetch
|
||||||
* @param sequence The ledger sequence to fetch for
|
* @param sequence The ledger sequence to fetch for
|
||||||
@@ -461,7 +490,11 @@ public:
|
|||||||
* @return The object as a Blob on success; nullopt otherwise
|
* @return The object as a Blob on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
virtual std::optional<Blob>
|
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.
|
* @brief The database-specific implementation for fetching a ledger object sequence.
|
||||||
@@ -531,13 +564,18 @@ public:
|
|||||||
* @return The successor on success; nullopt otherwise
|
* @return The successor on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
std::optional<LedgerObject>
|
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.
|
* @brief Fetches the successor key.
|
||||||
*
|
*
|
||||||
* Thea real fetch happens in doFetchSuccessorKey. This function will attempt to lookup the successor in the cache
|
* Thea real fetch happens in doFetchSuccessorKey. This function will attempt to lookup the
|
||||||
* first and only if it's not found in the cache will it fetch from the actual DB.
|
* 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 key The key to fetch for
|
||||||
* @param ledgerSequence The ledger sequence to fetch for
|
* @param ledgerSequence The ledger sequence to fetch for
|
||||||
@@ -545,7 +583,11 @@ public:
|
|||||||
* @return The successor key on success; nullopt otherwise
|
* @return The successor key on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
std::optional<ripple::uint256>
|
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
|
* @brief Database-specific implementation of fetching the successor key
|
||||||
@@ -556,7 +598,11 @@ public:
|
|||||||
* @return The successor on success; nullopt otherwise
|
* @return The successor on success; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
virtual std::optional<ripple::uint256>
|
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.
|
* @brief Fetches book offers.
|
||||||
@@ -583,7 +629,10 @@ public:
|
|||||||
* @return The status of the migrator if found; nullopt otherwise
|
* @return The status of the migrator if found; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
virtual std::optional<std::string>
|
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 */
|
/** @brief Return type for fetchClioNodesData() method */
|
||||||
using ClioNodesDataFetchResult =
|
using ClioNodesDataFetchResult =
|
||||||
@@ -601,7 +650,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Synchronously fetches the ledger range from DB.
|
* @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
|
* @return The ledger range if available; nullopt otherwise
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
add_library(clio_data)
|
add_library(clio_data)
|
||||||
target_sources(
|
target_sources(clio_data
|
||||||
clio_data
|
PRIVATE AmendmentCenter.cpp
|
||||||
PRIVATE AmendmentCenter.cpp
|
BackendCounters.cpp
|
||||||
BackendCounters.cpp
|
BackendInterface.cpp
|
||||||
BackendInterface.cpp
|
LedgerCache.cpp
|
||||||
LedgerCache.cpp
|
LedgerCacheSaver.cpp
|
||||||
LedgerCacheSaver.cpp
|
LedgerHeaderCache.cpp
|
||||||
LedgerHeaderCache.cpp
|
cassandra/impl/Future.cpp
|
||||||
cassandra/impl/Future.cpp
|
cassandra/impl/Cluster.cpp
|
||||||
cassandra/impl/Cluster.cpp
|
cassandra/impl/Batch.cpp
|
||||||
cassandra/impl/Batch.cpp
|
cassandra/impl/Result.cpp
|
||||||
cassandra/impl/Result.cpp
|
cassandra/impl/Tuple.cpp
|
||||||
cassandra/impl/Tuple.cpp
|
cassandra/impl/SslContext.cpp
|
||||||
cassandra/impl/SslContext.cpp
|
cassandra/Handle.cpp
|
||||||
cassandra/Handle.cpp
|
cassandra/SettingsProvider.cpp
|
||||||
cassandra/SettingsProvider.cpp
|
impl/InputFile.cpp
|
||||||
impl/InputFile.cpp
|
impl/LedgerCacheFile.cpp
|
||||||
impl/LedgerCacheFile.cpp
|
impl/OutputFile.cpp)
|
||||||
impl/OutputFile.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(clio_data PUBLIC cassandra-cpp-driver::cassandra-cpp-driver clio_util)
|
target_link_libraries(clio_data PUBLIC cassandra-cpp-driver::cassandra-cpp-driver clio_util)
|
||||||
|
|||||||
@@ -102,10 +102,14 @@ public:
|
|||||||
this->waitForWritesToFinish();
|
this->waitForWritesToFinish();
|
||||||
|
|
||||||
if (!range_) {
|
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_;
|
LOG(log_.warn()) << "Update failed for ledger " << ledgerSequence_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -139,7 +143,8 @@ public:
|
|||||||
r.bindAt(
|
r.bindAt(
|
||||||
1,
|
1,
|
||||||
std::make_tuple(
|
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))
|
cursorIn.value_or(ripple::uint256(0))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -170,9 +175,10 @@ public:
|
|||||||
selectNFTStatements.reserve(nftIDs.size());
|
selectNFTStatements.reserve(nftIDs.size());
|
||||||
|
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTStatements), [&](auto const& nftID) {
|
std::cbegin(nftIDs),
|
||||||
return schema_->selectNFT.bind(nftID, ledgerSequence);
|
std::cend(nftIDs),
|
||||||
}
|
std::back_inserter(selectNFTStatements),
|
||||||
|
[&](auto const& nftID) { return schema_->selectNFT.bind(nftID, ledgerSequence); }
|
||||||
);
|
);
|
||||||
|
|
||||||
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
||||||
@@ -181,9 +187,10 @@ public:
|
|||||||
selectNFTURIStatements.reserve(nftIDs.size());
|
selectNFTURIStatements.reserve(nftIDs.size());
|
||||||
|
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTURIStatements), [&](auto const& nftID) {
|
std::cbegin(nftIDs),
|
||||||
return schema_->selectNFTURI.bind(nftID, ledgerSequence);
|
std::cend(nftIDs),
|
||||||
}
|
std::back_inserter(selectNFTURIStatements),
|
||||||
|
[&](auto const& nftID) { return schema_->selectNFTURI.bind(nftID, ledgerSequence); }
|
||||||
);
|
);
|
||||||
|
|
||||||
auto const nftUris = executor_.readEach(yield, selectNFTURIStatements);
|
auto const nftUris = executor_.readEach(yield, selectNFTURIStatements);
|
||||||
@@ -193,7 +200,8 @@ public:
|
|||||||
maybeRow.has_value()) {
|
maybeRow.has_value()) {
|
||||||
auto [seq, owner, isBurned] = *maybeRow;
|
auto [seq, owner, isBurned] = *maybeRow;
|
||||||
NFT nft(nftIDs[i], seq, owner, isBurned);
|
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;
|
nft.uri = *maybeUri;
|
||||||
ret.nfts.push_back(nft);
|
ret.nfts.push_back(nft);
|
||||||
}
|
}
|
||||||
@@ -213,8 +221,9 @@ public:
|
|||||||
std::optional<ripple::AccountID> lastItem;
|
std::optional<ripple::AccountID> lastItem;
|
||||||
|
|
||||||
while (liveAccounts.size() < number) {
|
while (liveAccounts.size() < number) {
|
||||||
Statement const statement = lastItem ? schema_->selectAccountFromToken.bind(*lastItem, Limit{pageSize})
|
Statement const statement = lastItem
|
||||||
: schema_->selectAccountFromBeginning.bind(Limit{pageSize});
|
? schema_->selectAccountFromToken.bind(*lastItem, Limit{pageSize})
|
||||||
|
: schema_->selectAccountFromBeginning.bind(Limit{pageSize});
|
||||||
|
|
||||||
auto const res = executor_.read(yield, statement);
|
auto const res = executor_.read(yield, statement);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|||||||
@@ -83,8 +83,15 @@ struct NFTTransactionsData {
|
|||||||
* @param meta The transaction metadata
|
* @param meta The transaction metadata
|
||||||
* @param txHash The transaction hash
|
* @param txHash The transaction hash
|
||||||
*/
|
*/
|
||||||
NFTTransactionsData(ripple::uint256 const& tokenID, ripple::TxMeta const& meta, ripple::uint256 const& txHash)
|
NFTTransactionsData(
|
||||||
: tokenID(tokenID), ledgerSequence(meta.getLgrSeq()), transactionIndex(meta.getIndex()), txHash(txHash)
|
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.
|
* 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.
|
* The transaction index is only stored because we want to store only the final state of an NFT per
|
||||||
* Since we pull this from transactions we keep track of which tx index created this so we can de-duplicate, as it is
|
* ledger. Since we pull this from transactions we keep track of which tx index created this so we
|
||||||
* possible for one ledger to have multiple txs that change the state of the same NFT.
|
* 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 {
|
struct NFTsData {
|
||||||
ripple::uint256 tokenID;
|
ripple::uint256 tokenID;
|
||||||
@@ -113,8 +122,9 @@ struct NFTsData {
|
|||||||
* @brief Construct a new NFTsData object
|
* @brief Construct a new NFTsData object
|
||||||
*
|
*
|
||||||
* @note This constructor is used when parsing an NFTokenMint tx
|
* @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
|
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same
|
||||||
* explicitly record a null URI. For this reason, we _always_ write this field as a result of this tx.
|
* 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 tokenID The token ID
|
||||||
* @param owner The owner
|
* @param owner The owner
|
||||||
@@ -127,7 +137,11 @@ struct NFTsData {
|
|||||||
ripple::Blob const& uri,
|
ripple::Blob const& uri,
|
||||||
ripple::TxMeta const& meta
|
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 meta The transaction metadata
|
||||||
* @param isBurned Whether the NFT is burned
|
* @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)
|
: tokenID(tokenID)
|
||||||
, ledgerSequence(meta.getLgrSeq())
|
, ledgerSequence(meta.getLgrSeq())
|
||||||
, transactionIndex(meta.getIndex())
|
, transactionIndex(meta.getIndex())
|
||||||
@@ -154,8 +173,9 @@ struct NFTsData {
|
|||||||
* @brief Construct a new NFTsData object
|
* @brief Construct a new NFTsData object
|
||||||
*
|
*
|
||||||
* @note This constructor is used when parsing an NFTokenPage directly from ledger state.
|
* @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
|
* Unfortunately because of the extreme edge case of being able to re-mint an NFT with the same
|
||||||
* explicitly record a null URI. For this reason, we _always_ write this field as a result of this tx.
|
* 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 tokenID The token ID
|
||||||
* @param ledgerSequence The ledger sequence
|
* @param ledgerSequence The ledger sequence
|
||||||
|
|||||||
@@ -102,11 +102,17 @@ public:
|
|||||||
// This would be the first write to the table.
|
// This would be the first write to the table.
|
||||||
// In this case, insert both min_sequence/max_sequence range into the table.
|
// In this case, insert both min_sequence/max_sequence range into the table.
|
||||||
if (not range_.has_value()) {
|
if (not range_.has_value()) {
|
||||||
executor_.writeSync(schema_->insertLedgerRange, /* isLatestLedger =*/false, ledgerSequence_);
|
executor_.writeSync(
|
||||||
executor_.writeSync(schema_->insertLedgerRange, /* isLatestLedger =*/true, ledgerSequence_);
|
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_;
|
log_.warn() << "Update failed for ledger " << ledgerSequence_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -131,7 +137,8 @@ public:
|
|||||||
nftIDs = fetchNFTIDsByTaxon(issuer, *taxon, limit, cursorIn, yield);
|
nftIDs = fetchNFTIDsByTaxon(issuer, *taxon, limit, cursorIn, yield);
|
||||||
} else {
|
} else {
|
||||||
// Amazon Keyspaces Workflow for non-taxon queries
|
// 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));
|
auto const startTokenID = cursorIn.value_or(ripple::uint256(0));
|
||||||
|
|
||||||
Statement const firstQuery = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
|
Statement const firstQuery = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
|
||||||
@@ -163,10 +170,10 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief (Unsupported in Keyspaces) Fetches account root object indexes by page.
|
* @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.
|
* @note Loading the cache by enumerating all accounts is currently unsupported by the AWS
|
||||||
* This function's logic relies on "PER PARTITION LIMIT 1", which Keyspaces does not support, and there is
|
* Keyspaces backend. This function's logic relies on "PER PARTITION LIMIT 1", which Keyspaces
|
||||||
* no efficient alternative. This is acceptable as the cache is primarily loaded via diffs. Calling this
|
* does not support, and there is no efficient alternative. This is acceptable as the cache is
|
||||||
* function will throw an exception.
|
* primarily loaded via diffs. Calling this function will throw an exception.
|
||||||
*
|
*
|
||||||
* @param number The total number of accounts to fetch.
|
* @param number The total number of accounts to fetch.
|
||||||
* @param pageSize The maximum number of accounts per page.
|
* @param pageSize The maximum number of accounts per page.
|
||||||
@@ -220,7 +227,8 @@ private:
|
|||||||
{
|
{
|
||||||
std::vector<ripple::uint256> nftIDs;
|
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));
|
auto const startTokenID = cursorIn.value_or(ripple::uint256(0));
|
||||||
|
|
||||||
Statement firstQuery = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
|
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
|
NFTsAndCursor
|
||||||
populateNFTsAndCreateCursor(
|
populateNFTsAndCreateCursor(
|
||||||
@@ -273,17 +282,19 @@ private:
|
|||||||
std::vector<Statement> selectNFTStatements;
|
std::vector<Statement> selectNFTStatements;
|
||||||
selectNFTStatements.reserve(nftIDs.size());
|
selectNFTStatements.reserve(nftIDs.size());
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTStatements), [&](auto const& nftID) {
|
std::cbegin(nftIDs),
|
||||||
return schema_->selectNFT.bind(nftID, ledgerSequence);
|
std::cend(nftIDs),
|
||||||
}
|
std::back_inserter(selectNFTStatements),
|
||||||
|
[&](auto const& nftID) { return schema_->selectNFT.bind(nftID, ledgerSequence); }
|
||||||
);
|
);
|
||||||
|
|
||||||
std::vector<Statement> selectNFTURIStatements;
|
std::vector<Statement> selectNFTURIStatements;
|
||||||
selectNFTURIStatements.reserve(nftIDs.size());
|
selectNFTURIStatements.reserve(nftIDs.size());
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(nftIDs), std::cend(nftIDs), std::back_inserter(selectNFTURIStatements), [&](auto const& nftID) {
|
std::cbegin(nftIDs),
|
||||||
return schema_->selectNFTURI.bind(nftID, ledgerSequence);
|
std::cend(nftIDs),
|
||||||
}
|
std::back_inserter(selectNFTURIStatements),
|
||||||
|
[&](auto const& nftID) { return schema_->selectNFTURI.bind(nftID, ledgerSequence); }
|
||||||
);
|
);
|
||||||
|
|
||||||
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
|
||||||
@@ -295,7 +306,8 @@ private:
|
|||||||
maybeRow.has_value()) {
|
maybeRow.has_value()) {
|
||||||
auto [seq, owner, isBurned] = *maybeRow;
|
auto [seq, owner, isBurned] = *maybeRow;
|
||||||
NFT nft(nftIDs[i], seq, owner, isBurned);
|
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;
|
nft.uri = *maybeUri;
|
||||||
ret.nfts.push_back(nft);
|
ret.nfts.push_back(nft);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ LedgerCache::getSuccessorHitRate() const
|
|||||||
{
|
{
|
||||||
if (successorReqCounter_.get().value() == 0u)
|
if (successorReqCounter_.get().value() == 0u)
|
||||||
return 1;
|
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>
|
std::expected<void, std::string>
|
||||||
@@ -266,7 +267,9 @@ LedgerCache::saveToFile(std::string const& path) const
|
|||||||
|
|
||||||
impl::LedgerCacheFile file{path};
|
impl::LedgerCacheFile file{path};
|
||||||
std::shared_lock const lock{mtx_};
|
std::shared_lock const lock{mtx_};
|
||||||
impl::LedgerCacheFile::DataView const data{.latestSeq = latestSeq_, .map = map_, .deleted = deleted_};
|
impl::LedgerCacheFile::DataView const data{
|
||||||
|
.latestSeq = latestSeq_, .map = map_, .deleted = deleted_
|
||||||
|
};
|
||||||
return file.write(data);
|
return file.write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,26 +58,34 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// counters for fetchLedgerObject(s) hit rate
|
// counters for fetchLedgerObject(s) hit rate
|
||||||
std::reference_wrapper<util::prometheus::CounterInt> objectReqCounter_{PrometheusService::counterInt(
|
std::reference_wrapper<util::prometheus::CounterInt> objectReqCounter_{
|
||||||
"ledger_cache_counter_total_number",
|
PrometheusService::counterInt(
|
||||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "ledger_objects"}}),
|
"ledger_cache_counter_total_number",
|
||||||
"LedgerCache statistics"
|
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> objectHitCounter_{
|
||||||
)};
|
PrometheusService::counterInt(
|
||||||
|
"ledger_cache_counter_total_number",
|
||||||
|
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "ledger_objects"}})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// counters for fetchSuccessorKey hit rate
|
// counters for fetchSuccessorKey hit rate
|
||||||
std::reference_wrapper<util::prometheus::CounterInt> successorReqCounter_{PrometheusService::counterInt(
|
std::reference_wrapper<util::prometheus::CounterInt> successorReqCounter_{
|
||||||
"ledger_cache_counter_total_number",
|
PrometheusService::counterInt(
|
||||||
util::prometheus::Labels({{"type", "request"}, {"fetch", "successor_key"}}),
|
"ledger_cache_counter_total_number",
|
||||||
"ledgerCache"
|
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> successorHitCounter_{
|
||||||
)};
|
PrometheusService::counterInt(
|
||||||
|
"ledger_cache_counter_total_number",
|
||||||
|
util::prometheus::Labels({{"type", "cache_hit"}, {"fetch", "successor_key"}})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
CacheMap map_;
|
CacheMap map_;
|
||||||
CacheMap deleted_;
|
CacheMap deleted_;
|
||||||
@@ -96,7 +104,8 @@ private:
|
|||||||
"Whether ledger cache is disabled or not"
|
"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_;
|
std::unordered_set<ripple::uint256, ripple::hardened_hash<>> deletes_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -126,9 +126,9 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Sets the full flag to true.
|
* @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,
|
* This is used when cache loaded in its entirety at startup of the application. This can be
|
||||||
* populated together with initial ledger download (on first run) or downloaded from a peer node (specified in
|
* either loaded from DB, populated together with initial ledger download (on first run) or
|
||||||
* config).
|
* downloaded from a peer node (specified in config).
|
||||||
*/
|
*/
|
||||||
virtual void
|
virtual void
|
||||||
setFull() = 0;
|
setFull() = 0;
|
||||||
@@ -152,13 +152,15 @@ public:
|
|||||||
size() const = 0;
|
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
|
virtual float
|
||||||
getObjectHitRate() const = 0;
|
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
|
virtual float
|
||||||
getSuccessorHitRate() const = 0;
|
getSuccessorHitRate() const = 0;
|
||||||
|
|||||||
@@ -29,7 +29,10 @@
|
|||||||
|
|
||||||
namespace data {
|
namespace data {
|
||||||
|
|
||||||
LedgerCacheSaver::LedgerCacheSaver(util::config::ClioConfigDefinition const& config, LedgerCacheInterface const& cache)
|
LedgerCacheSaver::LedgerCacheSaver(
|
||||||
|
util::config::ClioConfigDefinition const& config,
|
||||||
|
LedgerCacheInterface const& cache
|
||||||
|
)
|
||||||
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path"))
|
: cacheFilePath_(config.maybeValue<std::string>("cache.file.path"))
|
||||||
, cache_(cache)
|
, cache_(cache)
|
||||||
, isAsync_(config.get<bool>("cache.file.async_save"))
|
, isAsync_(config.get<bool>("cache.file.async_save"))
|
||||||
@@ -51,11 +54,14 @@ LedgerCacheSaver::save()
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG(util::LogService::info()) << "Saving ledger cache to " << *cacheFilePath_;
|
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()) {
|
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 {
|
} 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_) {
|
if (not isAsync_) {
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ public:
|
|||||||
* @param config The configuration object containing the cache file path setting
|
* @param config The configuration object containing the cache file path setting
|
||||||
* @param cache Reference to the ledger cache interface to be saved
|
* @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.
|
* @brief Destructor that ensures the saving thread is properly joined.
|
||||||
|
|||||||
@@ -81,8 +81,16 @@ struct TransactionAndMetadata {
|
|||||||
* @param ledgerSequence The ledger sequence
|
* @param ledgerSequence The ledger sequence
|
||||||
* @param date The date
|
* @param date The date
|
||||||
*/
|
*/
|
||||||
TransactionAndMetadata(Blob transaction, Blob metadata, std::uint32_t ledgerSequence, std::uint32_t date)
|
TransactionAndMetadata(
|
||||||
: transaction{std::move(transaction)}, metadata{std::move(metadata)}, ledgerSequence{ledgerSequence}, date{date}
|
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,
|
ripple::AccountID const& owner,
|
||||||
Blob uri,
|
Blob uri,
|
||||||
bool isBurned)
|
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 owner The owner
|
||||||
* @param isBurned Whether the token is burned
|
* @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)
|
: NFT(tokenID, ledgerSequence, owner, {}, isBurned)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -212,8 +227,8 @@ struct NFT {
|
|||||||
/**
|
/**
|
||||||
* @brief Check if the NFT is the same as another
|
* @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
|
* Clearly two tokens are the same if they have the same ID, but this struct stores the state of
|
||||||
* token at a given ledger sequence, so we also need to compare with ledgerSequence.
|
* a given token at a given ledger sequence, so we also need to compare with ledgerSequence.
|
||||||
*
|
*
|
||||||
* @param other The other NFT
|
* @param other The other NFT
|
||||||
* @return true if they are the same; false otherwise
|
* @return true if they are the same; false otherwise
|
||||||
@@ -293,7 +308,8 @@ struct AmendmentKey {
|
|||||||
* @brief Construct a new AmendmentKey
|
* @brief Construct a new AmendmentKey
|
||||||
* @param val Anything convertible to a string
|
* @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;
|
operator<=>(AmendmentKey const& other) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr ripple::uint256 kFIRST_KEY{"0000000000000000000000000000000000000000000000000000000000000000"};
|
constexpr ripple::uint256 kFIRST_KEY{
|
||||||
constexpr ripple::uint256 kLAST_KEY{"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"};
|
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
constexpr ripple::uint256 kHI192{"0000000000000000000000000000000000000000000000001111111111111111"};
|
};
|
||||||
|
constexpr ripple::uint256 kLAST_KEY{
|
||||||
|
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||||
|
};
|
||||||
|
constexpr ripple::uint256 kHI192{
|
||||||
|
"0000000000000000000000000000000000000000000000001111111111111111"
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace data
|
} // namespace data
|
||||||
|
|||||||
@@ -104,7 +104,11 @@ public:
|
|||||||
* @param cache The ledger cache
|
* @param cache The ledger cache
|
||||||
* @param readOnly Whether the database should be in readonly mode
|
* @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)
|
: BackendInterface(cache)
|
||||||
, settingsProvider_{std::move(settingsProvider)}
|
, settingsProvider_{std::move(settingsProvider)}
|
||||||
, schema_{settingsProvider_}
|
, schema_{settingsProvider_}
|
||||||
@@ -116,8 +120,8 @@ public:
|
|||||||
|
|
||||||
if (not readOnly) {
|
if (not readOnly) {
|
||||||
if (auto const res = handle_.execute(schema_.createKeyspace); not res.has_value()) {
|
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
|
// on datastax, creation of keyspaces can be configured to only be done thru the
|
||||||
// interface. this does not mean that the keyspace does not already exist tho.
|
// admin interface. this does not mean that the keyspace does not already exist tho.
|
||||||
if (res.error().code() != CASS_ERROR_SERVER_UNAUTHORIZED)
|
if (res.error().code() != CASS_ERROR_SERVER_UNAUTHORIZED)
|
||||||
throw std::runtime_error("Could not create keyspace: " + res.error());
|
throw std::runtime_error("Could not create keyspace: " + res.error());
|
||||||
}
|
}
|
||||||
@@ -130,7 +134,8 @@ public:
|
|||||||
schema_.prepareStatements(handle_);
|
schema_.prepareStatements(handle_);
|
||||||
} catch (std::runtime_error const& ex) {
|
} catch (std::runtime_error const& ex) {
|
||||||
auto const error = fmt::format(
|
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.",
|
"node with write access to DB should be started first.",
|
||||||
ex.what(),
|
ex.what(),
|
||||||
readOnly
|
readOnly
|
||||||
@@ -169,8 +174,8 @@ public:
|
|||||||
auto cursor = txnCursor;
|
auto cursor = txnCursor;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
statement.bindAt(1, cursor->asTuple());
|
statement.bindAt(1, cursor->asTuple());
|
||||||
LOG(log_.debug()) << "account = " << ripple::strHex(account) << " tuple = " << cursor->ledgerSequence
|
LOG(log_.debug()) << "account = " << ripple::strHex(account)
|
||||||
<< cursor->transactionIndex;
|
<< " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
|
||||||
} else {
|
} else {
|
||||||
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
||||||
auto const placeHolder = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
|
auto const placeHolder = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
|
||||||
@@ -195,7 +200,8 @@ public:
|
|||||||
auto numRows = results.numRows();
|
auto numRows = results.numRows();
|
||||||
LOG(log_.info()) << "num_rows = " << 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);
|
hashes.push_back(hash);
|
||||||
if (--numRows == 0) {
|
if (--numRows == 0) {
|
||||||
LOG(log_.debug()) << "Setting cursor";
|
LOG(log_.debug()) << "Setting cursor";
|
||||||
@@ -251,7 +257,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ripple::LedgerHeader>
|
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)
|
if (auto const lock = ledgerCache_.get(); lock.has_value() && lock->seq == sequence)
|
||||||
return lock->ledger;
|
return lock->ledger;
|
||||||
@@ -259,7 +268,8 @@ public:
|
|||||||
auto const res = executor_.read(yield, schema_->selectLedgerBySeq, sequence);
|
auto const res = executor_.read(yield, schema_->selectLedgerBySeq, sequence);
|
||||||
if (res) {
|
if (res) {
|
||||||
if (auto const& result = res.value(); result) {
|
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));
|
auto const header = util::deserializeHeader(ripple::makeSlice(*maybeValue));
|
||||||
ledgerCache_.put(FetchLedgerCache::CacheEntry{header, sequence});
|
ledgerCache_.put(FetchLedgerCache::CacheEntry{header, sequence});
|
||||||
return header;
|
return header;
|
||||||
@@ -336,7 +346,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TransactionAndMetadata>
|
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);
|
auto hashes = fetchAllTransactionHashesInLedger(ledgerSequence, yield);
|
||||||
return fetchTransactions(hashes, yield);
|
return fetchTransactions(hashes, yield);
|
||||||
@@ -349,7 +362,8 @@ public:
|
|||||||
) const override
|
) const override
|
||||||
{
|
{
|
||||||
auto start = std::chrono::system_clock::now();
|
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) {
|
if (not res) {
|
||||||
LOG(log_.error()) << "Could not fetch all transaction hashes: " << res.error();
|
LOG(log_.error()) << "Could not fetch all transaction hashes: " << res.error();
|
||||||
@@ -368,9 +382,12 @@ public:
|
|||||||
hashes.push_back(std::move(hash));
|
hashes.push_back(std::move(hash));
|
||||||
|
|
||||||
auto end = std::chrono::system_clock::now();
|
auto end = std::chrono::system_clock::now();
|
||||||
LOG(log_.debug()) << "Fetched " << hashes.size() << " transaction hashes from database in "
|
LOG(
|
||||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
|
log_.debug()
|
||||||
<< " milliseconds";
|
) << "Fetched "
|
||||||
|
<< hashes.size() << " transaction hashes from database in "
|
||||||
|
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
|
||||||
|
<< " milliseconds";
|
||||||
|
|
||||||
return hashes;
|
return hashes;
|
||||||
}
|
}
|
||||||
@@ -386,7 +403,8 @@ public:
|
|||||||
if (not res)
|
if (not res)
|
||||||
return std::nullopt;
|
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 [seq, owner, isBurned] = *maybeRow;
|
||||||
auto result = std::make_optional<NFT>(tokenID, seq, owner, isBurned);
|
auto result = std::make_optional<NFT>(tokenID, seq, owner, isBurned);
|
||||||
|
|
||||||
@@ -437,8 +455,8 @@ public:
|
|||||||
auto cursor = cursorIn;
|
auto cursor = cursorIn;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
statement.bindAt(1, cursor->asTuple());
|
statement.bindAt(1, cursor->asTuple());
|
||||||
LOG(log_.debug()) << "token_id = " << ripple::strHex(tokenID) << " tuple = " << cursor->ledgerSequence
|
LOG(log_.debug()) << "token_id = " << ripple::strHex(tokenID)
|
||||||
<< cursor->transactionIndex;
|
<< " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
|
||||||
} else {
|
} else {
|
||||||
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
auto const seq = forward ? rng->minSequence : rng->maxSequence;
|
||||||
auto const placeHolder = forward ? 0 : std::numeric_limits<std::uint32_t>::max();
|
auto const placeHolder = forward ? 0 : std::numeric_limits<std::uint32_t>::max();
|
||||||
@@ -461,7 +479,8 @@ public:
|
|||||||
auto numRows = results.numRows();
|
auto numRows = results.numRows();
|
||||||
LOG(log_.info()) << "num_rows = " << 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);
|
hashes.push_back(hash);
|
||||||
if (--numRows == 0) {
|
if (--numRows == 0) {
|
||||||
LOG(log_.debug()) << "Setting cursor";
|
LOG(log_.debug()) << "Setting cursor";
|
||||||
@@ -495,7 +514,11 @@ public:
|
|||||||
) const override
|
) const override
|
||||||
{
|
{
|
||||||
auto const holderEntries = executor_.read(
|
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();
|
auto const& holderResults = holderEntries.value();
|
||||||
@@ -513,7 +536,9 @@ public:
|
|||||||
|
|
||||||
auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
|
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());
|
mptObjects.erase(it, mptObjects.end());
|
||||||
|
|
||||||
@@ -531,7 +556,8 @@ public:
|
|||||||
boost::asio::yield_context yield
|
boost::asio::yield_context yield
|
||||||
) const override
|
) 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 res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
|
||||||
if (auto const result = res->template get<Blob>(); result) {
|
if (auto const result = res->template get<Blob>(); result) {
|
||||||
if (result->size())
|
if (result->size())
|
||||||
@@ -553,7 +579,8 @@ public:
|
|||||||
boost::asio::yield_context yield
|
boost::asio::yield_context yield
|
||||||
) const override
|
) 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 res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
|
||||||
if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
|
if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
|
||||||
auto [_, seq] = result.value();
|
auto [_, seq] = result.value();
|
||||||
@@ -571,7 +598,8 @@ public:
|
|||||||
fetchTransaction(ripple::uint256 const& hash, boost::asio::yield_context yield) const override
|
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 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;
|
auto [transaction, meta, seq, date] = *maybeValue;
|
||||||
return std::make_optional<TransactionAndMetadata>(transaction, meta, seq, date);
|
return std::make_optional<TransactionAndMetadata>(transaction, meta, seq, date);
|
||||||
}
|
}
|
||||||
@@ -591,7 +619,8 @@ public:
|
|||||||
boost::asio::yield_context yield
|
boost::asio::yield_context yield
|
||||||
) const override
|
) 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 (auto const result = res->template get<ripple::uint256>(); result) {
|
||||||
if (*result == kLAST_KEY)
|
if (*result == kLAST_KEY)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -607,7 +636,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TransactionAndMetadata>
|
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())
|
if (hashes.empty())
|
||||||
return {};
|
return {};
|
||||||
@@ -622,9 +654,10 @@ public:
|
|||||||
auto const timeDiff = util::timed([this, yield, &results, &hashes, &statements]() {
|
auto const timeDiff = util::timed([this, yield, &results, &hashes, &statements]() {
|
||||||
// TODO: seems like a job for "hash IN (list of hashes)" instead?
|
// TODO: seems like a job for "hash IN (list of hashes)" instead?
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(hashes), std::cend(hashes), std::back_inserter(statements), [this](auto const& hash) {
|
std::cbegin(hashes),
|
||||||
return schema_->selectTransaction.bind(hash);
|
std::cend(hashes),
|
||||||
}
|
std::back_inserter(statements),
|
||||||
|
[this](auto const& hash) { return schema_->selectTransaction.bind(hash); }
|
||||||
);
|
);
|
||||||
|
|
||||||
auto const entries = executor_.readEach(yield, statements);
|
auto const entries = executor_.readEach(yield, statements);
|
||||||
@@ -633,7 +666,8 @@ public:
|
|||||||
std::cend(entries),
|
std::cend(entries),
|
||||||
std::back_inserter(results),
|
std::back_inserter(results),
|
||||||
[](auto const& res) -> TransactionAndMetadata {
|
[](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 *maybeRow;
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -642,8 +676,8 @@ public:
|
|||||||
});
|
});
|
||||||
|
|
||||||
ASSERT(numHashes == results.size(), "Number of hashes and results must match");
|
ASSERT(numHashes == results.size(), "Number of hashes and results must match");
|
||||||
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in " << timeDiff
|
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in "
|
||||||
<< " milliseconds";
|
<< timeDiff << " milliseconds";
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,14 +702,18 @@ public:
|
|||||||
|
|
||||||
// TODO: seems like a job for "key IN (list of keys)" instead?
|
// TODO: seems like a job for "key IN (list of keys)" instead?
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(keys), std::cend(keys), std::back_inserter(statements), [this, &sequence](auto const& key) {
|
std::cbegin(keys),
|
||||||
return schema_->selectObject.bind(key, sequence);
|
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);
|
auto const entries = executor_.readEach(yield, statements);
|
||||||
std::transform(
|
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)
|
if (auto const maybeValue = res.template get<Blob>(); maybeValue)
|
||||||
return *maybeValue;
|
return *maybeValue;
|
||||||
|
|
||||||
@@ -688,34 +726,40 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<LedgerObject>
|
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 [keys, timeDiff] =
|
||||||
auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
|
util::timed([this, &ledgerSequence, yield]() -> std::vector<ripple::uint256> {
|
||||||
if (not res) {
|
auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
|
||||||
LOG(log_.error()) << "Could not fetch ledger diff: " << res.error() << "; ledger = " << ledgerSequence;
|
if (not res) {
|
||||||
return {};
|
LOG(log_.error()) << "Could not fetch ledger diff: " << res.error()
|
||||||
}
|
<< "; ledger = " << ledgerSequence;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
auto const& results = res.value();
|
auto const& results = res.value();
|
||||||
if (not results) {
|
if (not results) {
|
||||||
LOG(log_.error()) << "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
|
LOG(log_.error())
|
||||||
return {};
|
<< "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
|
||||||
}
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ripple::uint256> resultKeys;
|
std::vector<ripple::uint256> resultKeys;
|
||||||
for (auto [key] : extract<ripple::uint256>(results))
|
for (auto [key] : extract<ripple::uint256>(results))
|
||||||
resultKeys.push_back(key);
|
resultKeys.push_back(key);
|
||||||
|
|
||||||
return resultKeys;
|
return resultKeys;
|
||||||
});
|
});
|
||||||
|
|
||||||
// one of the above errors must have happened
|
// one of the above errors must have happened
|
||||||
if (keys.empty())
|
if (keys.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in " << timeDiff
|
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in "
|
||||||
<< " milliseconds";
|
<< timeDiff << " milliseconds";
|
||||||
|
|
||||||
auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
|
||||||
std::vector<LedgerObject> results;
|
std::vector<LedgerObject> results;
|
||||||
@@ -733,7 +777,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string>
|
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));
|
auto const res = executor_.read(yield, schema_->selectMigratorStatus, Text(migratorName));
|
||||||
if (not res) {
|
if (not res) {
|
||||||
@@ -771,7 +818,8 @@ public:
|
|||||||
void
|
void
|
||||||
doWriteLedgerObject(std::string&& key, std::uint32_t const seq, std::string&& blob) override
|
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_)
|
if (range_)
|
||||||
executor_.write(schema_->insertDiff, seq, key);
|
executor_.write(schema_->insertDiff, seq, key);
|
||||||
@@ -783,7 +831,8 @@ public:
|
|||||||
writeSuccessor(std::string&& key, std::uint32_t const seq, std::string&& successor) override
|
writeSuccessor(std::string&& key, std::uint32_t const seq, std::string&& successor) override
|
||||||
{
|
{
|
||||||
LOG(log_.trace()) << "Writing successor. key = " << key.size() << " bytes. "
|
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(!key.empty(), "Key must not be empty");
|
||||||
ASSERT(!successor.empty(), "Successor 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
|
statements.reserve(data.size() * 10); // assume 10 transactions avg
|
||||||
|
|
||||||
for (auto& record : data) {
|
for (auto& record : data) {
|
||||||
std::ranges::transform(record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
std::ranges::transform(
|
||||||
return schema_->insertAccountTx.bind(
|
record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||||
std::forward<decltype(account)>(account),
|
return schema_->insertAccountTx.bind(
|
||||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
std::forward<decltype(account)>(account),
|
||||||
record.txHash
|
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||||
);
|
record.txHash
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
executor_.write(std::move(statements));
|
executor_.write(std::move(statements));
|
||||||
@@ -815,13 +866,15 @@ public:
|
|||||||
std::vector<Statement> statements;
|
std::vector<Statement> statements;
|
||||||
statements.reserve(record.accounts.size());
|
statements.reserve(record.accounts.size());
|
||||||
|
|
||||||
std::ranges::transform(record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
std::ranges::transform(
|
||||||
return schema_->insertAccountTx.bind(
|
record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
|
||||||
std::forward<decltype(account)>(account),
|
return schema_->insertAccountTx.bind(
|
||||||
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
std::forward<decltype(account)>(account),
|
||||||
record.txHash
|
std::make_tuple(record.ledgerSequence, record.transactionIndex),
|
||||||
);
|
record.txHash
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
executor_.write(std::move(statements));
|
executor_.write(std::move(statements));
|
||||||
}
|
}
|
||||||
@@ -834,7 +887,9 @@ public:
|
|||||||
|
|
||||||
std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
|
std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
|
||||||
return schema_->insertNFTTx.bind(
|
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_->insertLedgerTransaction, seq, hash);
|
||||||
executor_.write(
|
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) {
|
for (NFTsData const& record : data) {
|
||||||
if (!record.onlyUriChanged) {
|
if (!record.onlyUriChanged) {
|
||||||
statements.push_back(
|
statements.push_back(schema_->insertNFT.bind(
|
||||||
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
|
record.tokenID, record.ledgerSequence, record.owner, record.isBurned
|
||||||
);
|
));
|
||||||
|
|
||||||
// If `uri` is set (and it can be set to an empty uri), we know this
|
// 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
|
// 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)),
|
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
|
||||||
record.tokenID
|
record.tokenID
|
||||||
));
|
));
|
||||||
statements.push_back(
|
statements.push_back(schema_->insertNFTURI.bind(
|
||||||
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
|
record.tokenID, record.ledgerSequence, record.uri.value()
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// only uri changed, we update the uri table only
|
// only uri changed, we update the uri table only
|
||||||
statements.push_back(
|
statements.push_back(schema_->insertNFTURI.bind(
|
||||||
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
|
record.tokenID, record.ledgerSequence, record.uri.value()
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,14 +978,18 @@ public:
|
|||||||
writeMigratorStatus(std::string const& migratorName, std::string const& status) override
|
writeMigratorStatus(std::string const& migratorName, std::string const& status) override
|
||||||
{
|
{
|
||||||
executor_.writeSync(
|
executor_.writeSync(
|
||||||
schema_->insertMigratorStatus, data::cassandra::Text{migratorName}, data::cassandra::Text(status)
|
schema_->insertMigratorStatus,
|
||||||
|
data::cassandra::Text{migratorName},
|
||||||
|
data::cassandra::Text(status)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
writeNodeMessage(boost::uuids::uuid const& uuid, std::string message) override
|
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
|
bool
|
||||||
|
|||||||
@@ -78,12 +78,13 @@ concept SomeExecutionStrategy = requires(
|
|||||||
* @brief The requirements of a retry policy.
|
* @brief The requirements of a retry policy.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept SomeRetryPolicy = requires(T a, boost::asio::io_context ioc, CassandraError err, uint32_t attempt) {
|
concept SomeRetryPolicy =
|
||||||
{ T(ioc) };
|
requires(T a, boost::asio::io_context ioc, CassandraError err, uint32_t attempt) {
|
||||||
{ a.shouldRetry(err) } -> std::same_as<bool>;
|
{ T(ioc) };
|
||||||
{
|
{ a.shouldRetry(err) } -> std::same_as<bool>;
|
||||||
a.retry([]() {})
|
{
|
||||||
} -> std::same_as<void>;
|
a.retry([]() {})
|
||||||
};
|
} -> std::same_as<void>;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace data::cassandra
|
} // namespace data::cassandra
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ public:
|
|||||||
bool
|
bool
|
||||||
isTimeout() const
|
isTimeout() const
|
||||||
{
|
{
|
||||||
return code_ == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE or code_ == CASS_ERROR_LIB_REQUEST_TIMED_OUT or
|
return code_ == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE or
|
||||||
code_ == CASS_ERROR_SERVER_UNAVAILABLE or code_ == CASS_ERROR_SERVER_OVERLOADED or
|
code_ == CASS_ERROR_LIB_REQUEST_TIMED_OUT or code_ == CASS_ERROR_SERVER_UNAVAILABLE or
|
||||||
code_ == CASS_ERROR_SERVER_READ_TIMEOUT;
|
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)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +85,11 @@ Handle::disconnect() const
|
|||||||
Handle::FutureType
|
Handle::FutureType
|
||||||
Handle::asyncReconnect(std::string_view keyspace) const
|
Handle::asyncReconnect(std::string_view keyspace) const
|
||||||
{
|
{
|
||||||
if (auto rc = asyncDisconnect().await(); not rc) // sync
|
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);
|
return asyncConnect(keyspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +127,10 @@ Handle::asyncExecute(StatementType const& statement) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle::FutureWithCallbackType
|
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)};
|
return Handle::FutureWithCallbackType{cass_session_execute(session_, statement), std::move(cb)};
|
||||||
}
|
}
|
||||||
@@ -147,9 +154,14 @@ Handle::execute(std::vector<StatementType> const& statements) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle::FutureWithCallbackType
|
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
|
Handle::PreparedStatementType
|
||||||
|
|||||||
@@ -293,14 +293,18 @@ public:
|
|||||||
execute(std::vector<StatementType> const& statements) const;
|
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 statements The statements to execute
|
||||||
* @param cb The callback to execute when data is ready
|
* @param cb The callback to execute when data is ready
|
||||||
* @return A future that holds onto the callback provided
|
* @return A future that holds onto the callback provided
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] FutureWithCallbackType
|
[[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.
|
* @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
|
* @brief Extracts the results into series of std::tuple<Types...> by creating a simple wrapper with
|
||||||
* iterator inside.
|
* an STL input iterator inside.
|
||||||
*
|
*
|
||||||
* You can call .begin() and .end() in order to iterate as usual.
|
* 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.
|
* 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
|
* @return The qualified table name
|
||||||
*/
|
*/
|
||||||
template <SomeSettingsProvider SettingsProviderType>
|
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
|
* @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());
|
auto const path = std::filesystem::path(certPath.asString());
|
||||||
std::ifstream fileStream(path.string(), std::ios::in);
|
std::ifstream fileStream(path.string(), std::ios::in);
|
||||||
if (!fileStream) {
|
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()) {
|
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;
|
return contents;
|
||||||
@@ -82,7 +88,8 @@ SettingsProvider::parseSettings() const
|
|||||||
|
|
||||||
// all config values used in settings is under "database.cassandra" prefix
|
// all config values used in settings is under "database.cassandra" prefix
|
||||||
if (config_.getValueView("secure_connect_bundle").hasValue()) {
|
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;
|
settings.connectionInfo = bundle;
|
||||||
} else {
|
} else {
|
||||||
Settings::ContactPoints out;
|
Settings::ContactPoints out;
|
||||||
@@ -101,12 +108,14 @@ SettingsProvider::parseSettings() const
|
|||||||
|
|
||||||
if (config_.getValueView("connect_timeout").hasValue()) {
|
if (config_.getValueView("connect_timeout").hasValue()) {
|
||||||
auto const connectTimeoutSecond = config_.get<uint32_t>("connect_timeout");
|
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()) {
|
if (config_.getValueView("request_timeout").hasValue()) {
|
||||||
auto const requestTimeoutSecond = config_.get<uint32_t>("request_timeout");
|
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();
|
settings.certificate = parseOptionalCertificate();
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ template <
|
|||||||
typename StatementType,
|
typename StatementType,
|
||||||
typename HandleType = Handle,
|
typename HandleType = Handle,
|
||||||
SomeRetryPolicy RetryPolicyType = ExponentialBackoffRetryPolicy>
|
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 FutureWithCallbackType = typename HandleType::FutureWithCallbackType;
|
||||||
using CallbackType = std::function<void(typename HandleType::ResultOrErrorType)>;
|
using CallbackType = std::function<void(typename HandleType::ResultOrErrorType)>;
|
||||||
using RetryCallbackType = std::function<void()>;
|
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);
|
ptr->execute(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +106,10 @@ private:
|
|||||||
CallbackType&& onComplete,
|
CallbackType&& onComplete,
|
||||||
RetryCallbackType&& onRetry
|
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.
|
* 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.
|
* 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)
|
Batch::Batch(std::vector<Statement> const& statements)
|
||||||
: ManagedObject{cass_batch_new(CASS_BATCH_TYPE_UNLOGGED), kBATCH_DELETER}
|
: 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;
|
using std::to_string;
|
||||||
|
|
||||||
cass_cluster_set_token_aware_routing(*this, cass_true);
|
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(
|
throw std::runtime_error(
|
||||||
fmt::format("Error setting cassandra protocol version to v4: {}", cass_error_desc(rc))
|
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) {
|
if (auto const rc = cass_cluster_set_num_threads_io(*this, settings.threads); rc != CASS_OK) {
|
||||||
throw std::runtime_error(
|
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
|
// TODO: AWS keyspace reads should be local_one to save cost
|
||||||
if (settings.provider == cassandra::impl::Provider::Keyspace) {
|
if (settings.provider == cassandra::impl::Provider::Keyspace) {
|
||||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM); rc != CASS_OK) {
|
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM);
|
||||||
throw std::runtime_error(fmt::format("Error setting keyspace consistency: {}", cass_error_desc(rc)));
|
rc != CASS_OK) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
fmt::format("Error setting keyspace consistency: {}", cass_error_desc(rc))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_QUORUM); rc != CASS_OK) {
|
if (auto const rc = cass_cluster_set_consistency(*this, CASS_CONSISTENCY_QUORUM);
|
||||||
throw std::runtime_error(fmt::format("Error setting cassandra consistency: {}", cass_error_desc(rc)));
|
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) {
|
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 =
|
auto const queueSize = settings.queueSizeIO.value_or(
|
||||||
settings.queueSizeIO.value_or(settings.maxWriteRequestsOutstanding + settings.maxReadRequestsOutstanding);
|
settings.maxWriteRequestsOutstanding + settings.maxReadRequestsOutstanding
|
||||||
|
);
|
||||||
if (auto const rc = cass_cluster_set_queue_size_io(*this, queueSize); rc != CASS_OK) {
|
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);
|
setupConnection(settings);
|
||||||
@@ -111,7 +128,9 @@ Cluster::setupContactPoints(Settings::ContactPoints const& points)
|
|||||||
auto throwErrorIfNeeded = [](CassError rc, std::string const& label, std::string const& value) {
|
auto throwErrorIfNeeded = [](CassError rc, std::string const& label, std::string const& value) {
|
||||||
if (rc != CASS_OK) {
|
if (rc != CASS_OK) {
|
||||||
throw std::runtime_error(
|
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)
|
Cluster::setupSecureBundle(Settings::SecureConnectionBundle const& bundle)
|
||||||
{
|
{
|
||||||
LOG(log_.debug()) << "Attempt connection using secure 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) {
|
if (auto const rc =
|
||||||
throw std::runtime_error("Failed to connect using secure connection bundle " + bundle.bundle);
|
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;
|
return;
|
||||||
|
|
||||||
LOG(log_.debug()) << "Set credentials; username: " << settings.username.value();
|
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
|
} // namespace data::cassandra::impl
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ struct Settings {
|
|||||||
bool enableLog = false;
|
bool enableLog = false;
|
||||||
|
|
||||||
/** @brief Connect timeout specified in milliseconds */
|
/** @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 */
|
/** @brief Request timeout specified in milliseconds */
|
||||||
std::chrono::milliseconds requestTimeout = std::chrono::milliseconds{0}; // no timeout at all
|
std::chrono::milliseconds requestTimeout = std::chrono::milliseconds{0}; // no timeout at all
|
||||||
@@ -106,25 +107,31 @@ struct Settings {
|
|||||||
Provider provider = kDEFAULT_PROVIDER;
|
Provider provider = kDEFAULT_PROVIDER;
|
||||||
|
|
||||||
/** @brief Size of the IO queue */
|
/** @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 */
|
/** @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 */
|
/** @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` */
|
/** @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
|
Settings
|
||||||
withContactPoints(std::string_view contactPoints)
|
withContactPoints(std::string_view contactPoints)
|
||||||
{
|
{
|
||||||
auto tmp = *this;
|
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;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -267,8 +267,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Non-blocking query execution used for writing data. Contrast with write, this method does not execute
|
* @brief Non-blocking query execution used for writing data. Contrast with write, this method
|
||||||
* the statements in a batch.
|
* does not execute the statements in a batch.
|
||||||
*
|
*
|
||||||
* Retries forever with retry policy specified by @ref AsyncExecutor.
|
* Retries forever with retry policy specified by @ref AsyncExecutor.
|
||||||
*
|
*
|
||||||
@@ -278,7 +278,9 @@ public:
|
|||||||
void
|
void
|
||||||
writeEach(std::vector<StatementType>&& statements)
|
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 {
|
future.emplace(handle_.get().asyncExecute(statements, [sself](auto&& res) mutable {
|
||||||
boost::asio::post(
|
boost::asio::post(
|
||||||
boost::asio::get_associated_executor(*sself),
|
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 {
|
future.emplace(handle_.get().asyncExecute(statement, [sself](auto&& res) mutable {
|
||||||
boost::asio::post(
|
boost::asio::post(
|
||||||
boost::asio::get_associated_executor(*sself),
|
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);
|
futures.reserve(numOutstanding);
|
||||||
counters_->registerReadStarted(statements.size());
|
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 sself = std::make_shared<Self>(std::move(self));
|
||||||
auto executionHandler = [&errorsCount, &numOutstanding, sself](auto const& res) mutable {
|
auto executionHandler =
|
||||||
if (not res)
|
[&errorsCount, &numOutstanding, sself](auto const& res) mutable {
|
||||||
++errorsCount;
|
if (not res)
|
||||||
|
++errorsCount;
|
||||||
|
|
||||||
// when all async operations complete unblock the result
|
// when all async operations complete unblock the result
|
||||||
if (--numOutstanding == 0) {
|
if (--numOutstanding == 0) {
|
||||||
boost::asio::post(boost::asio::get_associated_executor(*sself), [sself]() mutable {
|
boost::asio::post(
|
||||||
sself->complete();
|
boost::asio::get_associated_executor(*sself),
|
||||||
});
|
[sself]() mutable { sself->complete(); }
|
||||||
}
|
);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::transform(
|
std::transform(
|
||||||
std::cbegin(statements),
|
std::cbegin(statements),
|
||||||
@@ -461,7 +471,9 @@ public:
|
|||||||
numReadRequestsOutstanding_ -= statements.size();
|
numReadRequestsOutstanding_ -= statements.size();
|
||||||
|
|
||||||
if (errorsCount > 0) {
|
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_->registerReadError(errorsCount);
|
||||||
counters_->registerReadFinished(startTime, statements.size() - errorsCount);
|
counters_->registerReadFinished(startTime, statements.size() - errorsCount);
|
||||||
throw DatabaseTimeout{};
|
throw DatabaseTimeout{};
|
||||||
@@ -471,7 +483,8 @@ public:
|
|||||||
std::vector<ResultType> results;
|
std::vector<ResultType> results;
|
||||||
results.reserve(futures.size());
|
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::transform(
|
||||||
std::make_move_iterator(std::begin(futures)),
|
std::make_move_iterator(std::begin(futures)),
|
||||||
std::make_move_iterator(std::end(futures)),
|
std::make_move_iterator(std::end(futures)),
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ void
|
|||||||
invokeHelper(CassFuture* ptr, void* cbPtr)
|
invokeHelper(CassFuture* ptr, void* cbPtr)
|
||||||
{
|
{
|
||||||
// Note: can't use Future{ptr}.get() because double free will occur :/
|
// Note: can't use Future{ptr}.get() because double free will occur :/
|
||||||
// Note2: we are moving/copying it locally as a workaround for an issue we are seeing from asio recently.
|
// Note2: we are moving/copying it locally as a workaround for an issue we are seeing from asio
|
||||||
// stackoverflow.com/questions/77004137/boost-asio-async-compose-gets-stuck-under-load
|
// recently. stackoverflow.com/questions/77004137/boost-asio-async-compose-gets-stuck-under-load
|
||||||
auto* cb = static_cast<FutureWithCallback::FnType*>(cbPtr);
|
auto* cb = static_cast<FutureWithCallback::FnType*>(cbPtr);
|
||||||
auto local = std::make_unique<FutureWithCallback::FnType>(std::move(*cb));
|
auto local = std::make_unique<FutureWithCallback::FnType>(std::move(*cb));
|
||||||
if (auto const rc = cass_future_error_code(ptr); rc) {
|
if (auto const rc = cass_future_error_code(ptr); rc) {
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ struct Result : public ManagedObject<CassResult const> {
|
|||||||
std::size_t idx = 0;
|
std::size_t idx = 0;
|
||||||
auto advanceId = [&idx]() { return idx++; };
|
auto advanceId = [&idx]() { return idx++; };
|
||||||
|
|
||||||
return std::make_optional<std::tuple<RowTypes...>>({extractColumn<RowTypes>(row, advanceId())...});
|
return std::make_optional<std::tuple<RowTypes...>>(
|
||||||
|
{extractColumn<RowTypes>(row, advanceId())...}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename RowType>
|
template <typename RowType>
|
||||||
|
|||||||
@@ -63,9 +63,11 @@ public:
|
|||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
shouldRetry([[maybe_unused]] CassandraError err)
|
shouldRetry([[maybe_unused]] CassandraError err)
|
||||||
{
|
{
|
||||||
auto const delayMs = std::chrono::duration_cast<std::chrono::milliseconds>(retry_.delayValue()).count();
|
auto const delayMs =
|
||||||
LOG(log_.error()) << "Cassandra write error: " << err << ", current retries " << retry_.attemptNumber()
|
std::chrono::duration_cast<std::chrono::milliseconds>(retry_.delayValue()).count();
|
||||||
<< ", retrying in " << delayMs << " milliseconds";
|
LOG(log_.error()) << "Cassandra write error: " << err << ", current retries "
|
||||||
|
<< retry_.attemptNumber() << ", retrying in " << delayMs
|
||||||
|
<< " milliseconds";
|
||||||
|
|
||||||
return true; // keep retrying forever
|
return true; // keep retrying forever
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ constexpr auto kCONTEXT_DELETER = [](CassSsl* ptr) { cass_ssl_free(ptr); };
|
|||||||
|
|
||||||
namespace data::cassandra::impl {
|
namespace data::cassandra::impl {
|
||||||
|
|
||||||
SslContext::SslContext(std::string const& certificate) : ManagedObject{cass_ssl_new(), kCONTEXT_DELETER}
|
SslContext::SslContext(std::string const& certificate)
|
||||||
|
: ManagedObject{cass_ssl_new(), kCONTEXT_DELETER}
|
||||||
{
|
{
|
||||||
cass_ssl_set_verify_flags(*this, CASS_SSL_VERIFY_NONE);
|
cass_ssl_set_verify_flags(*this, CASS_SSL_VERIFY_NONE);
|
||||||
if (auto const rc = cass_ssl_add_trusted_cert(*this, certificate.c_str()); rc != CASS_OK) {
|
if (auto const rc = cass_ssl_add_trusted_cert(*this, certificate.c_str()); rc != CASS_OK) {
|
||||||
throw std::runtime_error(std::string{"Error setting Cassandra SSL Context: "} + cass_error_desc(rc));
|
throw std::runtime_error(
|
||||||
|
std::string{"Error setting Cassandra SSL Context: "} + cass_error_desc(rc)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,12 +96,17 @@ public:
|
|||||||
{
|
{
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) {
|
auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) {
|
||||||
if (rc != CASS_OK)
|
if (rc != CASS_OK) {
|
||||||
throw std::logic_error(fmt::format("[{}] at idx {}: {}", label, idx, cass_error_desc(rc)));
|
throw std::logic_error(
|
||||||
|
fmt::format("[{}] at idx {}: {}", label, idx, cass_error_desc(rc))
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto bindBytes = [this, idx](auto const* data, size_t size) {
|
auto bindBytes = [this, idx](auto const* data, size_t size) {
|
||||||
return cass_statement_bind_bytes(*this, idx, static_cast<cass_byte_t const*>(data), size);
|
return cass_statement_bind_bytes(
|
||||||
|
*this, idx, static_cast<cass_byte_t const*>(data), size
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
using DecayedType = std::decay_t<Type>;
|
using DecayedType = std::decay_t<Type>;
|
||||||
@@ -110,7 +115,10 @@ public:
|
|||||||
using UintByteTupleType = std::tuple<uint32_t, ripple::uint256>;
|
using UintByteTupleType = std::tuple<uint32_t, ripple::uint256>;
|
||||||
using ByteVectorType = std::vector<ripple::uint256>;
|
using ByteVectorType = std::vector<ripple::uint256>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<DecayedType, ripple::uint256> || std::is_same_v<DecayedType, ripple::uint192>) {
|
if constexpr (
|
||||||
|
std::is_same_v<DecayedType, ripple::uint256> ||
|
||||||
|
std::is_same_v<DecayedType, ripple::uint192>
|
||||||
|
) {
|
||||||
auto const rc = bindBytes(value.data(), value.size());
|
auto const rc = bindBytes(value.data(), value.size());
|
||||||
throwErrorIfNeeded(rc, "Bind ripple::base_uint");
|
throwErrorIfNeeded(rc, "Bind ripple::base_uint");
|
||||||
} else if constexpr (std::is_same_v<DecayedType, ripple::AccountID>) {
|
} else if constexpr (std::is_same_v<DecayedType, ripple::AccountID>) {
|
||||||
@@ -121,17 +129,22 @@ public:
|
|||||||
throwErrorIfNeeded(rc, "Bind vector<unsigned char>");
|
throwErrorIfNeeded(rc, "Bind vector<unsigned char>");
|
||||||
} else if constexpr (std::is_convertible_v<DecayedType, std::string>) {
|
} else if constexpr (std::is_convertible_v<DecayedType, std::string>) {
|
||||||
// reinterpret_cast is needed here :'(
|
// reinterpret_cast is needed here :'(
|
||||||
auto const rc = bindBytes(reinterpret_cast<unsigned char const*>(value.data()), value.size());
|
auto const rc =
|
||||||
|
bindBytes(reinterpret_cast<unsigned char const*>(value.data()), value.size());
|
||||||
throwErrorIfNeeded(rc, "Bind string (as bytes)");
|
throwErrorIfNeeded(rc, "Bind string (as bytes)");
|
||||||
} else if constexpr (std::is_convertible_v<DecayedType, Text>) {
|
} else if constexpr (std::is_convertible_v<DecayedType, Text>) {
|
||||||
auto const rc = cass_statement_bind_string_n(*this, idx, value.text.c_str(), value.text.size());
|
auto const rc =
|
||||||
|
cass_statement_bind_string_n(*this, idx, value.text.c_str(), value.text.size());
|
||||||
throwErrorIfNeeded(rc, "Bind string (as TEXT)");
|
throwErrorIfNeeded(rc, "Bind string (as TEXT)");
|
||||||
} else if constexpr (std::is_same_v<DecayedType, UintTupleType> ||
|
} else if constexpr (
|
||||||
std::is_same_v<DecayedType, UintByteTupleType>) {
|
std::is_same_v<DecayedType, UintTupleType> ||
|
||||||
|
std::is_same_v<DecayedType, UintByteTupleType>
|
||||||
|
) {
|
||||||
auto const rc = cass_statement_bind_tuple(*this, idx, Tuple{std::forward<Type>(value)});
|
auto const rc = cass_statement_bind_tuple(*this, idx, Tuple{std::forward<Type>(value)});
|
||||||
throwErrorIfNeeded(rc, "Bind tuple<uint32, uint32> or <uint32_t, ripple::uint256>");
|
throwErrorIfNeeded(rc, "Bind tuple<uint32, uint32> or <uint32_t, ripple::uint256>");
|
||||||
} else if constexpr (std::is_same_v<DecayedType, ByteVectorType>) {
|
} else if constexpr (std::is_same_v<DecayedType, ByteVectorType>) {
|
||||||
auto const rc = cass_statement_bind_collection(*this, idx, Collection{std::forward<Type>(value)});
|
auto const rc =
|
||||||
|
cass_statement_bind_collection(*this, idx, Collection{std::forward<Type>(value)});
|
||||||
throwErrorIfNeeded(rc, "Bind collection");
|
throwErrorIfNeeded(rc, "Bind collection");
|
||||||
} else if constexpr (std::is_same_v<DecayedType, bool>) {
|
} else if constexpr (std::is_same_v<DecayedType, bool>) {
|
||||||
auto const rc = cass_statement_bind_bool(*this, idx, value ? cass_true : cass_false);
|
auto const rc = cass_statement_bind_bool(*this, idx, value ? cass_true : cass_false);
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ namespace data::cassandra::impl {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/* implicit */ TupleIterator::TupleIterator(CassIterator* ptr) : ManagedObject{ptr, kTUPLE_ITERATOR_DELETER}
|
/* implicit */ TupleIterator::TupleIterator(CassIterator* ptr)
|
||||||
|
: ManagedObject{ptr, kTUPLE_ITERATOR_DELETER}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ public:
|
|||||||
auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) {
|
auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) {
|
||||||
if (rc != CASS_OK) {
|
if (rc != CASS_OK) {
|
||||||
auto const tag = '[' + std::string{label} + ']';
|
auto const tag = '[' + std::string{label} + ']';
|
||||||
throw std::logic_error(tag + " at idx " + to_string(idx) + ": " + cass_error_desc(rc));
|
throw std::logic_error(
|
||||||
|
tag + " at idx " + to_string(idx) + ": " + cass_error_desc(rc)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ LedgerCacheFile::write(DataView dataView)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Header const header{
|
Header const header{
|
||||||
.latestSeq = dataView.latestSeq, .mapSize = dataView.map.size(), .deletedSize = dataView.deleted.size()
|
.latestSeq = dataView.latestSeq,
|
||||||
|
.mapSize = dataView.map.size(),
|
||||||
|
.deletedSize = dataView.deleted.size()
|
||||||
};
|
};
|
||||||
file.write(header);
|
file.write(header);
|
||||||
file.write(kSEPARATOR);
|
file.write(kSEPARATOR);
|
||||||
@@ -115,10 +117,17 @@ LedgerCacheFile::write(DataView dataView)
|
|||||||
auto const hash = file.hash();
|
auto const hash = file.hash();
|
||||||
file.write(hash.data(), decltype(hash)::bytes);
|
file.write(hash.data(), decltype(hash)::bytes);
|
||||||
|
|
||||||
|
// flush internal buffer explicitly before renaming
|
||||||
|
if (auto const expectedSuccess = file.close(); not expectedSuccess.has_value()) {
|
||||||
|
return expectedSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::filesystem::rename(newFilePath, path_);
|
std::filesystem::rename(newFilePath, path_);
|
||||||
} catch (std::exception const& e) {
|
} catch (std::exception const& e) {
|
||||||
return std::unexpected{fmt::format("Error moving cache file from {} to {}: {}", newFilePath, path_, e.what())};
|
return std::unexpected{
|
||||||
|
fmt::format("Error moving cache file from {} to {}: {}", newFilePath, path_, e.what())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -140,12 +149,14 @@ LedgerCacheFile::read(uint32_t minLatestSequence)
|
|||||||
return std::unexpected{"Error reading cache header"};
|
return std::unexpected{"Error reading cache header"};
|
||||||
}
|
}
|
||||||
if (header.version != kVERSION) {
|
if (header.version != kVERSION) {
|
||||||
return std::unexpected{
|
return std::unexpected{fmt::format(
|
||||||
fmt::format("Cache has wrong version: expected {} found {}", kVERSION, header.version)
|
"Cache has wrong version: expected {} found {}", kVERSION, header.version
|
||||||
};
|
)};
|
||||||
}
|
}
|
||||||
if (header.latestSeq < minLatestSequence) {
|
if (header.latestSeq < minLatestSequence) {
|
||||||
return std::unexpected{fmt::format("Latest sequence ({}) in the cache file is too low.", header.latestSeq)};
|
return std::unexpected{
|
||||||
|
fmt::format("Latest sequence ({}) in the cache file is too low.", header.latestSeq)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
result.latestSeq = header.latestSeq;
|
result.latestSeq = header.latestSeq;
|
||||||
|
|
||||||
@@ -153,7 +164,8 @@ LedgerCacheFile::read(uint32_t minLatestSequence)
|
|||||||
if (not file.readRaw(separator.data(), separator.size())) {
|
if (not file.readRaw(separator.data(), separator.size())) {
|
||||||
return std::unexpected{"Error reading cache header"};
|
return std::unexpected{"Error reading cache header"};
|
||||||
}
|
}
|
||||||
if (auto verificationResult = verifySeparator(separator); not verificationResult.has_value()) {
|
if (auto verificationResult = verifySeparator(separator);
|
||||||
|
not verificationResult.has_value()) {
|
||||||
return std::unexpected{std::move(verificationResult).error()};
|
return std::unexpected{std::move(verificationResult).error()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,15 +174,16 @@ LedgerCacheFile::read(uint32_t minLatestSequence)
|
|||||||
if (not cacheEntryExpected.has_value()) {
|
if (not cacheEntryExpected.has_value()) {
|
||||||
return std::unexpected{std::move(cacheEntryExpected).error()};
|
return std::unexpected{std::move(cacheEntryExpected).error()};
|
||||||
}
|
}
|
||||||
// Using insert with hint here to decrease insert operation complexity to the amortized constant instead of
|
// Using insert with hint here to decrease insert operation complexity to the amortized
|
||||||
// logN
|
// constant instead of logN
|
||||||
result.map.insert(result.map.end(), std::move(cacheEntryExpected).value());
|
result.map.insert(result.map.end(), std::move(cacheEntryExpected).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (not file.readRaw(separator.data(), separator.size())) {
|
if (not file.readRaw(separator.data(), separator.size())) {
|
||||||
return std::unexpected{"Error reading separator"};
|
return std::unexpected{"Error reading separator"};
|
||||||
}
|
}
|
||||||
if (auto verificationResult = verifySeparator(separator); not verificationResult.has_value()) {
|
if (auto verificationResult = verifySeparator(separator);
|
||||||
|
not verificationResult.has_value()) {
|
||||||
return std::unexpected{std::move(verificationResult).error()};
|
return std::unexpected{std::move(verificationResult).error()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,13 +198,16 @@ LedgerCacheFile::read(uint32_t minLatestSequence)
|
|||||||
if (not file.readRaw(separator.data(), separator.size())) {
|
if (not file.readRaw(separator.data(), separator.size())) {
|
||||||
return std::unexpected{"Error reading separator"};
|
return std::unexpected{"Error reading separator"};
|
||||||
}
|
}
|
||||||
if (auto verificationResult = verifySeparator(separator); not verificationResult.has_value()) {
|
if (auto verificationResult = verifySeparator(separator);
|
||||||
|
not verificationResult.has_value()) {
|
||||||
return std::unexpected{std::move(verificationResult).error()};
|
return std::unexpected{std::move(verificationResult).error()};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const dataHash = file.hash();
|
auto const dataHash = file.hash();
|
||||||
ripple::uint256 hashFromFile{};
|
ripple::uint256 hashFromFile{};
|
||||||
if (not file.readRaw(reinterpret_cast<char*>(hashFromFile.data()), decltype(hashFromFile)::bytes)) {
|
if (not file.readRaw(
|
||||||
|
reinterpret_cast<char*>(hashFromFile.data()), decltype(hashFromFile)::bytes
|
||||||
|
)) {
|
||||||
return std::unexpected{"Error reading hash"};
|
return std::unexpected{"Error reading hash"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -59,4 +60,14 @@ OutputFile::hash() const
|
|||||||
return std::move(sum).finalize();
|
return std::move(sum).finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string>
|
||||||
|
OutputFile::close()
|
||||||
|
{
|
||||||
|
file_.close();
|
||||||
|
if (not file_) {
|
||||||
|
return std::unexpected{"Error closing cache file"};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace data::impl
|
} // namespace data::impl
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -60,6 +61,9 @@ public:
|
|||||||
ripple::uint256
|
ripple::uint256
|
||||||
hash() const;
|
hash() const;
|
||||||
|
|
||||||
|
std::expected<void, std::string>
|
||||||
|
close();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
void
|
||||||
writeToFile(char const* data, size_t size);
|
writeToFile(char const* data, size_t size);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user