Add assertion that terminate clio (#994)

Fixes #893.

Also added termination handler to print backtrace on crash, so fixes #929.
This commit is contained in:
Sergey Kuznetsov
2023-11-21 13:06:04 +00:00
committed by GitHub
parent 3bab90ca7a
commit 8ebe2d6a80
41 changed files with 420 additions and 158 deletions

View File

@@ -0,0 +1,3 @@
target_compile_definitions(clio PUBLIC BOOST_STACKTRACE_LINK)
target_compile_definitions(clio PUBLIC BOOST_STACKTRACE_USE_BACKTRACE)
find_package(libbacktrace REQUIRED)

View File

@@ -44,6 +44,7 @@ include (CMake/deps/OpenSSL.cmake)
include (CMake/deps/Threads.cmake)
include (CMake/deps/libfmt.cmake)
include (CMake/deps/cassandra.cmake)
include (CMake/deps/libbacktrace.cmake)
# TODO: Include directory will be wrong when installed.
target_include_directories (clio PUBLIC src)
@@ -56,11 +57,14 @@ target_link_libraries (clio
PUBLIC Boost::system
PUBLIC Boost::log
PUBLIC Boost::log_setup
PUBLIC Boost::stacktrace_backtrace
PUBLIC cassandra-cpp-driver::cassandra-cpp-driver
PUBLIC fmt::fmt
PUBLIC OpenSSL::Crypto
PUBLIC OpenSSL::SSL
PUBLIC xrpl::libxrpl
PUBLIC dl
PUBLIC libbacktrace::libbacktrace
INTERFACE Threads::Threads
)
@@ -143,7 +147,9 @@ target_sources (clio PRIVATE
src/util/prometheus/Metrics.cpp
src/util/prometheus/Prometheus.cpp
src/util/Random.cpp
src/util/Taggable.cpp)
src/util/Taggable.cpp
src/util/TerminationHandler.cpp
)
# Clio server
add_executable (clio_server src/main/Main.cpp)
@@ -167,6 +173,7 @@ if (tests)
unittests/DOSGuardTests.cpp
unittests/SubscriptionTests.cpp
unittests/SubscriptionManagerTests.cpp
unittests/util/AssertTests.cpp
unittests/util/TestObject.cpp
unittests/util/StringUtils.cpp
unittests/util/prometheus/CounterTests.cpp

View File

