mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-27 17:22:33 +00:00
Compare commits
7 Commits
copilot/co
...
vlntb/grpc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3ad4b5269 | ||
|
|
9db517bba8 | ||
|
|
cb26308ba3 | ||
|
|
21367d49fb | ||
|
|
d34b21d9e0 | ||
|
|
4f96a5fa48 | ||
|
|
b6acff5184 |
309
.clang-tidy
309
.clang-tidy
@@ -1,143 +1,105 @@
|
||||
---
|
||||
Checks: "-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-bool-pointer-implicit-conversion,
|
||||
bugprone-casting-through-void,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-compare-pointer-to-member-virtual-function,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-incorrect-enable-if,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-multi-level-implicit-pointer-conversion,
|
||||
bugprone-multiple-new-in-one-expression,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-non-zero-enum-to-bool-conversion,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-shared-ptr-array-mismatch,
|
||||
bugprone-signal-handler,
|
||||
bugprone-signed-char-misuse,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-standalone-empty,
|
||||
bugprone-string-constructor,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-stringview-nullptr,
|
||||
bugprone-suspicious-enum-usage,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memory-comparison,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-exception-at-new,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-no-suspend-with-lock,
|
||||
cppcoreguidelines-virtual-class-destructor,
|
||||
hicpp-ignored-remove-result,
|
||||
misc-definitions-in-headers,
|
||||
misc-header-include-cycle,
|
||||
misc-misplaced-const,
|
||||
misc-static-assert,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
misc-unused-alias-decls,
|
||||
misc-unused-using-decls,
|
||||
readability-duplicate-include,
|
||||
readability-enum-initial-value,
|
||||
readability-misleading-indentation,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-declaration,
|
||||
readability-reference-to-constructed-temporary,
|
||||
modernize-deprecated-headers,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-move-constructor-init,
|
||||
performance-trivially-destructible
|
||||
bugprone-argument-comment
|
||||
"
|
||||
# ---
|
||||
# checks that have some issues that need to be resolved:
|
||||
#
|
||||
# bugprone-empty-catch,
|
||||
# bugprone-assert-side-effect,
|
||||
# bugprone-bad-signal-to-kill-thread,
|
||||
# bugprone-bool-pointer-implicit-conversion,
|
||||
# bugprone-casting-through-void,
|
||||
# bugprone-chained-comparison,
|
||||
# bugprone-compare-pointer-to-member-virtual-function,
|
||||
# bugprone-copy-constructor-init,
|
||||
# bugprone-crtp-constructor-accessibility,
|
||||
# bugprone-dangling-handle,
|
||||
# bugprone-dynamic-static-initializers,
|
||||
# bugprone-empty-catch,
|
||||
# bugprone-fold-init-type,
|
||||
# bugprone-forward-declaration-namespace,
|
||||
# bugprone-inaccurate-erase,
|
||||
# bugprone-inc-dec-in-conditions,
|
||||
# bugprone-reserved-identifier,
|
||||
# bugprone-incorrect-enable-if,
|
||||
# bugprone-incorrect-roundings,
|
||||
# bugprone-infinite-loop,
|
||||
# bugprone-integer-division,
|
||||
# bugprone-lambda-function-name,
|
||||
# bugprone-macro-parentheses,
|
||||
# bugprone-macro-repeated-side-effects,
|
||||
# bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
# bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
# bugprone-misplaced-widening-cast,
|
||||
# bugprone-move-forwarding-reference,
|
||||
# bugprone-unused-local-non-trivial-variable,
|
||||
# bugprone-return-const-ref-from-parameter,
|
||||
# bugprone-switch-missing-default-case,
|
||||
# bugprone-sizeof-expression,
|
||||
# bugprone-suspicious-stringview-data-usage,
|
||||
# bugprone-suspicious-missing-comma,
|
||||
# bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
# bugprone-multi-level-implicit-pointer-conversion,
|
||||
# bugprone-multiple-new-in-one-expression,
|
||||
# bugprone-multiple-statement-macro,
|
||||
# bugprone-no-escape,
|
||||
# bugprone-non-zero-enum-to-bool-conversion,
|
||||
# bugprone-optional-value-conversion,
|
||||
# bugprone-parent-virtual-call,
|
||||
# bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
# bugprone-posix-return,
|
||||
# bugprone-redundant-branch-condition,
|
||||
# bugprone-reserved-identifier,
|
||||
# bugprone-return-const-ref-from-parameter,
|
||||
# bugprone-shared-ptr-array-mismatch,
|
||||
# bugprone-signal-handler,
|
||||
# bugprone-signed-char-misuse,
|
||||
# bugprone-sizeof-container,
|
||||
# bugprone-sizeof-expression,
|
||||
# bugprone-spuriously-wake-up-functions,
|
||||
# bugprone-standalone-empty,
|
||||
# bugprone-string-constructor,
|
||||
# bugprone-string-integer-assignment,
|
||||
# bugprone-string-literal-with-embedded-nul,
|
||||
# bugprone-stringview-nullptr,
|
||||
# bugprone-suspicious-enum-usage,
|
||||
# bugprone-suspicious-include,
|
||||
# bugprone-suspicious-memory-comparison,
|
||||
# bugprone-suspicious-memset-usage,
|
||||
# bugprone-suspicious-missing-comma,
|
||||
# bugprone-suspicious-realloc-usage,
|
||||
# bugprone-suspicious-semicolon,
|
||||
# bugprone-suspicious-string-compare,
|
||||
# bugprone-suspicious-stringview-data-usage,
|
||||
# bugprone-swapped-arguments,
|
||||
# bugprone-switch-missing-default-case,
|
||||
# bugprone-terminating-continue,
|
||||
# bugprone-throw-keyword-missing,
|
||||
# bugprone-too-small-loop-variable,
|
||||
# bugprone-undefined-memory-manipulation,
|
||||
# bugprone-undelegated-constructor,
|
||||
# bugprone-unhandled-exception-at-new,
|
||||
# bugprone-unhandled-self-assignment,
|
||||
# bugprone-unique-ptr-array-mismatch,
|
||||
# bugprone-unsafe-functions,
|
||||
# bugprone-unused-local-non-trivial-variable,
|
||||
# bugprone-unused-raii,
|
||||
# bugprone-unused-return-value,
|
||||
# bugprone-use-after-move,
|
||||
# bugprone-unhandled-self-assignment,
|
||||
# bugprone-unused-raii,
|
||||
#
|
||||
# cppcoreguidelines-misleading-capture-default-by-value,
|
||||
# bugprone-virtual-near-miss,
|
||||
# cppcoreguidelines-init-variables,
|
||||
# cppcoreguidelines-misleading-capture-default-by-value,
|
||||
# cppcoreguidelines-no-suspend-with-lock,
|
||||
# cppcoreguidelines-pro-type-member-init,
|
||||
# cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
# cppcoreguidelines-use-default-member-init,
|
||||
# cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||
#
|
||||
# cppcoreguidelines-use-default-member-init,
|
||||
# cppcoreguidelines-virtual-class-destructor,
|
||||
# hicpp-ignored-remove-result,
|
||||
# llvm-namespace-comment,
|
||||
# misc-const-correctness,
|
||||
# misc-definitions-in-headers,
|
||||
# misc-header-include-cycle,
|
||||
# misc-include-cleaner,
|
||||
# misc-misplaced-const,
|
||||
# misc-redundant-expression,
|
||||
#
|
||||
# readability-avoid-nested-conditional-operator,
|
||||
# readability-avoid-return-with-void-value,
|
||||
# readability-braces-around-statements,
|
||||
# readability-container-contains,
|
||||
# readability-container-size-empty,
|
||||
# readability-convert-member-functions-to-static,
|
||||
# readability-const-return-type,
|
||||
# readability-else-after-return,
|
||||
# readability-implicit-bool-conversion,
|
||||
# readability-inconsistent-declaration-parameter-name,
|
||||
# readability-identifier-naming,
|
||||
# readability-make-member-function-const,
|
||||
# readability-math-missing-parentheses,
|
||||
# readability-redundant-inline-specifier,
|
||||
# readability-redundant-member-init,
|
||||
# readability-redundant-casting,
|
||||
# readability-redundant-string-init,
|
||||
# readability-simplify-boolean-expr,
|
||||
# readability-static-definition-in-anonymous-namespace,
|
||||
# readability-suspicious-call-argument,
|
||||
# readability-use-std-min-max,
|
||||
# readability-static-accessed-through-instance,
|
||||
#
|
||||
# misc-static-assert,
|
||||
# misc-throw-by-value-catch-by-reference,
|
||||
# misc-unused-alias-decls,
|
||||
# misc-unused-using-decls,
|
||||
# modernize-concat-nested-namespaces,
|
||||
# modernize-deprecated-headers,
|
||||
# modernize-make-shared,
|
||||
# modernize-make-unique,
|
||||
# modernize-pass-by-value,
|
||||
# modernize-type-traits,
|
||||
# modernize-use-designated-initializers,
|
||||
@@ -149,50 +111,79 @@ Checks: "-*,
|
||||
# modernize-use-starts-ends-with,
|
||||
# modernize-use-std-numbers,
|
||||
# modernize-use-using,
|
||||
#
|
||||
# performance-faster-string-find,
|
||||
# performance-for-range-copy,
|
||||
# performance-implicit-conversion-in-loop,
|
||||
# performance-inefficient-vector-operation,
|
||||
# performance-move-const-arg,
|
||||
# performance-move-constructor-init,
|
||||
# performance-no-automatic-move,
|
||||
# ---
|
||||
# performance-trivially-destructible,
|
||||
# readability-avoid-nested-conditional-operator,
|
||||
# readability-avoid-return-with-void-value,
|
||||
# readability-braces-around-statements,
|
||||
# readability-const-return-type,
|
||||
# readability-container-contains,
|
||||
# readability-container-size-empty,
|
||||
# readability-convert-member-functions-to-static,
|
||||
# readability-duplicate-include,
|
||||
# readability-else-after-return,
|
||||
# readability-enum-initial-value,
|
||||
# readability-implicit-bool-conversion,
|
||||
# readability-inconsistent-declaration-parameter-name,
|
||||
# readability-identifier-naming,
|
||||
# readability-make-member-function-const,
|
||||
# readability-math-missing-parentheses,
|
||||
# readability-misleading-indentation,
|
||||
# readability-non-const-parameter,
|
||||
# readability-redundant-casting,
|
||||
# readability-redundant-declaration,
|
||||
# readability-redundant-inline-specifier,
|
||||
# readability-redundant-member-init,
|
||||
# readability-redundant-string-init,
|
||||
# readability-reference-to-constructed-temporary,
|
||||
# readability-simplify-boolean-expr,
|
||||
# readability-static-accessed-through-instance,
|
||||
# readability-static-definition-in-anonymous-namespace,
|
||||
# readability-suspicious-call-argument,
|
||||
# readability-use-std-min-max
|
||||
#
|
||||
CheckOptions:
|
||||
# readability-braces-around-statements.ShortStatementLines: 2
|
||||
# readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassCase: CamelCase
|
||||
# readability-identifier-naming.StructCase: CamelCase
|
||||
# readability-identifier-naming.UnionCase: CamelCase
|
||||
# readability-identifier-naming.EnumCase: CamelCase
|
||||
# readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.GlobalConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
# readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
# readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
# readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
# readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMemberCase: camelBack
|
||||
# readability-identifier-naming.ClassConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticVariablePrefix: "k"
|
||||
# readability-identifier-naming.ConstexprVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.ConstexprVariablePrefix: "k"
|
||||
# readability-identifier-naming.LocalConstantCase: camelBack
|
||||
# readability-identifier-naming.LocalVariableCase: camelBack
|
||||
# readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
# readability-identifier-naming.ParameterCase: camelBack
|
||||
# readability-identifier-naming.FunctionCase: camelBack
|
||||
# readability-identifier-naming.MemberCase: camelBack
|
||||
# readability-identifier-naming.PrivateMemberSuffix: _
|
||||
# readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
# readability-identifier-naming.PublicMemberSuffix: ""
|
||||
# readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*"
|
||||
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
# CheckOptions:
|
||||
# readability-braces-around-statements.ShortStatementLines: 2
|
||||
# readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassCase: CamelCase
|
||||
# readability-identifier-naming.StructCase: CamelCase
|
||||
# readability-identifier-naming.UnionCase: CamelCase
|
||||
# readability-identifier-naming.EnumCase: CamelCase
|
||||
# readability-identifier-naming.EnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.ScopedEnumConstantCase: CamelCase
|
||||
# readability-identifier-naming.GlobalConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.GlobalConstantPrefix: "k"
|
||||
# readability-identifier-naming.GlobalVariableCase: CamelCase
|
||||
# readability-identifier-naming.GlobalVariablePrefix: "g"
|
||||
# readability-identifier-naming.ConstexprFunctionCase: camelBack
|
||||
# readability-identifier-naming.ConstexprMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMethodCase: camelBack
|
||||
# readability-identifier-naming.ClassMemberCase: camelBack
|
||||
# readability-identifier-naming.ClassConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.ClassConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticConstantCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticConstantPrefix: "k"
|
||||
# readability-identifier-naming.StaticVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.StaticVariablePrefix: "k"
|
||||
# readability-identifier-naming.ConstexprVariableCase: UPPER_CASE
|
||||
# readability-identifier-naming.ConstexprVariablePrefix: "k"
|
||||
# readability-identifier-naming.LocalConstantCase: camelBack
|
||||
# readability-identifier-naming.LocalVariableCase: camelBack
|
||||
# readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
# readability-identifier-naming.ParameterCase: camelBack
|
||||
# readability-identifier-naming.FunctionCase: camelBack
|
||||
# readability-identifier-naming.MemberCase: camelBack
|
||||
# readability-identifier-naming.PrivateMemberSuffix: _
|
||||
# readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
# readability-identifier-naming.PublicMemberSuffix: ""
|
||||
# readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*"
|
||||
# bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
# bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
|
||||
# misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp'
|
||||
#
|
||||
|
||||
@@ -78,9 +78,9 @@ jobs:
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }}
|
||||
FILES: ${{ inputs.files }}
|
||||
run: |
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "$BUILD_DIR" $FILES 2>&1 | tee clang-tidy-output.txt
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: steps.run_clang_tidy.outcome != 'success'
|
||||
|
||||
14
.github/workflows/reusable-clang-tidy.yml
vendored
14
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -22,8 +22,7 @@ jobs:
|
||||
if: ${{ inputs.check_only_changed }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }}
|
||||
any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }}
|
||||
any_changed: ${{ steps.changed_files.outputs.any_changed }}
|
||||
all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -39,17 +38,10 @@ jobs:
|
||||
**/*.ipp
|
||||
separator: " "
|
||||
|
||||
- name: Get changed clang-tidy configuration
|
||||
id: changed_clang_tidy
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
with:
|
||||
files: |
|
||||
.clang-tidy
|
||||
|
||||
run-clang-tidy:
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_changed == 'true') }}
|
||||
uses: ./.github/workflows/reusable-clang-tidy-files.yml
|
||||
with:
|
||||
files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed == 'true' && '') || (inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '') }}
|
||||
files: ${{ inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '' }}
|
||||
create_issue_on_failure: ${{ inputs.create_issue_on_failure }}
|
||||
|
||||
@@ -251,29 +251,6 @@ pip3 install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## Clang-tidy
|
||||
|
||||
All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy).
|
||||
|
||||
There is a Continuous Integration job that runs clang-tidy on pull requests. The CI will check:
|
||||
|
||||
- All changed C++ files (`.cpp`, `.h`, `.ipp`) when only code files are modified
|
||||
- **All files in the repository** when the `.clang-tidy` configuration file is changed
|
||||
|
||||
This ensures that configuration changes don't introduce new warnings across the codebase.
|
||||
|
||||
### Running clang-tidy locally
|
||||
|
||||
Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions.
|
||||
|
||||
Then run clang-tidy on your local changes:
|
||||
|
||||
```
|
||||
run-clang-tidy -p build src tests
|
||||
```
|
||||
|
||||
This will check all source files in the `src` and `tests` directories using the compile commands from your `build` directory.
|
||||
|
||||
## Contracts and instrumentation
|
||||
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <cctype>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace beast {
|
||||
@@ -182,7 +181,7 @@ split_commas(FwdIt first, FwdIt last)
|
||||
|
||||
template <class Result = std::vector<std::string>>
|
||||
Result
|
||||
split_commas(std::string_view const& s)
|
||||
split_commas(boost::beast::string_view const& s)
|
||||
{
|
||||
return split_commas(s.begin(), s.end());
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Json {
|
||||
|
||||
class Value;
|
||||
|
||||
using Output = std::function<void(std::string_view const&)>;
|
||||
using Output = std::function<void(boost::beast::string_view const&)>;
|
||||
|
||||
inline Output
|
||||
stringOutput(std::string& s)
|
||||
{
|
||||
return [&](std::string_view const& b) { s.append(b.data(), b.size()); };
|
||||
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
|
||||
}
|
||||
|
||||
/** Writes a minimal representation of a Json value to an Output in O(n) time.
|
||||
|
||||
62
include/xrpl/server/FDGuard.h
Normal file
62
include/xrpl/server/FDGuard.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/predef.h>
|
||||
|
||||
#if !BOOST_OS_WINDOWS
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* FDGuard: File Descriptor monitoring and throttling helper
|
||||
*
|
||||
* Monitors system file descriptor usage and provides throttling
|
||||
* decisions based on configurable thresholds.
|
||||
*
|
||||
* Thread-safe: All methods are const and stateless.
|
||||
*/
|
||||
class FDGuard
|
||||
{
|
||||
public:
|
||||
struct FDStats
|
||||
{
|
||||
std::uint64_t used{0}; // Currently open file descriptors
|
||||
std::uint64_t limit{0}; // System limit (from getrlimit)
|
||||
};
|
||||
|
||||
/**
|
||||
* Query current file descriptor usage statistics.
|
||||
*
|
||||
* @return FDStats if available, std::nullopt on Windows or if query fails
|
||||
*
|
||||
* Implementation:
|
||||
* - POSIX: Uses getrlimit(RLIMIT_NOFILE) for limit,
|
||||
* counts entries in /proc/self/fd (Linux) or /dev/fd (BSD/macOS)
|
||||
* - Windows: Always returns std::nullopt
|
||||
*/
|
||||
static std::optional<FDStats>
|
||||
query_fd_stats();
|
||||
|
||||
/**
|
||||
* Determine if system should throttle based on FD availability.
|
||||
*
|
||||
* @param free_threshold Minimum ratio of free FDs required (0.0 to 1.0)
|
||||
* Default: 0.70 (require at least 70% free)
|
||||
* @return true if free FDs below threshold (throttle recommended),
|
||||
* false otherwise or if stats unavailable
|
||||
*
|
||||
* Example: threshold=0.70, limit=1000, used=800
|
||||
* free=200, ratio=0.20 < 0.70 → returns true (throttle)
|
||||
*/
|
||||
static bool
|
||||
should_throttle(double free_threshold = 0.70);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -49,7 +48,8 @@ private:
|
||||
bool ping_active_ = false;
|
||||
boost::beast::websocket::ping_data payload_;
|
||||
error_code ec_;
|
||||
std::function<void(boost::beast::websocket::frame_type, std::string_view)> control_callback_;
|
||||
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
|
||||
control_callback_;
|
||||
|
||||
public:
|
||||
template <class Body, class Headers>
|
||||
@@ -137,7 +137,7 @@ protected:
|
||||
on_ping(error_code const& ec);
|
||||
|
||||
void
|
||||
on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload);
|
||||
on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
|
||||
|
||||
void
|
||||
on_timer(error_code ec);
|
||||
@@ -414,11 +414,11 @@ template <class Handler, class Impl>
|
||||
void
|
||||
BaseWSPeer<Handler, Impl>::on_ping_pong(
|
||||
boost::beast::websocket::frame_type kind,
|
||||
std::string_view payload)
|
||||
boost::beast::string_view payload)
|
||||
{
|
||||
if (kind == boost::beast::websocket::frame_type::pong)
|
||||
{
|
||||
std::string_view p(payload_.begin(), payload_.size());
|
||||
boost::beast::string_view p(payload_.begin());
|
||||
if (payload == p)
|
||||
{
|
||||
close_on_timer_ = false;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/server/FDGuard.h>
|
||||
#include <xrpl/server/detail/ExponentialBackoff.h>
|
||||
#include <xrpl/server/detail/PlainHTTPPeer.h>
|
||||
#include <xrpl/server/detail/SSLHTTPPeer.h>
|
||||
#include <xrpl/server/detail/io_list.h>
|
||||
@@ -17,14 +19,6 @@
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/predef.h>
|
||||
|
||||
#if !BOOST_OS_WINDOWS
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -90,27 +84,12 @@ private:
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
bool ssl_;
|
||||
bool plain_;
|
||||
static constexpr std::chrono::milliseconds INITIAL_ACCEPT_DELAY{50};
|
||||
static constexpr std::chrono::milliseconds MAX_ACCEPT_DELAY{2000};
|
||||
std::chrono::milliseconds accept_delay_{INITIAL_ACCEPT_DELAY};
|
||||
ExponentialBackoff backoff_;
|
||||
boost::asio::steady_timer backoff_timer_;
|
||||
static constexpr double FREE_FD_THRESHOLD = 0.70;
|
||||
|
||||
struct FDStats
|
||||
{
|
||||
std::uint64_t used{0};
|
||||
std::uint64_t limit{0};
|
||||
};
|
||||
|
||||
void
|
||||
reOpen();
|
||||
|
||||
std::optional<FDStats>
|
||||
query_fd_stats() const;
|
||||
|
||||
bool
|
||||
should_throttle_for_fds();
|
||||
|
||||
public:
|
||||
Door(Handler& handler, boost::asio::io_context& io_context, Port const& port, beast::Journal j);
|
||||
|
||||
@@ -335,13 +314,13 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
{
|
||||
while (acceptor_.is_open())
|
||||
{
|
||||
if (should_throttle_for_fds())
|
||||
if (FDGuard::should_throttle(0.70))
|
||||
{
|
||||
backoff_timer_.expires_after(accept_delay_);
|
||||
backoff_timer_.expires_after(backoff_.current());
|
||||
boost::system::error_code tec;
|
||||
backoff_timer_.async_wait(do_yield[tec]);
|
||||
accept_delay_ = std::min(accept_delay_ * 2, MAX_ACCEPT_DELAY);
|
||||
JLOG(j_.warn()) << "Throttling do_accept for " << accept_delay_.count() << "ms.";
|
||||
auto const delay = backoff_.increase();
|
||||
JLOG(j_.warn()) << "Throttling do_accept for " << delay.count() << "ms.";
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -358,14 +337,15 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
if (ec == boost::asio::error::no_descriptors ||
|
||||
ec == boost::asio::error::no_buffer_space)
|
||||
{
|
||||
JLOG(j_.warn()) << "accept: Too many open files. Pausing for "
|
||||
<< accept_delay_.count() << "ms.";
|
||||
auto const delay = backoff_.current();
|
||||
JLOG(j_.warn()) << "accept: Too many open files. Pausing for " << delay.count()
|
||||
<< "ms.";
|
||||
|
||||
backoff_timer_.expires_after(accept_delay_);
|
||||
backoff_timer_.expires_after(delay);
|
||||
boost::system::error_code tec;
|
||||
backoff_timer_.async_wait(do_yield[tec]);
|
||||
|
||||
accept_delay_ = std::min(accept_delay_ * 2, MAX_ACCEPT_DELAY);
|
||||
backoff_.increase();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,7 +354,7 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
continue;
|
||||
}
|
||||
|
||||
accept_delay_ = INITIAL_ACCEPT_DELAY;
|
||||
backoff_.reset();
|
||||
|
||||
if (ssl_ && plain_)
|
||||
{
|
||||
@@ -389,57 +369,4 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
}
|
||||
}
|
||||
|
||||
template <class Handler>
|
||||
std::optional<typename Door<Handler>::FDStats>
|
||||
Door<Handler>::query_fd_stats() const
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
return std::nullopt;
|
||||
#else
|
||||
FDStats s;
|
||||
struct rlimit rl;
|
||||
if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY)
|
||||
return std::nullopt;
|
||||
s.limit = static_cast<std::uint64_t>(rl.rlim_cur);
|
||||
#if BOOST_OS_LINUX
|
||||
constexpr char const* kFdDir = "/proc/self/fd";
|
||||
#else
|
||||
constexpr char const* kFdDir = "/dev/fd";
|
||||
#endif
|
||||
if (DIR* d = ::opendir(kFdDir))
|
||||
{
|
||||
std::uint64_t cnt = 0;
|
||||
while (::readdir(d) != nullptr)
|
||||
++cnt;
|
||||
::closedir(d);
|
||||
// readdir counts '.', '..', and the DIR* itself shows in the list
|
||||
s.used = (cnt >= 3) ? (cnt - 3) : 0;
|
||||
return s;
|
||||
}
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Handler>
|
||||
bool
|
||||
Door<Handler>::should_throttle_for_fds()
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
return false;
|
||||
#else
|
||||
auto const stats = query_fd_stats();
|
||||
if (!stats || stats->limit == 0)
|
||||
return false;
|
||||
|
||||
auto const& s = *stats;
|
||||
auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull;
|
||||
double const free_ratio = static_cast<double>(free) / static_cast<double>(s.limit);
|
||||
if (free_ratio < FREE_FD_THRESHOLD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
93
include/xrpl/server/detail/ExponentialBackoff.h
Normal file
93
include/xrpl/server/detail/ExponentialBackoff.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Exponential backoff delay manager with configurable limits.
|
||||
*
|
||||
* Manages delay values that double on each increase (capped at maximum)
|
||||
* and can be reset to initial value. Used for throttling accept() calls
|
||||
* when file descriptor pressure is detected.
|
||||
*/
|
||||
class ExponentialBackoff
|
||||
{
|
||||
public:
|
||||
using duration_type = std::chrono::milliseconds;
|
||||
|
||||
static constexpr duration_type DEFAULT_INITIAL_DELAY{50};
|
||||
static constexpr duration_type DEFAULT_MAX_DELAY{2000};
|
||||
|
||||
/**
|
||||
* @brief Construct with custom or default delay parameters.
|
||||
*
|
||||
* @param initial Initial delay value (default: 50ms)
|
||||
* @param maximum Maximum delay cap (default: 2000ms)
|
||||
*/
|
||||
explicit ExponentialBackoff(
|
||||
duration_type initial = DEFAULT_INITIAL_DELAY,
|
||||
duration_type maximum = DEFAULT_MAX_DELAY)
|
||||
: initial_(initial), maximum_(maximum), current_(initial)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current delay value.
|
||||
*/
|
||||
[[nodiscard]] duration_type
|
||||
current() const noexcept
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Double the current delay, capped at maximum.
|
||||
*
|
||||
* @return The new current delay value after increase.
|
||||
*/
|
||||
duration_type
|
||||
increase() noexcept
|
||||
{
|
||||
current_ = std::min(current_ * 2, maximum_);
|
||||
return current_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset delay to initial value.
|
||||
*
|
||||
* @return The initial delay value.
|
||||
*/
|
||||
duration_type
|
||||
reset() noexcept
|
||||
{
|
||||
current_ = initial_;
|
||||
return current_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get initial delay value.
|
||||
*/
|
||||
[[nodiscard]] duration_type
|
||||
initial() const noexcept
|
||||
{
|
||||
return initial_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get maximum delay value.
|
||||
*/
|
||||
[[nodiscard]] duration_type
|
||||
maximum() const noexcept
|
||||
{
|
||||
return maximum_;
|
||||
}
|
||||
|
||||
private:
|
||||
duration_type const initial_;
|
||||
duration_type const maximum_;
|
||||
duration_type current_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -88,14 +87,14 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
output(std::string_view const& bytes)
|
||||
output(boost::beast::string_view const& bytes)
|
||||
{
|
||||
markStarted();
|
||||
output_(bytes);
|
||||
}
|
||||
|
||||
void
|
||||
stringOutput(std::string_view const& bytes)
|
||||
stringOutput(boost::beast::string_view const& bytes)
|
||||
{
|
||||
markStarted();
|
||||
std::size_t position = 0, writtenUntil = 0;
|
||||
|
||||
56
src/libxrpl/server/FDGuard.cpp
Normal file
56
src/libxrpl/server/FDGuard.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <xrpl/server/FDGuard.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::optional<FDGuard::FDStats>
|
||||
FDGuard::query_fd_stats()
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
return std::nullopt;
|
||||
#else
|
||||
FDStats s;
|
||||
struct rlimit rl;
|
||||
if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY)
|
||||
return std::nullopt;
|
||||
s.limit = static_cast<std::uint64_t>(rl.rlim_cur);
|
||||
#if BOOST_OS_LINUX
|
||||
constexpr char const* kFdDir = "/proc/self/fd";
|
||||
#else
|
||||
constexpr char const* kFdDir = "/dev/fd";
|
||||
#endif
|
||||
if (DIR* d = ::opendir(kFdDir))
|
||||
{
|
||||
std::uint64_t cnt = 0;
|
||||
while (::readdir(d) != nullptr)
|
||||
++cnt;
|
||||
::closedir(d);
|
||||
// readdir counts '.', '..', and the DIR* itself shows in the list
|
||||
s.used = (cnt >= 3) ? (cnt - 3) : 0;
|
||||
return s;
|
||||
}
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
FDGuard::should_throttle(double free_threshold)
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
return false;
|
||||
#else
|
||||
auto const stats = query_fd_stats();
|
||||
if (!stats || stats->limit == 0)
|
||||
return false;
|
||||
|
||||
auto const& s = *stats;
|
||||
auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull;
|
||||
double const free_ratio = static_cast<double>(free) / static_cast<double>(s.limit);
|
||||
if (free_ratio < free_threshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
162
src/test/server/ExponentialBackoff_test.cpp
Normal file
162
src/test/server/ExponentialBackoff_test.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/server/detail/ExponentialBackoff.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ExponentialBackoff_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testDefaultConstruction()
|
||||
{
|
||||
testcase("default construction");
|
||||
|
||||
ExponentialBackoff backoff;
|
||||
|
||||
BEAST_EXPECT(backoff.initial() == ExponentialBackoff::DEFAULT_INITIAL_DELAY);
|
||||
BEAST_EXPECT(backoff.maximum() == ExponentialBackoff::DEFAULT_MAX_DELAY);
|
||||
BEAST_EXPECT(backoff.current() == ExponentialBackoff::DEFAULT_INITIAL_DELAY);
|
||||
}
|
||||
|
||||
void
|
||||
testCustomConstruction()
|
||||
{
|
||||
testcase("custom construction");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ExponentialBackoff backoff{100ms, 5000ms};
|
||||
|
||||
BEAST_EXPECT(backoff.initial() == 100ms);
|
||||
BEAST_EXPECT(backoff.maximum() == 5000ms);
|
||||
BEAST_EXPECT(backoff.current() == 100ms);
|
||||
}
|
||||
|
||||
void
|
||||
testIncreaseDoublesDelay()
|
||||
{
|
||||
testcase("increase doubles delay");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ExponentialBackoff backoff{50ms, 2000ms};
|
||||
|
||||
BEAST_EXPECT(backoff.current() == 50ms);
|
||||
|
||||
auto delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 100ms);
|
||||
BEAST_EXPECT(backoff.current() == 100ms);
|
||||
|
||||
delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 200ms);
|
||||
BEAST_EXPECT(backoff.current() == 200ms);
|
||||
|
||||
delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 400ms);
|
||||
BEAST_EXPECT(backoff.current() == 400ms);
|
||||
|
||||
delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 800ms);
|
||||
BEAST_EXPECT(backoff.current() == 800ms);
|
||||
|
||||
delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 1600ms);
|
||||
BEAST_EXPECT(backoff.current() == 1600ms);
|
||||
}
|
||||
|
||||
void
|
||||
testIncreaseCapsAtMaximum()
|
||||
{
|
||||
testcase("increase caps at maximum");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ExponentialBackoff backoff{50ms, 2000ms};
|
||||
|
||||
// Increase until we hit the cap
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
backoff.increase();
|
||||
}
|
||||
|
||||
// Should be capped at maximum
|
||||
BEAST_EXPECT(backoff.current() == 2000ms);
|
||||
|
||||
// Further increases should not exceed maximum
|
||||
auto delay = backoff.increase();
|
||||
BEAST_EXPECT(delay == 2000ms);
|
||||
BEAST_EXPECT(backoff.current() == 2000ms);
|
||||
}
|
||||
|
||||
void
|
||||
testResetReturnsToInitial()
|
||||
{
|
||||
testcase("reset returns to initial");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ExponentialBackoff backoff{50ms, 2000ms};
|
||||
|
||||
// Increase several times
|
||||
backoff.increase();
|
||||
backoff.increase();
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 400ms);
|
||||
|
||||
// Reset should return to initial
|
||||
auto delay = backoff.reset();
|
||||
BEAST_EXPECT(delay == 50ms);
|
||||
BEAST_EXPECT(backoff.current() == 50ms);
|
||||
}
|
||||
|
||||
void
|
||||
testTypicalDoorUsage()
|
||||
{
|
||||
testcase("typical door usage pattern");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Simulates Door's usage pattern
|
||||
ExponentialBackoff backoff{50ms, 2000ms};
|
||||
|
||||
// First throttle
|
||||
BEAST_EXPECT(backoff.current() == 50ms);
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 100ms);
|
||||
|
||||
// Second throttle
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 200ms);
|
||||
|
||||
// Success - reset
|
||||
backoff.reset();
|
||||
BEAST_EXPECT(backoff.current() == 50ms);
|
||||
|
||||
// Another throttle sequence
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 100ms);
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 200ms);
|
||||
backoff.increase();
|
||||
BEAST_EXPECT(backoff.current() == 400ms);
|
||||
|
||||
// Success - reset
|
||||
backoff.reset();
|
||||
BEAST_EXPECT(backoff.current() == 50ms);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDefaultConstruction();
|
||||
testCustomConstruction();
|
||||
testIncreaseDoublesDelay();
|
||||
testIncreaseCapsAtMaximum();
|
||||
testResetReturnsToInitial();
|
||||
testTypicalDoorUsage();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ExponentialBackoff, server, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -358,6 +358,15 @@ GRPCServerImpl::shutdown()
|
||||
server_->Shutdown();
|
||||
JLOG(journal_.debug()) << "Server has been shutdown";
|
||||
|
||||
// Cancel any pending backoff alarm
|
||||
backoffAlarm_.Cancel();
|
||||
{
|
||||
std::lock_guard lk(backoffMutex_);
|
||||
deferredListeners_.clear();
|
||||
backoffScheduled_ = false;
|
||||
}
|
||||
JLOG(journal_.debug()) << "Backoff alarm cancelled";
|
||||
|
||||
// Always shutdown the completion queue after the server. This call allows
|
||||
// cq_.Next() to return false, once all events posted to the completion
|
||||
// queue have been processed. See handleRpcs() for more details.
|
||||
@@ -387,7 +396,7 @@ GRPCServerImpl::handleRpcs()
|
||||
bool ok;
|
||||
// Block waiting to read the next event from the completion queue. The
|
||||
// event is uniquely identified by its tag, which in this case is the
|
||||
// memory address of a CallData instance.
|
||||
// memory address of a CallData instance or a BackoffTag instance.
|
||||
// The return value of Next should always be checked. This return value
|
||||
// tells us whether there is any kind of event or cq_ is shutting down.
|
||||
// When cq_.Next(...) returns false, all work has been completed and the
|
||||
@@ -401,7 +410,18 @@ GRPCServerImpl::handleRpcs()
|
||||
// loop to exit.
|
||||
while (cq_->Next(&tag, &ok))
|
||||
{
|
||||
auto ptr = static_cast<Processor*>(tag);
|
||||
auto base = static_cast<CQTag*>(tag);
|
||||
|
||||
// Handle backoff alarm events
|
||||
if (base->kind == CQTag::Kind::Backoff)
|
||||
{
|
||||
JLOG(journal_.debug()) << "Backoff alarm fired";
|
||||
onBackoffFired();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle CallData events
|
||||
auto ptr = static_cast<Processor*>(base);
|
||||
JLOG(journal_.trace()) << "Processing CallData object."
|
||||
<< " ptr = " << ptr << " ok = " << ok;
|
||||
|
||||
@@ -416,12 +436,54 @@ GRPCServerImpl::handleRpcs()
|
||||
if (!ptr->isFinished())
|
||||
{
|
||||
JLOG(journal_.debug()) << "Received new request. Processing";
|
||||
// ptr is now processing a request, so create a new CallData
|
||||
// object to handle additional requests
|
||||
auto cloned = ptr->clone();
|
||||
requests.push_back(cloned);
|
||||
// process the request
|
||||
ptr->process();
|
||||
|
||||
// Check FD pressure before creating clone
|
||||
if (FDGuard::should_throttle(0.70))
|
||||
{
|
||||
JLOG(journal_.warn()) << "gRPC FD pressure detected - deferring listener";
|
||||
|
||||
{
|
||||
std::lock_guard lk(backoffMutex_);
|
||||
|
||||
// Find shared_ptr for this raw pointer
|
||||
auto it = std::find_if(
|
||||
requests.begin(),
|
||||
requests.end(),
|
||||
[ptr](std::shared_ptr<Processor>& sPtr) { return sPtr.get() == ptr; });
|
||||
BOOST_ASSERT(it != requests.end());
|
||||
deferredListeners_.push_back(*it);
|
||||
|
||||
if (!backoffScheduled_)
|
||||
{
|
||||
backoffScheduled_ = true;
|
||||
|
||||
auto deadline = std::chrono::system_clock::now() + backoff_.current();
|
||||
|
||||
backoffAlarm_.Set(
|
||||
cq_.get(), deadline, static_cast<void*>(&backoffTag_));
|
||||
|
||||
auto const delay = backoff_.increase();
|
||||
|
||||
JLOG(journal_.warn())
|
||||
<< "Scheduled backoff alarm for " << delay.count() << "ms";
|
||||
}
|
||||
}
|
||||
|
||||
// Process current request
|
||||
ptr->process();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not throttled - reset delay and clone immediately
|
||||
backoff_.reset();
|
||||
|
||||
// ptr is now processing a request, so create a new CallData
|
||||
// object to handle additional requests
|
||||
auto cloned = ptr->clone();
|
||||
requests.push_back(cloned);
|
||||
// process the request
|
||||
ptr->process();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -433,6 +495,57 @@ GRPCServerImpl::handleRpcs()
|
||||
JLOG(journal_.debug()) << "Completion Queue drained";
|
||||
}
|
||||
|
||||
void
|
||||
GRPCServerImpl::onBackoffFired()
|
||||
{
|
||||
std::vector<std::shared_ptr<Processor>> deferred;
|
||||
|
||||
{
|
||||
std::lock_guard lk(backoffMutex_);
|
||||
backoffScheduled_ = false;
|
||||
deferred.swap(deferredListeners_);
|
||||
}
|
||||
|
||||
JLOG(journal_.debug()) << "Processing " << deferred.size() << " deferred listeners";
|
||||
|
||||
if (FDGuard::should_throttle(0.70))
|
||||
{
|
||||
JLOG(journal_.warn()) << "Still under FD pressure - rescheduling backoff";
|
||||
|
||||
std::lock_guard lk(backoffMutex_);
|
||||
|
||||
deferredListeners_.insert(
|
||||
deferredListeners_.end(),
|
||||
std::make_move_iterator(deferred.begin()),
|
||||
std::make_move_iterator(deferred.end()));
|
||||
|
||||
if (!backoffScheduled_)
|
||||
{
|
||||
backoffScheduled_ = true;
|
||||
|
||||
auto deadline = std::chrono::system_clock::now() + backoff_.current();
|
||||
|
||||
backoffAlarm_.Set(cq_.get(), deadline, static_cast<void*>(&backoffTag_));
|
||||
|
||||
auto const delay = backoff_.increase();
|
||||
|
||||
JLOG(journal_.warn()) << "Rescheduled backoff alarm for " << delay.count() << "ms";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Recovery - FD pressure relieved
|
||||
JLOG(journal_.info()) << "FD pressure relieved - resuming normal operation";
|
||||
backoff_.reset();
|
||||
|
||||
for (auto const& ptr : deferred)
|
||||
{
|
||||
auto cloned = ptr->clone();
|
||||
requests_.push_back(cloned);
|
||||
}
|
||||
}
|
||||
|
||||
// create a CallData instance for each RPC
|
||||
std::vector<std::shared_ptr<Processor>>
|
||||
GRPCServerImpl::setupListeners()
|
||||
|
||||
@@ -9,19 +9,36 @@
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/server/FDGuard.h>
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
#include <xrpl/server/detail/ExponentialBackoff.h>
|
||||
|
||||
#include <grpcpp/alarm.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Base class for completion queue tags
|
||||
struct CQTag
|
||||
{
|
||||
enum class Kind { CallData, Backoff };
|
||||
Kind kind;
|
||||
|
||||
explicit CQTag(Kind k) : kind(k)
|
||||
{
|
||||
}
|
||||
virtual ~CQTag() = default;
|
||||
};
|
||||
|
||||
// Interface that CallData implements
|
||||
class Processor
|
||||
class Processor : public CQTag
|
||||
{
|
||||
public:
|
||||
virtual ~Processor() = default;
|
||||
|
||||
Processor() = default;
|
||||
Processor() : CQTag(Kind::CallData)
|
||||
{
|
||||
}
|
||||
|
||||
Processor(Processor const&) = delete;
|
||||
|
||||
@@ -45,6 +62,14 @@ public:
|
||||
isFinished() = 0;
|
||||
};
|
||||
|
||||
// Tag for backoff alarm events
|
||||
struct BackoffTag : public CQTag
|
||||
{
|
||||
BackoffTag() : CQTag(Kind::Backoff)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class GRPCServerImpl final
|
||||
{
|
||||
private:
|
||||
@@ -68,6 +93,14 @@ private:
|
||||
|
||||
beast::Journal journal_;
|
||||
|
||||
// FD throttling and backoff state
|
||||
std::mutex backoffMutex_;
|
||||
bool backoffScheduled_{false};
|
||||
ExponentialBackoff backoff_;
|
||||
BackoffTag backoffTag_;
|
||||
grpc::Alarm backoffAlarm_;
|
||||
std::vector<std::shared_ptr<Processor>> deferredListeners_;
|
||||
|
||||
// typedef for function to bind a listener
|
||||
// This is always of the form:
|
||||
// org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::Request[RPC NAME]
|
||||
@@ -124,6 +157,10 @@ public:
|
||||
getEndpoint() const;
|
||||
|
||||
private:
|
||||
// Handle backoff alarm firing - retry deferred listeners
|
||||
void
|
||||
onBackoffFired();
|
||||
|
||||
// Class encompassing the state and logic needed to serve a request.
|
||||
template <class Request, class Response>
|
||||
class CallData : public Processor,
|
||||
|
||||
@@ -59,7 +59,7 @@ to_string(ProtocolVersion const& p)
|
||||
}
|
||||
|
||||
std::vector<ProtocolVersion>
|
||||
parseProtocolVersions(std::string_view const& value)
|
||||
parseProtocolVersions(boost::beast::string_view const& value)
|
||||
{
|
||||
static boost::regex re(
|
||||
"^" // start of line
|
||||
@@ -130,7 +130,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions)
|
||||
}
|
||||
|
||||
std::optional<ProtocolVersion>
|
||||
negotiateProtocolVersion(std::string_view const& versions)
|
||||
negotiateProtocolVersion(boost::beast::string_view const& versions)
|
||||
{
|
||||
auto const them = parseProtocolVersions(versions);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -38,7 +39,7 @@ to_string(ProtocolVersion const& p);
|
||||
no duplicates and will be sorted in ascending protocol order.
|
||||
*/
|
||||
std::vector<ProtocolVersion>
|
||||
parseProtocolVersions(std::string_view const& s);
|
||||
parseProtocolVersions(boost::beast::string_view const& s);
|
||||
|
||||
/** Given a list of supported protocol versions, choose the one we prefer. */
|
||||
std::optional<ProtocolVersion>
|
||||
@@ -46,7 +47,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions);
|
||||
|
||||
/** Given a list of supported protocol versions, choose the one we prefer. */
|
||||
std::optional<ProtocolVersion>
|
||||
negotiateProtocolVersion(std::string_view const& versions);
|
||||
negotiateProtocolVersion(boost::beast::string_view const& versions);
|
||||
|
||||
/** The list of all the protocol versions we support. */
|
||||
std::string const&
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -231,7 +230,7 @@ ServerHandler::onHandoff(
|
||||
static inline Json::Output
|
||||
makeOutput(Session& session)
|
||||
{
|
||||
return [&](std::string_view const& b) { session.write(b.data(), b.size()); };
|
||||
return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
|
||||
}
|
||||
|
||||
static std::map<std::string, std::string>
|
||||
@@ -528,14 +527,11 @@ ServerHandler::processSession(
|
||||
makeOutput(*session),
|
||||
coro,
|
||||
forwardedFor(session->request()),
|
||||
[&]() -> std::string_view {
|
||||
[&] {
|
||||
auto const iter = session->request().find("X-User");
|
||||
if (iter != session->request().end())
|
||||
{
|
||||
auto const val = iter->value();
|
||||
return std::string_view(val.data(), val.size());
|
||||
}
|
||||
return std::string_view{};
|
||||
return iter->value();
|
||||
return boost::beast::string_view{};
|
||||
}());
|
||||
|
||||
if (beast::rfc2616::is_keep_alive(session->request()))
|
||||
|
||||
Reference in New Issue
Block a user