Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace xrpl::ledger_entries {
// builder's STObject and the wrapper's SLE.
TEST(${name}Tests, BuilderSettersRoundTrip)
{
uint256 const index{1u};
uint256 const index{1};

% for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
Expand Down Expand Up @@ -85,7 +85,7 @@ TEST(${name}Tests, BuilderSettersRoundTrip)
// from that SLE, build a new wrapper, and verify all fields (and validate()).
TEST(${name}Tests, BuilderFromSleRoundTrip)
{
uint256 const index{2u};
uint256 const index{2};

% for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
Expand Down Expand Up @@ -146,7 +146,7 @@ TEST(${name}Tests, BuilderFromSleRoundTrip)
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
{
uint256 const index{3u};
uint256 const index{3};

// Build a valid ledger entry of a different type
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
Expand Down Expand Up @@ -177,7 +177,7 @@ TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
// 4) Verify builder throws when constructed from wrong ledger entry type.
TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
{
uint256 const index{4u};
uint256 const index{4};

// Build a valid ledger entry of a different type
% if wrong_le_include == "Ticket":
Expand Down Expand Up @@ -207,7 +207,7 @@ TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
// 5) Build with only required fields and verify optional fields return nullopt.
TEST(${name}Tests, OptionalFieldsReturnNullopt)
{
uint256 const index{3u};
uint256 const index{3};

% for field in required_fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
Expand Down
201 changes: 146 additions & 55 deletions include/xrpl/basics/CountedObject.h
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
#pragma once

#include <xrpl/beast/type_name.h>
#include <xrpl/beast/utility/instrumentation.h>

#include <boost/core/type_name.hpp>

#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

namespace xrpl {

/**
* Manages all counted object types.
*
* Counters register themselves on a lock-free intrusive list maintained
* by this object when constructed. Because counters are never destroyed
* or removed, the ABA problem does not apply.
*
* The registry is iterable as a forward range.
*/
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;

using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;

[[nodiscard]] List
getCounts(int minimumThreshold) const;

public:
/**
* Implementation for @ref CountedObject.
Expand All @@ -33,68 +35,147 @@ class CountedObjects
class Counter
{
public:
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
Counter(std::string name) noexcept;

// Counters are intrusive list nodes whose addresses are published
// in the registry; they must never be copied or moved. The atomic
// members already force this, but we make it explicit.
Counter(Counter const&) = delete;
Counter&
operator=(Counter const&) = delete;
Counter(Counter&&) = delete;
Counter&
operator=(Counter&&) = delete;

std::uint32_t
increment() noexcept
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head = nullptr;

do
{
head = instance.head_.load();
next_ = head;
} while (instance.head_.exchange(this) != head);
auto const newCount = count_.fetch_add(1, std::memory_order::relaxed) + 1;
XRPL_ASSERT(newCount != 0, "xrpl::CountedObjects::Counter::increment : no overflow");

++instance.count_;
}
auto maxCount = maxCount_.load(std::memory_order::relaxed);

~Counter() noexcept = default;
while (newCount > maxCount &&
!maxCount_.compare_exchange_weak(maxCount, newCount, std::memory_order::relaxed))
{
}

int
increment() noexcept
{
return ++count_;
return newCount;
}

int
std::uint32_t
decrement() noexcept
{
return --count_;
auto const prev = count_.fetch_sub(1, std::memory_order::relaxed);
XRPL_ASSERT(prev != 0, "xrpl::CountedObjects::Counter::decrement : no underflow");
return prev - 1;
}

[[nodiscard]] int
getCount() const noexcept
[[nodiscard]] std::uint32_t
count() const noexcept
{
return count_.load();
return count_.load(std::memory_order::relaxed);
}

[[nodiscard]] Counter*
getNext() const noexcept
[[nodiscard]] std::uint32_t
max() const noexcept
{
return next_;
return std::max(
count_.load(std::memory_order::relaxed),
maxCount_.load(std::memory_order::relaxed));
}

[[nodiscard]] std::string const&
getName() const noexcept
name() const noexcept
{
return name_;
}

private:
friend class CountedObjects;

Counter* next_ = nullptr;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
std::atomic<int> count_;
Counter* next_;
};

private:
CountedObjects() noexcept;
~CountedObjects() noexcept = default;
class Iterator
{
public:
using value_type = Counter const;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;

explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
{
}

reference
operator*() const noexcept
{
return *current_;
}

pointer
operator->() const noexcept
{
return current_;
}

Iterator&
operator++() noexcept
{
current_ = current_->next_;
return *this;
}

Iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}

bool
operator==(Iterator const&) const noexcept = default;

private:
Counter* current_;
};

constexpr CountedObjects() noexcept = default;

[[nodiscard]] auto
begin() const noexcept
{
return Iterator{head_.load(std::memory_order::acquire)};
}

[[nodiscard]] auto
end() const noexcept
{
return Iterator{};
}

private:
std::atomic<int> count_;
std::atomic<Counter*> head_;
std::atomic<Counter*> head_ = nullptr;
};

/** The global counted object registry. */
inline constinit CountedObjects gCountedObjects;

inline CountedObjects::Counter::Counter(std::string name) noexcept
: next_(gCountedObjects.head_.load(std::memory_order::relaxed)), name_(std::move(name))
{
while (!gCountedObjects.head_.compare_exchange_weak(
next_, this, std::memory_order::release, std::memory_order::relaxed))
;
}

//------------------------------------------------------------------------------

/**
Expand All @@ -103,27 +184,37 @@ class CountedObjects
* Derived classes have their instances counted automatically. This is used
* for reporting purposes.
*
* The constructors are private and `Object` is befriended so that the
* CRTP parameter must be the deriving class itself: a copy-paste error
* like `class B : public CountedObject<A>` fails to compile instead of
* silently polluting A's count.
*
* @note This class has no move operations by design: a derived class's
* move constructor falls back to the copy constructor for this
* base, so the newly created instance is counted. This keeps the
* invariant that count is the number of outstanding subobjects.
*
* @warning Counted objects constructed during dynamic initialization of
* other translation units may have their increments discarded when
* counter itself is dynamically initialized. Do not create counted
* objects before main() begins.
*
* @ingroup basics
*/
template <class Object>
requires std::is_class_v<Object>
class CountedObject
{
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter kC{beast::typeName<Object>()};
return kC;
}
static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};

CountedObject() noexcept
{
getCounter().increment();
counter.increment();
}

CountedObject(CountedObject const&) noexcept
{
getCounter().increment();
counter.increment();
}

CountedObject&
Expand All @@ -132,7 +223,7 @@ class CountedObject
public:
~CountedObject() noexcept
{
getCounter().decrement();
counter.decrement();
}

friend Object;
Expand Down
Loading
Loading