@@ -1,6 +1,6 @@
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re
class Clio(ConanFile):
name = 'clio'
@@ -11,7 +11,7 @@ class Clio(ConanFile):
settings = 'os', 'compiler', 'build_type', 'arch'
options = {
'fPIC': [True, False],
'verbose': [True, False],
'verbose': [True, False],
'tests': [True, False], # build unit tests; create `clio_tests` binary
'docs': [True, False], # doxygen API docs; create custom target 'docs'
'packaging': [True, False], # create distribution packages
@@ -27,6 +27,7 @@ class Clio(ConanFile):
'grpc/1.50.1',
'openssl/1.1.1u',
'xrpl/2.0.0-rc1',
'libbacktrace/cci.20210118'
]
default_options = {

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <data/BackendCounters.h>
#include <util/Assert.h>
#include <util/prometheus/Prometheus.h>
@@ -161,7 +162,10 @@ BackendCounters::AsyncOperationCounters::registerStarted(std::uint64_t const cou
void
BackendCounters::AsyncOperationCounters::registerFinished(std::uint64_t const count)
{
assert(pendingCounter_.get().value() >= static_cast<std::int64_t>(count));
ASSERT(
pendingCounter_.get().value() >= static_cast<std::int64_t>(count),
"Finished operations can't be more than pending"
);
pendingCounter_.get() -= count;
completedCounter_.get() += count;
}
@@ -175,7 +179,9 @@ BackendCounters::AsyncOperationCounters::registerRetry(std::uint64_t count)
void
BackendCounters::AsyncOperationCounters::registerError(std::uint64_t count)
{
assert(pendingCounter_.get().value() >= static_cast<std::int64_t>(count));
ASSERT(
pendingCounter_.get().value() >= static_cast<std::int64_t>(count), "Error operations can't be more than pending"
);
pendingCounter_.get() -= count;
errorCounter_.get() += count;
}

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <data/BackendInterface.h>
#include <util/Assert.h>
#include <util/log/Logger.h>
#include <ripple/protocol/Indexes.h>
@@ -43,7 +44,7 @@ BackendInterface::finishWrites(std::uint32_t const ledgerSequence)
void
BackendInterface::writeLedgerObject(std::string&& key, std::uint32_t const seq, std::string&& blob)
{
assert(key.size() == sizeof(ripple::uint256));
ASSERT(key.size() == sizeof(ripple::uint256), "Key must be 256 bits");
doWriteLedgerObject(std::move(key), seq, std::move(blob));
}
@@ -188,7 +189,7 @@ BackendInterface::fetchBookOffers(
}
auto nextKey = ripple::keylet::page(uTipIndex, next);
auto nextDir = fetchLedgerObject(nextKey.key, ledgerSequence, yield);
assert(nextDir);
ASSERT(nextDir.has_value(), "Next dir must exist");
offerDir->blob = *nextDir;
offerDir->key = nextKey.key;
}
@@ -200,7 +201,7 @@ BackendInterface::fetchBookOffers(
for (size_t i = 0; i < keys.size() && i < limit; ++i) {
LOG(gLog.trace()) << "Key = " << ripple::strHex(keys[i]) << " blob = " << ripple::strHex(objs[i])
<< " ledgerSequence = " << ledgerSequence;
assert(!objs[i].empty());
ASSERT(!objs[i].empty(), "Ledger object can't be empty");
page.offers.push_back({keys[i], objs[i]});
}
auto end = std::chrono::system_clock::now();
@@ -234,7 +235,14 @@ void
BackendInterface::updateRange(uint32_t newMax)
{
std::scoped_lock const lck(rngMtx_);
assert(!range || newMax >= range->maxSequence);
ASSERT(
!range || newMax >= range->maxSequence,
"Range shouldn't exist yet or newMax should be greater. newMax = {}, range->maxSequence = {}",
newMax,
range->maxSequence
);
if (!range) {
range = {newMax, newMax};
} else {

View File

@@ -25,6 +25,7 @@
#include <data/cassandra/Schema.h>
#include <data/cassandra/SettingsProvider.h>
#include <data/cassandra/impl/ExecutionStrategy.h>
#include <util/Assert.h>
#include <util/LedgerUtils.h>
#include <util/Profiler.h>
#include <util/log/Logger.h>
@@ -622,7 +623,7 @@ public:
);
});
assert(numHashes == results.size());
ASSERT(numHashes == results.size(), "Number of hashes and results must match");
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from Cassandra in " << timeDiff
<< " milliseconds";
return results;
@@ -735,8 +736,8 @@ public:
{
LOG(log_.trace()) << "Writing successor. key = " << key.size() << " bytes. "
<< " seq = " << std::to_string(seq) << " successor = " << successor.size() << " bytes.";
assert(!key.empty());
assert(!successor.empty());
ASSERT(!key.empty(), "Key must not be empty");
ASSERT(!successor.empty(), "Successor must not be empty");
executor_.write(schema_->insertSuccessor, std::move(key), seq, std::move(successor));
}

View File

@@ -20,6 +20,8 @@
/** @file */
#pragma once
#include <util/Assert.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/SField.h>
@@ -233,7 +235,7 @@ getBookBase(T const& key)
{
static constexpr size_t KEY_SIZE = 24;
assert(key.size() == ripple::uint256::size());
ASSERT(key.size() == ripple::uint256::size(), "Invalid key size {}", key.size());
ripple::uint256 ret;
for (size_t i = 0; i < KEY_SIZE; ++i)

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <data/LedgerCache.h>
#include <util/Assert.h>
namespace data {
@@ -37,7 +38,12 @@ LedgerCache::update(std::vector<LedgerObject> const& objs, uint32_t seq, bool is
{
std::scoped_lock const lck{mtx_};
if (seq > latestSeq_) {
assert(seq == latestSeq_ + 1 || latestSeq_ == 0);
ASSERT(
seq == latestSeq_ + 1 || latestSeq_ == 0,
"New sequense must be either next or first. seq = {}, latestSeq_ = {}",
seq,
latestSeq_
);
latestSeq_ = seq;
}
for (auto const& obj : objs) {

View File

@@ -24,6 +24,7 @@
#include <data/cassandra/Handle.h>
#include <data/cassandra/Types.h>
#include <data/cassandra/impl/AsyncExecutor.h>
#include <util/Assert.h>
#include <util/Expected.h>
#include <util/log/Logger.h>
@@ -400,7 +401,7 @@ public:
numReadRequestsOutstanding_ -= statements.size();
if (errorsCount > 0) {
assert(errorsCount <= statements.size());
ASSERT(errorsCount <= statements.size(), "Errors number cannot exceed statements number");
counters_->registerReadError(errorsCount);
counters_->registerReadFinished(statements.size() - errorsCount);
throw DatabaseTimeout{};
@@ -422,8 +423,18 @@ public:
}
);
assert(futures.size() == statements.size());
assert(results.size() == statements.size());
ASSERT(
futures.size() == statements.size(),
"Futures size must be equal to statements size. Got {} and {}",
futures.size(),
statements.size()
);
ASSERT(
results.size() == statements.size(),
"Results size must be equal to statements size. Got {} and {}",
results.size(),
statements.size()
);
return results;
}
@@ -455,10 +466,7 @@ private:
decrementOutstandingRequestCount()
{
// sanity check
if (numWriteRequestsOutstanding_ == 0) {
assert(false);
throw std::runtime_error("decrementing num outstanding below 0");
}
ASSERT(numWriteRequestsOutstanding_ > 0, "Decrementing num outstanding below 0");
size_t const cur = (--numWriteRequestsOutstanding_);
{
// mutex lock required to prevent race condition around spurious

View File

@@ -26,6 +26,7 @@
#include <optional>
#include <queue>
#include <sstream>
#include <util/Assert.h>
namespace etl {
/**
@@ -209,7 +210,7 @@ public:
inline std::vector<ripple::uint256>
getMarkers(size_t numMarkers)
{
assert(numMarkers <= 256);
ASSERT(numMarkers <= 256, "Number of markers must be <= 256. Got: {}", numMarkers);
unsigned char const incr = 256 / numMarkers;
@@ -222,4 +223,4 @@ getMarkers(size_t numMarkers)
}
return markers;
}
} // namespace etl
} // namespace etl

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <etl/ETLService.h>
#include <util/Assert.h>
#include <util/Constants.h>
#include <ripple/protocol/LedgerHeader.h>
@@ -125,7 +126,7 @@ ETLService::monitor()
cacheLoader_.load(rng->maxSequence);
}
assert(rng);
ASSERT(rng.has_value(), "Ledger range can't be null");
uint32_t nextSequence = rng->maxSequence + 1;
LOG(log_.debug()) << "Database is populated. "

View File

@@ -23,6 +23,7 @@
#include <etl/ProbingSource.h>
#include <etl/Source.h>
#include <rpc/RPCHelpers.h>
#include <util/Assert.h>
#include <util/Profiler.h>
#include <util/Random.h>
#include <util/log/Logger.h>
@@ -206,7 +207,7 @@ bool
LoadBalancer::shouldPropagateTxnStream(Source* in) const
{
for (auto& src : sources_) {
assert(src);
ASSERT(src != nullptr, "Source is nullptr");
// We pick the first Source encountered that is connected
if (src->isConnected())

View File

@@ -794,7 +794,7 @@ private:
uint32_t const sequence = std::stoll(minAndMax[0]);
pairs.emplace_back(sequence, sequence);
} else {
assert(minAndMax.size() == 2);
ASSERT(minAndMax.size() == 2, "Min and max should be of size 2. Got size = {}", minAndMax.size());
uint32_t const min = std::stoll(minAndMax[0]);
uint32_t const max = std::stoll(minAndMax[1]);
pairs.emplace_back(min, max);

View File

@@ -21,6 +21,7 @@
#include <data/BackendInterface.h>
#include <etl/NFTHelpers.h>
#include <util/Assert.h>
#include <util/log/Logger.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
@@ -60,7 +61,12 @@ public:
<< " . prefix = " << ripple::strHex(std::string(1, prefix))
<< " . nextPrefix_ = " << ripple::strHex(std::string(1, nextPrefix_));
assert(nextPrefix_ > prefix || nextPrefix_ == 0x00);
ASSERT(
nextPrefix_ > prefix || nextPrefix_ == 0x00,
"Next prefix must be greater than current prefix. Got: nextPrefix_ = {}, prefix = {}",
nextPrefix_,
prefix
);
cur_ = std::make_unique<org::xrpl::rpc::v1::GetLedgerDataResponse>();
next_ = std::make_unique<org::xrpl::rpc::v1::GetLedgerDataResponse>();

View File

@@ -134,10 +134,7 @@ public:
return;
}
if (cache_.get().isFull()) {
assert(false);
return;
}
ASSERT(!cache_.get().isFull(), "Cache must not be full. seq = {}", seq);
if (!clioPeers_.empty()) {
boost::asio::spawn(ioContext_.get(), [this, seq](boost::asio::yield_context yield) {

View File

@@ -20,6 +20,7 @@
#pragma once
#include <etl/SystemState.h>
#include <util/Assert.h>
#include <util/Profiler.h>
#include <util/log/Logger.h>
@@ -76,7 +77,7 @@ public:
void
waitTillFinished()
{
assert(thread_.joinable());
ASSERT(thread_.joinable(), "Extractor thread must be joinable");
thread_.join();
}

View File

@@ -23,6 +23,7 @@
#include <etl/NFTHelpers.h>
#include <etl/SystemState.h>
#include <etl/impl/LedgerFetcher.h>
#include <util/Assert.h>
#include <util/LedgerUtils.h>
#include <util/Profiler.h>
#include <util/log/Logger.h>
@@ -152,8 +153,7 @@ public:
// check that database is actually empty
auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (rng) {
LOG(log_.fatal()) << "Database is not empty";
assert(false);
ASSERT(false, "Database is not empty");
return {};
}
@@ -189,50 +189,48 @@ public:
size_t numWrites = 0;
backend_->cache().setFull();
auto seconds =
::util::timed<std::chrono::seconds>([this, edgeKeys = &edgeKeys, sequence, &numWrites]() {
for (auto& key : *edgeKeys) {
LOG(log_.debug()) << "Writing edge key = " << ripple::strHex(key);
auto succ =
backend_->cache().getSuccessor(*ripple::uint256::fromVoidChecked(key), sequence);
if (succ)
backend_->writeSuccessor(std::move(key), sequence, uint256ToString(succ->key));
}
auto seconds = ::util::timed<std::chrono::seconds>([this, edgeKeys = &edgeKeys, sequence, &numWrites] {
for (auto& key : *edgeKeys) {
LOG(log_.debug()) << "Writing edge key = " << ripple::strHex(key);
auto succ = backend_->cache().getSuccessor(*ripple::uint256::fromVoidChecked(key), sequence);
if (succ)
backend_->writeSuccessor(std::move(key), sequence, uint256ToString(succ->key));
}
ripple::uint256 prev = data::firstKey;
while (auto cur = backend_->cache().getSuccessor(prev, sequence)) {
assert(cur);
if (prev == data::firstKey)
backend_->writeSuccessor(uint256ToString(prev), sequence, uint256ToString(cur->key));
ripple::uint256 prev = data::firstKey;
while (auto cur = backend_->cache().getSuccessor(prev, sequence)) {
ASSERT(cur.has_value(), "Succesor for key {} must exist", ripple::strHex(prev));
if (prev == data::firstKey)
backend_->writeSuccessor(uint256ToString(prev), sequence, uint256ToString(cur->key));
if (isBookDir(cur->key, cur->blob)) {
auto base = getBookBase(cur->key);
// make sure the base is not an actual object
if (!backend_->cache().get(cur->key, sequence)) {
auto succ = backend_->cache().getSuccessor(base, sequence);
assert(succ);
if (succ->key == cur->key) {
LOG(log_.debug()) << "Writing book successor = " << ripple::strHex(base)
<< " - " << ripple::strHex(cur->key);
if (isBookDir(cur->key, cur->blob)) {
auto base = getBookBase(cur->key);
// make sure the base is not an actual object
if (!backend_->cache().get(cur->key, sequence)) {
auto succ = backend_->cache().getSuccessor(base, sequence);
ASSERT(succ.has_value(), "Book base {} must have a successor", ripple::strHex(base));
if (succ->key == cur->key) {
LOG(log_.debug()) << "Writing book successor = " << ripple::strHex(base) << " - "
<< ripple::strHex(cur->key);
backend_->writeSuccessor(
uint256ToString(base), sequence, uint256ToString(cur->key)
);
}
backend_->writeSuccessor(
uint256ToString(base), sequence, uint256ToString(cur->key)
);
}
++numWrites;
}
prev = cur->key;
static constexpr std::size_t LOG_INTERVAL = 100000;
if (numWrites % LOG_INTERVAL == 0 && numWrites != 0)
LOG(log_.info()) << "Wrote " << numWrites << " book successors";
++numWrites;
}
backend_->writeSuccessor(uint256ToString(prev), sequence, uint256ToString(data::lastKey));
++numWrites;
});
prev = cur->key;
static constexpr std::size_t LOG_INTERVAL = 100000;
if (numWrites % LOG_INTERVAL == 0 && numWrites != 0)
LOG(log_.info()) << "Wrote " << numWrites << " book successors";
}
backend_->writeSuccessor(uint256ToString(prev), sequence, uint256ToString(data::lastKey));
++numWrites;
});
LOG(log_.info()) << "Looping through cache and submitting all writes took " << seconds
<< " seconds. numWrites = " << std::to_string(numWrites);

View File

@@ -22,6 +22,7 @@
#include <data/BackendInterface.h>
#include <etl/SystemState.h>
#include <feed/SubscriptionManager.h>
#include <util/Assert.h>
#include <util/LedgerUtils.h>
#include <util/log/Logger.h>
@@ -115,7 +116,7 @@ public:
return backend_->fetchLedgerBySequence(ledgerSequence, yield);
});
assert(lgr);
ASSERT(lgr.has_value(), "Ledger must exist in database. Ledger sequence = {}", ledgerSequence);
publish(*lgr);
return true;
@@ -157,7 +158,7 @@ public:
std::optional<ripple::Fees> fees = data::synchronousAndRetryOnTimeout([&](auto yield) {
return backend_->fetchFees(lgrInfo.seq, yield);
});
assert(fees);
ASSERT(fees.has_value(), "Fees must exist for ledger {}", lgrInfo.seq);
std::vector<data::TransactionAndMetadata> transactions =
data::synchronousAndRetryOnTimeout([&](auto yield) {
@@ -165,7 +166,7 @@ public:
});
auto const ledgerRange = backend_->fetchLedgerRange();
assert(ledgerRange);
ASSERT(ledgerRange.has_value(), "Ledger range must exist");
std::string const range =
std::to_string(ledgerRange->minSequence) + "-" + std::to_string(ledgerRange->maxSequence);

View File

@@ -23,6 +23,7 @@
#include <etl/SystemState.h>
#include <etl/impl/AmendmentBlock.h>
#include <etl/impl/LedgerLoader.h>
#include <util/Assert.h>
#include <util/LedgerUtils.h>
#include <util/Profiler.h>
#include <util/log/Logger.h>
@@ -113,7 +114,7 @@ public:
void
waitTillFinished()
{
assert(thread_.joinable());
ASSERT(thread_.joinable(), "Transformer thread must be joinable");
thread_.join();
}
@@ -228,7 +229,7 @@ private:
for (auto& obj : *(rawData.mutable_ledger_objects()->mutable_objects())) {
auto key = ripple::uint256::fromVoidChecked(obj.key());
assert(key);
ASSERT(key.has_value(), "Failed to deserialize key from void");
cacheUpdates.push_back({*key, {obj.mutable_data()->begin(), obj.mutable_data()->end()}});
LOG(log_.debug()) << "key = " << ripple::strHex(*key) << " - mod type = " << obj.mod_type();
@@ -245,7 +246,7 @@ private:
if (isDeleted) {
auto const old = backend_->cache().get(*key, lgrInfo.seq - 1);
assert(old);
ASSERT(old.has_value(), "Deleted object must be in cache");
checkBookBase = isBookDir(*key, *old);
} else {
checkBookBase = isBookDir(*key, *blob);
@@ -256,7 +257,11 @@ private:
auto const bookBase = getBookBase(*key);
auto const oldFirstDir = backend_->cache().getSuccessor(bookBase, lgrInfo.seq - 1);
assert(oldFirstDir);
ASSERT(
oldFirstDir.has_value(),
"Book base must have a successor for lgrInfo.seq - 1 = {}",
lgrInfo.seq - 1
);
// We deleted the first directory, or we added a directory prior to the old first
// directory

View File

@@ -20,6 +20,7 @@
#include <feed/SubscriptionManager.h>
#include <rpc/BookChangesHelper.h>
#include <rpc/RPCHelpers.h>
#include <util/Assert.h>
namespace feed {
@@ -77,13 +78,13 @@ SubscriptionManager::subLedger(boost::asio::yield_context yield, SessionPtrType
subscribeHelper(session, ledgerSubscribers_, [this](SessionPtrType session) { unsubLedger(session); });
auto ledgerRange = backend_->fetchLedgerRange();
assert(ledgerRange);
ASSERT(ledgerRange.has_value(), "Ledger range must be valid");
auto lgrInfo = backend_->fetchLedgerBySequence(ledgerRange->maxSequence, yield);
assert(lgrInfo);
ASSERT(lgrInfo.has_value(), "Ledger must be valid");
std::optional<ripple::Fees> fees;
fees = backend_->fetchFees(lgrInfo->seq, yield);
assert(fees);
ASSERT(fees.has_value(), "Fees must be valid");
std::string const range = std::to_string(ledgerRange->minSequence) + "-" + std::to_string(ledgerRange->maxSequence);

View File

@@ -24,6 +24,7 @@
#include <rpc/Counters.h>
#include <rpc/RPCEngine.h>
#include <rpc/common/impl/HandlerProvider.h>
#include <util/TerminationHandler.h>
#include <util/config/Config.h>
#include <util/prometheus/Prometheus.h>
#include <web/RPCServerHandler.h>
@@ -147,6 +148,7 @@ start(io_context& ioc, std::uint32_t numThreads)
int
main(int argc, char* argv[])
try {
util::setTerminationHandler();
auto const configPath = parseCli(argc, argv);
auto const config = ConfigReader::open(configPath);
if (!config) {
@@ -218,4 +220,8 @@ try {
return EXIT_SUCCESS;
} catch (std::exception const& e) {
LOG(LogService::fatal()) << "Exit on exception: " << e.what();
return EXIT_FAILURE;
} catch (...) {
LOG(LogService::fatal()) << "Exit on exception: unknown";
return EXIT_FAILURE;
}

View File

@@ -23,6 +23,7 @@
#include <rpc/RPCHelpers.h>
#include <rpc/common/Types.h>
#include <rpc/common/Validators.h>
#include <util/Assert.h>
namespace feed {
class SubscriptionManager;
@@ -129,7 +130,7 @@ private:
} else if (stream == "book_changes") {
subscriptions_->unsubBookChanges(session);
} else {
assert(false);
ASSERT(false, "Unknown stream: {}", stream);
}
}
}

53
src/util/Assert.h Normal file
View File

@@ -0,0 +1,53 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/SourceLocation.h>
#include <boost/stacktrace.hpp>
#include <fmt/format.h>
#include <fmt/ostream.h>
template <>
struct fmt::formatter<boost::stacktrace::stacktrace> : ostream_formatter {};
namespace util {
template <typename... Args>
constexpr void
assertImpl(
SourceLocationType const location,
char const* expression,
bool const condition,
fmt::format_string<Args...> format,
Args&&... args
)
{
if (!condition) {
fmt::println(stderr, "Assertion '{}' failed at {}:{}:", expression, location.file_name(), location.line());
fmt::println(stderr, format, std::forward<Args>(args)...);
fmt::println(stderr, "Stacktrace:\n{}\n", boost::stacktrace::stacktrace());
std::abort();
}
}
} // namespace util
#define ASSERT(condition, ...) util::assertImpl(CURRENT_SRC_LOCATION, #condition, (condition), __VA_ARGS__)

View File

@@ -18,6 +18,8 @@
//==============================================================================
#pragma once
#include <util/Assert.h>
#include <random>
namespace util {
@@ -28,7 +30,7 @@ public:
static T
uniform(T min, T max)
{
assert(min <= max);
ASSERT(min <= max, "Min cannot be greater than max. min: {}, max: {}", min, max);
if constexpr (std::is_floating_point_v<T>) {
std::uniform_real_distribution<T> distribution(min, max);
return distribution(generator_);

78
src/util/SourceLocation.h Normal file
View File

@@ -0,0 +1,78 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
// this is used by fully compatible compilers like gcc
#include <source_location>
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
// this is used by clang on linux where source_location is still not out of
// experimental headers
#include <experimental/source_location>
#endif
#include <optional>
#include <string>
namespace util {
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
using SourceLocationType = std::source_location;
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
using SourceLocationType = std::experimental::source_location;
#else
// A workaround for AppleClang that is lacking source_location atm.
// TODO: remove this workaround when all compilers catch up to c++20
class SourceLocation {
char const* file_;
std::size_t line_;
public:
constexpr SourceLocation(char const* file, std::size_t line) : file_{file}, line_{line}
{
}
constexpr std::string_view
file_name() const
{
return file_;
}
constexpr std::size_t
line() const
{
return line_;
}
};
using SourceLocationType = SourceLocation;
#define SOURCE_LOCATION_OLD_API
#endif
} // namespace util
#if defined(SOURCE_LOCATION_OLD_API)
#define CURRENT_SRC_LOCATION util::SourceLocationType(__FILE__, __LINE__)
#else
#define CURRENT_SRC_LOCATION util::SourceLocationType::current()
#endif

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <util/TerminationHandler.h>
#include <util/log/Logger.h>
#include <boost/stacktrace.hpp>
#include <iostream>
namespace util {
namespace {
void
terminationHandler()
{
try {
LOG(LogService::fatal()) << "Exit on terminate. Backtrace:\n" << boost::stacktrace::stacktrace();
} catch (...) {
LOG(LogService::fatal()) << "Exit on terminate. Can't get backtrace.";
}
std::abort();
}
} // namespace
void
setTerminationHandler()
{
std::set_terminate(terminationHandler);
}
} // namespace util

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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
namespace util {
void
setTerminationHandler();
} // namespace util

View File

@@ -19,6 +19,8 @@
#pragma once
#include <util/SourceLocation.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/json.hpp>
@@ -35,58 +37,10 @@
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
// this is used by fully compatible compilers like gcc
#include <source_location>
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
// this is used by clang on linux where source_location is still not out of
// experimental headers
#include <experimental/source_location>
#endif
#include <optional>
#include <string>
namespace util {
class Config;
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
using SourceLocationType = std::source_location;
#define CURRENT_SRC_LOCATION SourceLocationType::current()
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
using SourceLocationType = std::experimental::source_location;
#define CURRENT_SRC_LOCATION SourceLocationType::current()
#else
// A workaround for AppleClang that is lacking source_location atm.
// TODO: remove this workaround when all compilers catch up to c++20
class SourceLocation {
std::string_view file_;
std::size_t line_;
public:
SourceLocation(std::string_view file, std::size_t line) : file_{file}, line_{line}
{
}
std::string_view
file_name() const
{
return file_;
}
std::size_t
line() const
{
return line_;
}
};
using SourceLocationType = SourceLocation;
#define CURRENT_SRC_LOCATION SourceLocationType(__builtin_FILE(), __builtin_LINE())
#endif
/**
* @brief Skips evaluation of expensive argument lists if the given logger is disabled for the required severity level.
*

View File

@@ -19,6 +19,7 @@
#pragma once
#include <util/Assert.h>
#include <util/prometheus/Metrics.h>
#include <util/prometheus/impl/AnyCounterBase.h>
@@ -64,7 +65,7 @@ struct AnyCounter : MetricBase, detail::AnyCounterBase<NumberType> {
AnyCounter&
operator+=(ValueType const value)
{
assert(value >= 0);
ASSERT(value >= 0, "Cannot decrease a counter");
this->pimpl_->add(value);
return *this;
}

View File

@@ -20,8 +20,6 @@
#include <util/prometheus/Counter.h>
#include <util/prometheus/Gauge.h>
#include <cassert>
namespace util::prometheus {
MetricBase::MetricBase(std::string name, std::string labelsString)
@@ -52,7 +50,7 @@ toString(MetricType type)
case MetricType::SUMMARY:
return "summary";
default:
assert(false);
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return "";
}
@@ -85,7 +83,7 @@ MetricsFamily::MetricBuilder MetricsFamily::defaultMetricBuilder =
case MetricType::HISTOGRAM:
[[fallthrough]];
default:
assert(false);
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return nullptr;
};

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <util/Assert.h>
#include <util/prometheus/Prometheus.h>
namespace util::prometheus {
@@ -28,9 +29,7 @@ MetricType&
convertBaseTo(MetricBase& metricBase)
{
auto result = dynamic_cast<MetricType*>(&metricBase);
assert(result != nullptr);
if (result == nullptr)
throw std::runtime_error("Failed to convert metric type");
ASSERT(result != nullptr, "Failed to cast metric {} to the requested type", metricBase.name());
return *result;
}
@@ -162,7 +161,7 @@ PrometheusService::replaceInstance(std::unique_ptr<util::prometheus::PrometheusI
util::prometheus::PrometheusInterface&
PrometheusService::instance()
{
assert(instance_);
ASSERT(instance_ != nullptr, "PrometheusService::instance() called before init()");
return *instance_;
}

View File

@@ -23,8 +23,6 @@
#include <util/prometheus/Counter.h>
#include <util/prometheus/Gauge.h>
#include <cassert>
namespace util::prometheus {
class PrometheusInterface {

View File

@@ -19,8 +19,9 @@
#pragma once
#include <util/Assert.h>
#include <atomic>
#include <cassert>
#include <concepts>
namespace util::prometheus::detail {
@@ -55,7 +56,7 @@ public:
// Move constructor should be used only used during initialization
CounterImpl(CounterImpl&& other)
{
assert(other.value_ == 0);
ASSERT(other.value_ == 0, "Move constructor should only be used during initialization");
value_ = other.value_.exchange(0);
}

View File

@@ -19,6 +19,7 @@
#pragma once
#include <util/Assert.h>
#include <util/config/Config.h>
#include <util/log/Logger.h>
#include <web/IntervalSweepHandler.h>
@@ -172,7 +173,7 @@ public:
if (whitelistHandler_.get().isWhiteListed(ip))
return;
std::scoped_lock const lck{mtx_};
assert(ipConnCount_[ip] > 0);
ASSERT(ipConnCount_[ip] > 0, "Connection count for ip {} can't be 0", ip);
ipConnCount_[ip]--;
if (ipConnCount_[ip] == 0)
ipConnCount_.erase(ip);

View File

@@ -19,6 +19,7 @@
#include <web/IntervalSweepHandler.h>
#include <util/Assert.h>
#include <util/Constants.h>
#include <web/DOSGuard.h>
@@ -47,9 +48,9 @@ IntervalSweepHandler::~IntervalSweepHandler()
void
IntervalSweepHandler::setup(web::BaseDOSGuard* guard)
{
assert(dosGuard_ == nullptr);
ASSERT(dosGuard_ == nullptr, "Cannot setup DOS guard more than once");
dosGuard_ = guard;
assert(dosGuard_ != nullptr);
ASSERT(dosGuard_ != nullptr, "DOS guard must be not null");
createTimer();
}

View File

@@ -20,6 +20,7 @@
#pragma once
#include <rpc/Errors.h>
#include <util/Assert.h>
#include <web/interface/ConnectionBase.h>
#include <boost/beast/http.hpp>
@@ -82,7 +83,9 @@ public:
case rpc::ClioError::rpcMALFORMED_ADDRESS:
case rpc::ClioError::rpcINVALID_HOT_WALLET:
case rpc::ClioError::rpcFIELD_NOT_FOUND_TRANSACTION:
assert(false); // this should never happen
ASSERT(
false, "Unknown rpc error code {}", static_cast<int>(*clioCode)
); // this should never happen
break;
}
} else {

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include <util/Assert.h>
#include <util/Fixtures.h>
#include <util/config/Config.h>
@@ -192,7 +193,7 @@ struct Custom {
friend Custom
tag_invoke(json::value_to_tag<Custom>, json::value const& value)
{
assert(value.is_object());
ASSERT(value.is_object(), "Value must be an object");
auto const& obj = value.as_object();
return {obj.at("str").as_string().c_str(), obj.at("int").as_int64(), obj.at("bool").as_bool()};
}

View File

@@ -16,6 +16,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <util/TerminationHandler.h>
#include <util/prometheus/Prometheus.h>
#include <gtest/gtest.h>
@@ -23,6 +25,7 @@
int
main(int argc, char** argv)
{
util::setTerminationHandler();
PrometheusService::init();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View File

@@ -225,6 +225,7 @@ TEST_F(BackendCountersMockPrometheusTest, registerWriteFinished)
makeMock<GaugeInt>("backend_operations_current_number", "{operation=\"write_async\",status=\"pending\"}");
auto& completedCounter =
makeMock<CounterInt>("backend_operations_total_number", "{operation=\"write_async\",status=\"completed\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(1));
EXPECT_CALL(pendingCounter, add(-1));
EXPECT_CALL(completedCounter, add(1));
counters->registerWriteFinished();
@@ -252,6 +253,7 @@ TEST_F(BackendCountersMockPrometheusTest, registerReadFinished)
makeMock<GaugeInt>("backend_operations_current_number", "{operation=\"read_async\",status=\"pending\"}");
auto& completedCounter =
makeMock<CounterInt>("backend_operations_total_number", "{operation=\"read_async\",status=\"completed\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(1));
EXPECT_CALL(pendingCounter, add(-1));
EXPECT_CALL(completedCounter, add(1));
counters->registerReadFinished();
@@ -271,6 +273,7 @@ TEST_F(BackendCountersMockPrometheusTest, registerReadError)
makeMock<GaugeInt>("backend_operations_current_number", "{operation=\"read_async\",status=\"pending\"}");
auto& errorCounter =
makeMock<CounterInt>("backend_operations_total_number", "{operation=\"read_async\",status=\"error\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(1));
EXPECT_CALL(pendingCounter, add(-1));
EXPECT_CALL(errorCounter, add(1));
counters->registerReadError();

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <util/Assert.h>
#include <gtest/gtest.h>
TEST(AssertTests, assertTrue)
{
EXPECT_NO_THROW(ASSERT(true, "Should not fail"));
}
TEST(AssertTests, assertFalse)
{
EXPECT_DEATH({ ASSERT(false, "failure"); }, "failure");
}

View File

@@ -78,8 +78,7 @@ struct MockPrometheusImpl : PrometheusInterface {
}
auto* basePtr = it->second.get();
auto* metricPtr = dynamic_cast<MetricType*>(basePtr);
if (metricPtr == nullptr)
throw std::runtime_error("Wrong metric type");
ASSERT(metricPtr != nullptr, "Wrong metric type");
return *metricPtr;
}
@@ -101,8 +100,7 @@ struct MockPrometheusImpl : PrometheusInterface {
}
auto* ptr = metrics.emplace(key, std::move(metric)).first->second.get();
auto metricPtr = dynamic_cast<MetricType*>(ptr);
if (metricPtr == nullptr)
throw std::runtime_error("Wrong metric type");
ASSERT(metricPtr != nullptr, "Wrong metric type");
return *metricPtr;
}
@@ -137,8 +135,7 @@ struct WithMockPrometheus : virtual ::testing::Test {
mockPrometheus()
{
auto* ptr = dynamic_cast<MockPrometheusImpl*>(&PrometheusService::instance());
if (ptr == nullptr)
throw std::runtime_error("Wrong prometheus type");
ASSERT(ptr != nullptr, "Wrong prometheus type");
return *ptr;
}
@@ -147,8 +144,7 @@ struct WithMockPrometheus : virtual ::testing::Test {
makeMock(std::string name, std::string labelsString)
{
auto* mockPrometheusPtr = dynamic_cast<MockPrometheusImpl*>(&PrometheusService::instance());
if (mockPrometheusPtr == nullptr)
throw std::runtime_error("Wrong prometheus type");
ASSERT(mockPrometheusPtr != nullptr, "Wrong prometheus type");
std::string const key = name + labelsString;
mockPrometheusPtr->makeMetric<MetricType>(std::move(name), std::move(labelsString));
@@ -159,7 +155,7 @@ struct WithMockPrometheus : virtual ::testing::Test {
} else if constexpr (std::is_same_v<typename MetricType::ValueType, double>) {
return mockPrometheusPtr->counterDoubleImpls[key];
}
throw std::runtime_error("Wrong metric type");
ASSERT(false, "Wrong metric type");
}
};