Compare commits

...
Author SHA1 Message Date
tequ ac1e2356e5 Refactor ledger_entry RPC source code and tests (#5237)
This is a major refactor of LedgerEntry.cpp. It adds a number of helper functions to make the code easier to maintain.

It also splits up the ledger and ledger_entry tests into different files, and cleans up the ledger_entry tests to make them easier to write and maintain.

This refactor also caught a few bugs in some of the other RPC processing, so those are fixed along the way.

Xahau: the Xahau ledger entry parsers (app_loader, cron, emitted_txn, hook, hook_definition, hook_state, import_vlseq, manifest_entry, random, uri_token) are ported to the new helpers; rpcENTRY_NOT_FOUND and rpcUNEXPECTED_LEDGER_TYPE take upstream's codes 98 and 99. A non-string permissioned_domain account now returns malformedAddress, matching upstream after #5344. `maxURITokenURILength` is introduced in Protocol.h and used in URIToken.cpp and the tests (taken from Xahau sync commit 9ff0d3c7c3; no behaviour change). requiredUInt256 reports "hex string" instead of upstream's "Hash256" (sync-branch choice; it only affects the hook_state key/namespace_id error text). The Vault_test.cpp hunk and the Delegate test are dropped because Xahau has neither feature.
2026-10-22 16:33:31 +09:00
tequ 5de98a6775 refactor(trivial): reorganize ledger entry tests and helper functions (#5376)
This PR splits out `ledger_entry` tests into its own file (`LedgerEntry_test.cpp`) and alphabetizes the helper functions in `LedgerEntry.cpp`. These commits were split out of #5237 to make that PR a little more manageable, since these basic trivial changes are most of the diff. There is no code change, just moving code around.

Xahau: the Xahau-specific parsers (app_loader, random) and tests (emitted_txn, import_vlseq/uri_token/hook invalid-param cases) move along with the rest.
2026-10-22 16:10:19 +09:00
Richard HollandandAlphonse N. Mousse 5c05e107c0 A-noni-mousse Changeset v3 (#899)
From https://github.com/Xahau/xahaud/pull/712

Co-authored-by: Alphonse N. Mousse <[email protected]>
2026-10-21 12:18:45 +10:00
Richard Holland 3dc41b2a59 ignore amendment cfg option (#900)
Use like so, on testnet:
[ignore_amendments]
HookOnV2
2026-10-17 09:19:39 +10:00
85 changed files with 7165 additions and 8387 deletions

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ Loop: xrpld.core xrpld.net
xrpld.net > xrpld.core
Loop: xrpld.core xrpld.perflog
xrpld.perflog == xrpld.core
xrpld.perflog ~= xrpld.core
Loop: xrpld.net xrpld.rpc
xrpld.rpc > xrpld.net
-2
View File
@@ -52,8 +52,6 @@ test.core > test.toplevel
test.core > test.unit_test
test.core > xrpl.basics
test.core > xrpld.core
test.core > xrpld.perflog
test.core > xrpl.json
test.core > xrpl.server
test.csf > xrpl.basics
test.csf > xrpld.consensus
+1 -1
View File
@@ -88,7 +88,7 @@ git remote -v >> release.info &&
echo "Git status:" >> release.info &&
git status -v >> release.info &&
echo "Git log [last 20]:" >> release.info &&
git log -n 20 >> release.info;
git log -n 20 >> release.info || exit 1
if [[ "$4" == "" ]]; then
# Non GH, local building
+124 -83
View File
@@ -1,75 +1,33 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#include <xrpl/beast/type_name.h>
#include <atomic>
#include <cstddef>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
namespace ripple {
/** Manages all counted object types. */
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;
using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;
List
getCounts(int minimumThreshold) const;
public:
/** Implementation for @ref CountedObject.
@internal
*/
class Counter
{
public:
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head;
do
{
head = instance.m_head.load();
next_ = head;
} while (instance.m_head.exchange(this) != head);
++instance.m_count;
}
~Counter() noexcept = default;
Counter(std::string name) noexcept;
int
increment() noexcept
{
return ++count_;
auto const newCount = ++count_;
if (auto maxCount = maxCount_.load(); newCount > maxCount)
maxCount_.compare_exchange_strong(maxCount, newCount);
return newCount;
}
int
@@ -79,78 +37,161 @@ public:
}
int
getCount() const noexcept
count() const noexcept
{
return count_.load();
}
Counter*
getNext() const noexcept
int
max() const noexcept
{
return next_;
return std::max(count_.load(), maxCount_.load());
}
std::string const&
getName() const noexcept
name() const noexcept
{
return name_;
}
private:
std::string const name_;
std::atomic<int> count_;
friend class CountedObjects;
Counter* next_;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
};
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_;
};
using List = std::vector<std::pair<std::string, int>>;
constexpr CountedObjects() noexcept = default;
static CountedObjects&
getInstance() noexcept;
/** Returns the name and count of every counter with at least
`minimumThreshold` live instances. */
List
getCounts(int minimumThreshold) const
{
List ret;
for (auto c = head_.load(); c != nullptr; c = c->next_)
if (c->count() >= minimumThreshold)
ret.emplace_back(c->name(), c->count());
return ret;
}
auto
begin() const noexcept
{
return Iterator{head_.load()};
}
auto
end() const noexcept
{
return Iterator{};
}
private:
std::atomic<int> m_count;
std::atomic<Counter*> m_head;
friend class Counter;
std::atomic<Counter*> head_ = nullptr;
};
inline constinit CountedObjects countedObjects;
inline CountedObjects&
CountedObjects::getInstance() noexcept
{
return countedObjects;
}
inline CountedObjects::Counter::Counter(std::string name) noexcept
: name_(std::move(name))
{
do
next_ = countedObjects.head_.load();
while (!countedObjects.head_.compare_exchange_weak(next_, this));
}
//------------------------------------------------------------------------------
/** Tracks the number of instances of an object.
Derived classes have their instances counted automatically. This is used
for reporting purposes.
@ingroup ripple_basics
*/
template <class Object>
class CountedObject
{
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter c{beast::type_name<Object>()};
return c;
}
static CountedObjects::Counter counter_;
public:
CountedObject() noexcept
{
getCounter().increment();
counter_.increment();
}
CountedObject(CountedObject const&) noexcept
{
getCounter().increment();
counter_.increment();
}
CountedObject&
operator=(CountedObject const&) noexcept = default;
~CountedObject() noexcept
{
getCounter().decrement();
counter_.decrement();
}
};
// Instantiation of the static CountedObject<T>::counter_
template <class Object>
CountedObjects::Counter CountedObject<Object>::counter_{
beast::type_name<Object>()};
} // namespace ripple
#endif
+1 -1
View File
@@ -113,7 +113,7 @@ template <>
inline std::size_t
extract(SHAMapHash const& key)
{
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data());
return extract(key.as_uint256());
}
} // namespace ripple
+528 -305
View File
@@ -21,195 +21,443 @@
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/basics/spinlock.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/align.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/predef.h>
#include <boost/predef/os.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <vector>
#include <bit>
#if BOOST_OS_LINUX
#include <limits>
#include <new>
#ifdef BOOST_OS_LINUX
#include <sys/mman.h>
#endif
namespace ripple {
namespace slab {
namespace detail {
template <typename Type>
class SlabAllocator
/// Alignment for hugepage support.
inline constexpr std::size_t pageSize = megabytes(std::size_t(2));
/** Allocate a new 2MB-aligned data buffer.
@param size Size of the buffer to allocate in bytes.
@return Pointer to allocated memory, or nullptr on failure.
@note On Linux, advises the kernel to back the allocation with
transparent huge pages if available.
*/
[[nodiscard, gnu::malloc]] inline std::uint8_t*
allocateBuffer(std::size_t size) noexcept
{
static_assert(
sizeof(Type) >= sizeof(std::uint8_t*),
"SlabAllocator: the requested object must be larger than a pointer.");
auto ptr = reinterpret_cast<std::uint8_t*>(
boost::alignment::aligned_alloc(pageSize, size));
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
#if BOOST_OS_LINUX
if (ptr != nullptr) [[likely]]
madvise(ptr, size, MADV_HUGEPAGE);
#endif
/** A block of memory that is owned by a slab allocator */
struct SlabBlock
return ptr;
}
/** Deallocate a buffer previously allocated by allocateBuffer.
@param ptr Pointer to buffer, or nullptr (no-op).
*/
inline void
deallocateBuffer(std::uint8_t* ptr) noexcept
{
boost::alignment::aligned_free(ptr);
}
//------------------------------------------------------------------------------
/** A block of memory metadata for slab allocators.
Each block manages a single slab buffer and its associated free list.
Blocks are linked together to form a list of slabs per allocator.
@note slab_t instances are intentionally never destroyed or reclaimed
once allocated, to support lock-free iteration of lists of slabs
without introducing ABA hazards. The data buffer the class holds
can be released independently.
*/
struct slab_t
{
/** A node in the intrusive free list.
Constructed in-place within free memory blocks. The next pointer
is const because nodes are never modified after construction;
they are simply constructed anew when reused.
*/
struct item_t
{
// A mutex to protect the freelist for this block:
std::mutex m_;
item_t* const next;
// A linked list of appropriately sized free buffers:
std::uint8_t* l_ = nullptr;
// The next memory block
SlabBlock* next_;
// The underlying memory block:
std::uint8_t const* const p_ = nullptr;
// The extent of the underlying memory block:
std::size_t const size_;
SlabBlock(
SlabBlock* next,
std::uint8_t* data,
std::size_t size,
std::size_t item)
: next_(next), p_(data), size_(size)
constexpr explicit item_t(item_t* n) noexcept : next(n)
{
// We don't need to grab the mutex here, since we're the only
// ones with access at this moment.
while (data + item <= p_ + size_)
{
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(data, &l_, sizeof(std::uint8_t*));
l_ = data;
data += item;
}
}
~SlabBlock()
{
// Calling this destructor will release the allocated memory but
// will not properly destroy any objects that are constructed in
// the block itself.
}
SlabBlock(SlabBlock const& other) = delete;
SlabBlock&
operator=(SlabBlock const& other) = delete;
SlabBlock(SlabBlock&& other) = delete;
SlabBlock&
operator=(SlabBlock&& other) = delete;
/** Determines whether the given pointer belongs to this allocator */
bool
own(std::uint8_t const* p) const noexcept
{
return (p >= p_) && (p < p_ + size_);
}
std::uint8_t*
allocate() noexcept
{
std::uint8_t* ret;
{
std::lock_guard l(m_);
ret = l_;
if (ret)
{
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(&l_, ret, sizeof(std::uint8_t*));
}
}
return ret;
}
/** Return an item to this allocator's freelist.
@param ptr The pointer to the chunk of memory being deallocated.
@note This is a dangerous, private interface; the item being
returned should belong to this allocator. Debug builds
will check and assert if this is not the case. Release
builds will not.
*/
void
deallocate(std::uint8_t* ptr) noexcept
{
XRPL_ASSERT(
own(ptr),
"ripple::SlabAllocator::SlabBlock::deallocate : own input");
std::lock_guard l(m_);
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(ptr, &l_, sizeof(std::uint8_t*));
l_ = ptr;
}
};
private:
// A linked list of slabs
std::atomic<SlabBlock*> slabs_ = nullptr;
// We need this to be trivially destructible so that we do not
// have to explicitly invoke the destructor when popping items
// from the free list or when deallocating the slab buffer.
static_assert(std::is_trivially_destructible_v<item_t>);
// The alignment requirements of the item we're allocating:
std::size_t const itemAlignment_;
item_t* head_ = nullptr;
slab_t* const next_ = nullptr;
std::atomic<std::uint8_t*> data_{nullptr};
std::atomic<std::uint8_t> lock_{0};
std::uint16_t cycles_ = 0;
std::uint32_t outstanding_ = 0;
// The size of an item, including the extra bytes requested and
// any padding needed for alignment purposes:
std::size_t const itemSize_;
/** Construct a block that is unlinked. */
constexpr slab_t() noexcept = default;
// The size of each individual slab:
std::size_t const slabSize_;
/** Construct a block with a link to the next block in the chain.
@param next Pointer to the next block, or nullptr for the tail.
*/
constexpr explicit slab_t(slab_t* next) noexcept : next_(next)
{
}
~slab_t() = default;
slab_t(slab_t const&) = delete;
slab_t&
operator=(slab_t const&) = delete;
slab_t(slab_t&&) = delete;
slab_t&
operator=(slab_t&&) = delete;
/** Attempt to acquire the block's spinlock.
@return true if lock was acquired, false if already held.
*/
[[nodiscard]] bool
try_lock() noexcept
{
return spin_try_lock(lock_);
}
/** Acquire the block's spinlock, blocking until available. */
void
lock() noexcept
{
spin_lock(lock_);
}
/** Release the block's spinlock. */
void
unlock() noexcept
{
spin_unlock(lock_);
}
/** Attempt to assign a buffer to this block.
If the block has no buffer, takes ownership and initializes
the free list.
@param buffer Pointer to the buffer to assign.
@param slabSize Size of the buffer in bytes.
@param itemSize Size of each item in bytes.
@return Pointer to first item if assigned, nullptr if block already
has a buffer.
*/
[[nodiscard]] std::uint8_t*
try_assign(
std::uint8_t* buffer,
std::size_t slabSize,
std::size_t itemSize) noexcept
{
std::uint8_t* expected = nullptr;
if (!data_.compare_exchange_strong(
expected, buffer, std::memory_order::acquire))
return nullptr;
spin_lock(lock_);
head_ = nullptr;
outstanding_ = 1;
auto p = buffer;
for (std::size_t n = (slabSize / itemSize) - 1; n--; p += itemSize)
head_ = std::construct_at(reinterpret_cast<item_t*>(p), head_);
spin_unlock(lock_);
return p;
}
/** Attempt to allocate from this block.
@return Pointer to allocated memory, or nullptr if block is empty.
*/
[[nodiscard]] std::uint8_t*
try_allocate() noexcept
{
spin_lock(lock_);
auto ret = head_;
if (ret)
{
head_ = ret->next;
++outstanding_;
}
spin_unlock(lock_);
return reinterpret_cast<std::uint8_t*>(ret);
}
/** Attempt to return a pointer to this block.
@param ptr Pointer to memory block.
@param slabSize Size of the slab's data buffer.
@param releaseBuffer Callback invoked with a pointer to the block's
buffer so that it can be released.
@return true if ptr belonged to this block and was freed,
false otherwise.
*/
template <typename ReleaseFunc>
[[nodiscard]] bool
try_deallocate(
std::uint8_t* ptr,
std::size_t slabSize,
ReleaseFunc&& releaseBuffer) noexcept
{
auto data = data_.load(std::memory_order::acquire);
if (!data || ptr < data || ptr >= data + slabSize)
return false;
std::uint8_t* buf = nullptr;
spin_lock(lock_);
assert(ptr != nullptr);
assert(outstanding_ > 0);
head_ = std::construct_at(reinterpret_cast<item_t*>(ptr), head_);
// If this block became empty and it is not the first block, we
// release its buffer. The first block (at the tail of the list
// with next_ == nullptr) is exempt to keep one block ready.
if (--outstanding_ == 0 && next_ != nullptr)
{
head_ = nullptr;
buf = data_.exchange(buf, std::memory_order::relaxed);
cycles_ += (cycles_ < std::numeric_limits<std::uint16_t>::max());
}
spin_unlock(lock_);
if (buf != nullptr)
releaseBuffer(buf);
return true;
}
};
//------------------------------------------------------------------------------
/** Global pool of slab_t objects shared by all slab allocators.
Provides a fixed-size array of pre-allocated slabs with fallback
to heap allocation if exhausted.
*/
class slab_pool_t
{
std::array<slab_t, 32768> blocks_{};
std::atomic<std::size_t> count_{0};
public:
constexpr slab_pool_t() = default;
slab_pool_t(slab_pool_t const&) = delete;
slab_pool_t&
operator=(slab_pool_t const&) = delete;
slab_pool_t(slab_pool_t&&) = delete;
slab_pool_t&
operator=(slab_pool_t&&) = delete;
/** Acquire a slab from the pool.
@param next Pointer to link as the slab's next pointer.
@return Pointer to acquired block, or nullptr if allocation failed.
*/
[[nodiscard]] slab_t*
acquire(slab_t* next) noexcept
{
auto idx = count_.load(std::memory_order::relaxed);
while (idx < blocks_.size())
{
if (count_.compare_exchange_weak(
idx, idx + 1, std::memory_order::relaxed))
{
return std::construct_at(&blocks_[idx], next);
}
}
// Pool exhausted (unlikely!) so fall back to heap
return new (std::nothrow) slab_t(next);
}
};
/// Single global block pool for all slab allocators.
inline constinit slab_pool_t globalSlabPool;
} // namespace detail
//------------------------------------------------------------------------------
/** A slab allocator for fixed-size memory blocks.
Allocates memory in large slabs (multiples of 2MB) and sub-allocates
fixed-size blocks from them. Provides fast allocation with minimal
fragmentation for objects of uniform size.
@tparam Type The type used to determine minimum block size and alignment.
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
*/
template <typename Type, std::size_t Align = alignof(Type)>
requires(
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
std::has_single_bit(Align))
class sized_allocator_t
{
/// Linked list of slabs for this allocator.
std::atomic<detail::slab_t*> slabs_{nullptr};
/// Cached buffer to avoid thrashing when oscillating around block
/// boundaries.
std::atomic<std::uint8_t*> cachedBuffer_{nullptr};
/// Spinlock to serialize the slow allocation path.
std::atomic<std::uint8_t> blockLock_{0};
/// Item size (sizeof(Type) + extra, rounded up for alignment).
std::size_t itemSize_;
/// Size of each slab's data buffer (multiple of 2 MB).
std::size_t slabSize_;
/** Allocate a memory block from the existing slabs.
Fast path allocation that doesn't acquire the global lock.
@return Pointer to allocated memory, or nullptr if all slabs are full.
*/
[[nodiscard]] std::uint8_t*
fast_allocate() noexcept
{
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
{
if (auto ret = slab->try_allocate())
return ret;
}
return nullptr;
}
/** Allocate a memory block, potentially creating new capacity.
Slow path allocation that may allocate a new buffer or slab.
Must be called with blockLock_ held.
@return Pointer to allocated memory, or nullptr on failure.
*/
[[nodiscard]] std::uint8_t*
slow_allocate() noexcept
{
if (auto ret = fast_allocate())
return ret;
// Use a cached buffer first, if we have one, or allocate memory
// from the operating system.
auto buf = cachedBuffer_.exchange(nullptr, std::memory_order::acquire);
if (!buf)
buf = detail::allocateBuffer(slabSize_);
if (!buf) [[unlikely]]
return nullptr;
// Try to give buffer to an existing empty slab
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
{
if (auto ret = slab->try_assign(buf, slabSize_, itemSize_))
return ret;
}
// Create new slab from global pool
auto* slab = detail::globalSlabPool.acquire(
slabs_.load(std::memory_order::relaxed));
if (!slab) [[unlikely]]
{
detail::deallocateBuffer(buf);
return nullptr;
}
auto ret = slab->try_assign(buf, slabSize_, itemSize_);
assert(ret);
// Link the new slab. Since we hold the lock, we can just do a
// direct assignment here.
slabs_.store(slab, std::memory_order::release);
return ret;
}
public:
/** Constructs a slab allocator able to allocate objects of a fixed size
@param count the number of items the slab allocator can allocate; note
that a count of 0 is valid and means that the allocator
is, effectively, disabled. This can be very useful in some
contexts (e.g. when mimimal memory usage is needed) and
allows for graceful failure.
@param extra Extra bytes per item beyond sizeof(Type).
@param minItems Minimum number of items per slab (rounded up to 2MB).
*/
constexpr explicit SlabAllocator(
constexpr explicit sized_allocator_t(
std::size_t extra,
std::size_t alloc = 0,
std::size_t align = 0)
: itemAlignment_(align ? align : alignof(Type))
, itemSize_(
boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
, slabSize_(alloc)
std::size_t minItems)
: itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
, slabSize_(boost::alignment::align_up(
itemSize_ * minItems,
detail::pageSize))
{
XRPL_ASSERT(
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
"ripple::SlabAllocator::SlabAllocator : valid alignment");
}
SlabAllocator(SlabAllocator const& other) = delete;
SlabAllocator&
operator=(SlabAllocator const& other) = delete;
// Data buffers are intentionally not released. C++ destruction order
// does not guarantee that all items allocated from this allocator
// have been freed by the time this destructor runs.
~sized_allocator_t() = default;
SlabAllocator(SlabAllocator&& other) = delete;
SlabAllocator&
operator=(SlabAllocator&& other) = delete;
sized_allocator_t(sized_allocator_t const&) = delete;
sized_allocator_t&
operator=(sized_allocator_t const&) = delete;
sized_allocator_t(sized_allocator_t&&) = delete;
sized_allocator_t&
operator=(sized_allocator_t&&) = delete;
~SlabAllocator()
{
// FIXME: We can't destroy the memory blocks we've allocated, because
// we can't be sure that they are not being used. Cleaning the
// shutdown process up could make this possible.
}
/** Returns the size of memory blocks returned by this allocator.
/** Returns the size of the memory block this allocator returns. */
constexpr std::size_t
@return Size of each allocated block in bytes.
*/
[[nodiscard]] constexpr std::size_t
size() const noexcept
{
return itemSize_;
@@ -217,87 +465,36 @@ public:
/** Returns a suitably aligned pointer, if one is available.
@return a pointer to a block of memory from the allocator, or
nullptr if the allocator can't satisfy this request.
@return Pointer to allocated memory, or nullptr on failure.
@note The gnu::malloc attribute is an optimization hint that can
be leveraged by GCC and Clang.
*/
std::uint8_t*
[[nodiscard, gnu::malloc]] std::uint8_t*
allocate() noexcept
{
auto slab = slabs_.load();
// Fast path: try existing slabs
auto ret = fast_allocate();
while (slab != nullptr)
if (ret == nullptr)
{
if (auto ret = slab->allocate())
return ret;
slab = slab->next_;
// Slow path: need new capacity. Grab the lock to serialize access
// to this code path because it is expensive. Then re-check in case
// another thread added capacity while we waited.
spin_lock(blockLock_);
ret = slow_allocate();
spin_unlock(blockLock_);
}
// No slab can satisfy our request, so we attempt to allocate a new
// one here:
std::size_t size = slabSize_;
// We want to allocate the memory at a 2 MiB boundary, to make it
// possible to use hugepage mappings on Linux:
auto buf =
boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
// clang-format off
if (!buf) [[unlikely]]
return nullptr;
// clang-format on
#if BOOST_OS_LINUX
// When allocating large blocks, attempt to leverage Linux's
// transparent hugepage support. It is unclear and difficult
// to accurately determine if doing this impacts performance
// enough to justify using platform-specific tricks.
if (size >= megabytes(std::size_t(4)))
madvise(buf, size, MADV_HUGEPAGE);
#endif
// We need to carve out a bit of memory for the slab header
// and then align the rest appropriately:
auto slabData = reinterpret_cast<void*>(
reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
auto slabSize = size - sizeof(SlabBlock);
// This operation is essentially guaranteed not to fail but
// let's be careful anyways.
if (!boost::alignment::align(
itemAlignment_, itemSize_, slabData, slabSize))
{
boost::alignment::aligned_free(buf);
return nullptr;
}
slab = new (buf) SlabBlock(
slabs_.load(),
reinterpret_cast<std::uint8_t*>(slabData),
slabSize,
itemSize_);
// Link the new slab
while (!slabs_.compare_exchange_weak(
slab->next_,
slab,
std::memory_order_release,
std::memory_order_relaxed))
{
; // Nothing to do
}
return slab->allocate();
return ret;
}
/** Returns the memory block to the allocator.
@param ptr A pointer to a memory block.
@param size If non-zero, a hint as to the size of the block.
@return true if this memory block belonged to the allocator and has
been released; false otherwise.
@param ptr Pointer to memory block.
@return true if the block belonged to this allocator and was freed.
*/
bool
[[nodiscard]] bool
deallocate(std::uint8_t* ptr) noexcept
{
XRPL_ASSERT(
@@ -305,112 +502,135 @@ public:
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
"input");
for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next_)
auto release = [this](std::uint8_t* buf) {
std::uint8_t* expected = nullptr;
if (buf != nullptr &&
!cachedBuffer_.compare_exchange_strong(
expected, buf, std::memory_order::release))
detail::deallocateBuffer(buf);
};
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
{
if (slab->own(ptr))
{
slab->deallocate(ptr);
if (slab->try_deallocate(ptr, slabSize_, release))
return true;
}
}
return false;
}
};
/** A collection of slab allocators of various sizes for a given type. */
template <typename Type>
class SlabAllocatorSet
//------------------------------------------------------------------------------
/** Configuration for a single slab allocator.
@tparam MinItems Minimum number of items per slab (must be > 0).
@tparam Extra Extra bytes per item beyond sizeof(Type).
*/
template <std::size_t MinItems, std::size_t Extra = 0>
requires(MinItems > 0)
struct config
{
private:
// The list of allocators that belong to this set
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_;
static constexpr std::size_t minItems = MinItems;
static constexpr std::size_t extra = Extra;
};
std::size_t maxSize_ = 0;
/** Concept for valid slab configuration types. */
template <typename T>
concept SlabConfig = requires {
{ T::minItems } -> std::convertible_to<std::size_t>;
{ T::extra } -> std::convertible_to<std::size_t>;
requires T::minItems > 0;
};
public:
class SlabConfig
{
friend class SlabAllocatorSet;
/** Validate slab configurations at compile time.
private:
std::size_t extra;
std::size_t alloc;
std::size_t align;
Checks that configurations produce strictly increasing sizes after
alignment. This catches both unsorted configs and configs that
collapse to the same size after alignment.
public:
constexpr SlabConfig(
std::size_t extra_,
std::size_t alloc_ = 0,
std::size_t align_ = alignof(Type))
: extra(extra_), alloc(alloc_), align(align_)
{
}
@tparam Type The type used to determine base size.
@tparam Align Alignment for allocated blocks.
@tparam Configs Configuration types to validate.
@return true if configurations are valid, false otherwise.
*/
template <typename Type, std::size_t Align, SlabConfig... Configs>
consteval bool
validate_slab_config()
{
constexpr auto align_up = [](std::size_t n, std::size_t a) {
return (n + a - 1) & ~(a - 1);
};
constexpr SlabAllocatorSet(std::vector<SlabConfig> cfg)
{
// Ensure that the specified allocators are sorted from smallest to
// largest by size:
std::sort(
std::begin(cfg),
std::end(cfg),
[](SlabConfig const& a, SlabConfig const& b) {
return a.extra < b.extra;
});
constexpr std::array<std::size_t, sizeof...(Configs)> sizes{
align_up(sizeof(Type) + Configs::extra, Align)...};
// We should never have two slabs of the same size
if (std::adjacent_find(
std::begin(cfg),
std::end(cfg),
[](SlabConfig const& a, SlabConfig const& b) {
return a.extra == b.extra;
}) != cfg.end())
{
throw std::runtime_error(
"SlabAllocatorSet<" + beast::type_name<Type>() +
">: duplicate slab size");
}
for (std::size_t i = 1; i < sizes.size(); ++i)
if (sizes[i - 1] >= sizes[i])
return false;
for (auto const& c : cfg)
{
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
return true;
}
if (a.size() > maxSize_)
maxSize_ = a.size();
}
}
//------------------------------------------------------------------------------
SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
SlabAllocatorSet&
operator=(SlabAllocatorSet const& other) = delete;
/** A collection of slab allocators for different sizes.
SlabAllocatorSet(SlabAllocatorSet&& other) = delete;
SlabAllocatorSet&
operator=(SlabAllocatorSet&& other) = delete;
Manages multiple sized_allocator_t instances, each configured for a
different block size. Allocations are routed to the smallest allocator
that can satisfy the request.
~SlabAllocatorSet()
@tparam Type The type used to determine minimum block size and alignment.
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
@tparam Configs Configuration types specifying each size class.
*/
template <typename Type, std::size_t Align, SlabConfig... Configs>
requires(
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
std::has_single_bit(Align) && sizeof...(Configs) > 0 &&
validate_slab_config<Type, Align, Configs...>())
class aligned_allocator_t
{
std::array<sized_allocator_t<Type, Align>, sizeof...(Configs)> allocators_;
public:
/** Construct an allocator set. */
constexpr aligned_allocator_t()
: allocators_{sized_allocator_t<Type, Align>(
Configs::extra,
Configs::minItems)...}
{
}
/** Returns a suitably aligned pointer, if one is available.
~aligned_allocator_t() = default;
@param extra The number of extra bytes, above and beyond the size of
the object, that should be returned by the allocator.
aligned_allocator_t(aligned_allocator_t const&) = delete;
aligned_allocator_t&
operator=(aligned_allocator_t const&) = delete;
aligned_allocator_t(aligned_allocator_t&&) = delete;
aligned_allocator_t&
operator=(aligned_allocator_t&&) = delete;
@return a pointer to a block of memory, or nullptr if the allocator
can't satisfy this request.
/** Allocate memory for an object with extra bytes.
@param extra Extra bytes needed beyond sizeof(Type).
@return Pointer to memory, or nullptr if no suitable allocator
or allocation failed.
@note The gnu::malloc attribute is an optimization hint that can
be leveraged by GCC and Clang.
*/
std::uint8_t*
[[nodiscard, gnu::malloc]] std::uint8_t*
allocate(std::size_t extra) noexcept
{
if (auto const size = sizeof(Type) + extra; size <= maxSize_)
auto const size = sizeof(Type) + extra;
for (auto& a : allocators_)
{
for (auto& a : allocators_)
{
if (a.size() >= size)
return a.allocate();
}
if (a.size() >= size)
return a.allocate();
}
return nullptr;
@@ -418,12 +638,10 @@ public:
/** Returns the memory block to the allocator.
@param ptr A pointer to a memory block.
@return true if this memory block belonged to one of the allocators
in this set and has been released; false otherwise.
@param ptr Pointer to memory block.
@return true if memory belonged to this set and was freed.
*/
bool
[[nodiscard]] bool
deallocate(std::uint8_t* ptr) noexcept
{
for (auto& a : allocators_)
@@ -431,11 +649,16 @@ public:
if (a.deallocate(ptr))
return true;
}
return false;
}
};
/** Alias for aligned_allocator_t with default alignment. */
template <typename Type, SlabConfig... Configs>
using allocator_t = aligned_allocator_t<Type, alignof(Type), Configs...>;
} // namespace slab
} // namespace ripple
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
File diff suppressed because it is too large. Load diff
+11 -5
View File
@@ -23,6 +23,7 @@
#include <xrpl/beast/type_name.h>
#include <exception>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <utility>
@@ -52,7 +53,7 @@ Rethrow()
}
template <class E, class... Args>
[[noreturn]] inline void
[[noreturn]] constexpr void
Throw(Args&&... args)
{
static_assert(
@@ -60,10 +61,15 @@ Throw(Args&&... args)
"Exception must derive from std::exception.");
E e(std::forward<Args>(args)...);
LogThrow(
std::string(
"Throwing exception of type " + beast::type_name<E>() + ": ") +
e.what());
// Skip logging when evaluated at compile time, so that `Throw` can be
// called from constexpr/consteval functions.
if (!std::is_constant_evaluated())
LogThrow(
std::string(
"Throwing exception of type " + beast::type_name<E>() + ": ") +
e.what());
throw e;
}
+139 -96
View File
@@ -20,6 +20,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <atomic>
#include <concepts>
#include <limits>
#include <type_traits>
@@ -30,6 +31,7 @@
namespace ripple {
namespace detail {
/** Inform the processor that we are in a tight spin-wait loop.
Spinlocks caught in tight loops can result in the processor's pipeline
@@ -52,11 +54,130 @@ spin_pause() noexcept
} // namespace detail
/** @{ */
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
/** Attempt to acquire a spinlock without blocking.
Packed spinlocks allow for tremendously space-efficient lock-sharding
but they come at a cost.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
@return true if the lock was acquired, false if it was already held.
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
[[nodiscard]] bool
spin_try_lock(std::atomic<T>& lock) noexcept
{
T expected = 0;
return lock.compare_exchange_strong(
expected,
std::numeric_limits<T>::max(),
std::memory_order::acquire,
std::memory_order::relaxed);
}
/** Acquire a spinlock, blocking until available.
Uses a TTAS (test-and-test-and-set) pattern to reduce cache coherency
traffic during contention.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
void
spin_lock(std::atomic<T>& lock) noexcept
{
T expected = 0;
while (!lock.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order::acquire,
std::memory_order::relaxed))
{
expected = 0;
while (lock.load(std::memory_order::relaxed) != 0)
detail::spin_pause();
}
}
/** Release a spinlock.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
void
spin_unlock(std::atomic<T>& lock) noexcept
{
lock.store(0, std::memory_order::release);
}
//------------------------------------------------------------------------------
/** A Lockable interface to a spinlock implemented on top of an atomic.
@tparam T An unsigned integral type.
@note Using `packed_spinlock` and `spinlock` against the same underlying
atomic integer can result in `spinlock` not being able to actually
acquire the lock during periods of high contention, because of how
the two locks operate: `spinlock` will spin trying to grab all the
bits at once, whereas any given `packed_spinlock` will only try to
grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
class spinlock
{
std::atomic<T>& lock_;
public:
spinlock(spinlock const&) = delete;
spinlock&
operator=(spinlock const&) = delete;
/** Construct a spinlock handle.
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
explicit spinlock(std::atomic<T>& lock) noexcept : lock_(lock)
{
}
[[nodiscard]] bool
try_lock() noexcept
{
return spin_try_lock(lock_);
}
void
lock() noexcept
{
spin_lock(lock_);
}
void
unlock() noexcept
{
spin_unlock(lock_);
}
};
//------------------------------------------------------------------------------
/** A Lockable interface to a packed spinlock implemented on top of an atomic.
Packed spinlocks offer tremendous space-efficient lock-sharding but
they come at a cost.
First, the implementation is necessarily low-level and uses advanced
features like memory ordering and highly platform-specific tricks to
@@ -78,26 +199,19 @@ spin_pause() noexcept
that it can, usually, outperform spinlocks.
@tparam T An unsigned integral type (e.g. std::uint16_t)
*/
/** A class that grabs a single packed spinlock from an atomic integer.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
template <typename T>
requires(
std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free &&
requires(std::atomic<T>& a, T v) {
{ a.fetch_or(v) } -> std::same_as<T>;
{ a.fetch_and(v) } -> std::same_as<T>;
})
class packed_spinlock
{
// clang-format off
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
static_assert(
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
// clang-format on
private:
std::atomic<T>& bits_;
T const mask_;
@@ -114,7 +228,7 @@ public:
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
packed_spinlock(std::atomic<T>& lock, int index)
packed_spinlock(std::atomic<T>& lock, int index) noexcept
: bits_(lock), mask_(static_cast<T>(1) << index)
{
XRPL_ASSERT(
@@ -123,13 +237,13 @@ public:
}
[[nodiscard]] bool
try_lock()
try_lock() noexcept
{
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
}
void
lock()
lock() noexcept
{
while (!try_lock())
{
@@ -137,89 +251,18 @@ public:
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
detail::spin_pause();
}
}
void
unlock()
unlock() noexcept
{
bits_.fetch_and(~mask_, std::memory_order_release);
bits_.fetch_and(~mask_, std::memory_order::release);
}
};
/** A spinlock implemented on top of an atomic integer.
@note Using `packed_spinlock` and `spinlock` against the same underlying
atomic integer can result in `spinlock` not being able to actually
acquire the lock during periods of high contention, because of how
the two locks operate: `spinlock` will spin trying to grab all the
bits at once, whereas any given `packed_spinlock` will only try to
grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class spinlock
{
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
private:
std::atomic<T>& lock_;
public:
spinlock(spinlock const&) = delete;
spinlock&
operator=(spinlock const&) = delete;
/** Grabs the
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
spinlock(std::atomic<T>& lock) : lock_(lock)
{
}
[[nodiscard]] bool
try_lock()
{
T expected = 0;
return lock_.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order_acquire,
std::memory_order_relaxed);
}
void
lock()
{
while (!try_lock())
{
// The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while (lock_.load(std::memory_order_relaxed) != 0)
detail::spin_pause();
}
}
void
unlock()
{
lock_.store(0, std::memory_order_release);
}
};
/** @} */
} // namespace ripple
#endif
-307
View File
@@ -1,307 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <[email protected]>
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
#define BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
#include <atomic>
#include <iterator>
#include <type_traits>
namespace beast {
//------------------------------------------------------------------------------
template <class Container, bool IsConst>
class LockFreeStackIterator
{
protected:
using Node = typename Container::Node;
using NodePtr =
typename std::conditional<IsConst, Node const*, Node*>::type;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = typename Container::value_type;
using difference_type = typename Container::difference_type;
using pointer = typename std::conditional<
IsConst,
typename Container::const_pointer,
typename Container::pointer>::type;
using reference = typename std::conditional<
IsConst,
typename Container::const_reference,
typename Container::reference>::type;
LockFreeStackIterator() : m_node()
{
}
LockFreeStackIterator(NodePtr node) : m_node(node)
{
}
template <bool OtherIsConst>
explicit LockFreeStackIterator(
LockFreeStackIterator<Container, OtherIsConst> const& other)
: m_node(other.m_node)
{
}
LockFreeStackIterator&
operator=(NodePtr node)
{
m_node = node;
return static_cast<LockFreeStackIterator&>(*this);
}
LockFreeStackIterator&
operator++()
{
m_node = m_node->m_next.load();
return static_cast<LockFreeStackIterator&>(*this);
}
LockFreeStackIterator
operator++(int)
{
LockFreeStackIterator result(*this);
m_node = m_node->m_next;
return result;
}
NodePtr
node() const
{
return m_node;
}
reference
operator*() const
{
return *this->operator->();
}
pointer
operator->() const
{
return static_cast<pointer>(m_node);
}
private:
NodePtr m_node;
};
//------------------------------------------------------------------------------
template <class Container, bool LhsIsConst, bool RhsIsConst>
bool
operator==(
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
{
return lhs.node() == rhs.node();
}
template <class Container, bool LhsIsConst, bool RhsIsConst>
bool
operator!=(
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
{
return lhs.node() != rhs.node();
}
//------------------------------------------------------------------------------
/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
This stack is implemented using the same intrusive interface as List.
All mutations are lock-free.
The caller is responsible for preventing the "ABA" problem:
http://en.wikipedia.org/wiki/ABA_problem
@param Tag A type name used to distinguish lists and nodes, for
putting objects in multiple lists. If this parameter is
omitted, the default tag is used.
*/
template <class Element, class Tag = void>
class LockFreeStack
{
public:
class Node
{
public:
Node() : m_next(nullptr)
{
}
explicit Node(Node* next) : m_next(next)
{
}
Node(Node const&) = delete;
Node&
operator=(Node const&) = delete;
private:
friend class LockFreeStack;
template <class Container, bool IsConst>
friend class LockFreeStackIterator;
std::atomic<Node*> m_next;
};
public:
using value_type = Element;
using pointer = Element*;
using reference = Element&;
using const_pointer = Element const*;
using const_reference = Element const&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = LockFreeStackIterator<LockFreeStack<Element, Tag>, false>;
using const_iterator =
LockFreeStackIterator<LockFreeStack<Element, Tag>, true>;
LockFreeStack() : m_end(nullptr), m_head(&m_end)
{
}
LockFreeStack(LockFreeStack const&) = delete;
LockFreeStack&
operator=(LockFreeStack const&) = delete;
/** Returns true if the stack is empty. */
bool
empty() const
{
return m_head.load() == &m_end;
}
/** Push a node onto the stack.
The caller is responsible for preventing the ABA problem.
This operation is lock-free.
Thread safety:
Safe to call from any thread.
@param node The node to push.
@return `true` if the stack was previously empty. If multiple threads
are attempting to push, only one will receive `true`.
*/
// VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list?
bool
push_front(Node* node)
{
bool first;
Node* old_head = m_head.load(std::memory_order_relaxed);
do
{
first = (old_head == &m_end);
node->m_next = old_head;
} while (!m_head.compare_exchange_strong(
old_head,
node,
std::memory_order_release,
std::memory_order_relaxed));
return first;
}
/** Pop an element off the stack.
The caller is responsible for preventing the ABA problem.
This operation is lock-free.
Thread safety:
Safe to call from any thread.
@return The element that was popped, or `nullptr` if the stack
was empty.
*/
Element*
pop_front()
{
Node* node = m_head.load();
Node* new_head;
do
{
if (node == &m_end)
return nullptr;
new_head = node->m_next.load();
} while (!m_head.compare_exchange_strong(
node,
new_head,
std::memory_order_release,
std::memory_order_relaxed));
return static_cast<Element*>(node);
}
/** Return a forward iterator to the beginning or end of the stack.
Undefined behavior results if push_front or pop_front is called
while an iteration is in progress.
Thread safety:
Caller is responsible for synchronization.
*/
/** @{ */
iterator
begin()
{
return iterator(m_head.load());
}
iterator
end()
{
return iterator(&m_end);
}
const_iterator
begin() const
{
return const_iterator(m_head.load());
}
const_iterator
end() const
{
return const_iterator(&m_end);
}
const_iterator
cbegin() const
{
return const_iterator(m_head.load());
}
const_iterator
cend() const
{
return const_iterator(&m_end);
}
/** @} */
private:
Node m_end;
std::atomic<Node*> m_head;
};
} // namespace beast
#endif
+26 -518
View File
@@ -1,523 +1,31 @@
#ifndef HOOKMISC_INCLUDED
#define HOOKMISC_INCLUDED 1
#include <xrpl/basics/base_uint.h>
namespace ripple {
// RH TODO: there's definitely a mucher nicer way to do this, but it involves
// modifying the base_uint class and we don't want to do that yet.
static const std::array<ripple::uint256, 256> UINT256_BIT = {
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000001"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000002"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000004"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000008"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000010"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000020"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000040"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000080"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000100"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000200"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000400"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000800"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000001000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000002000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000004000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000008000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000010000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000020000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000040000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000080000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000100000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000200000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000400000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000800000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000001000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000002000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000004000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000008000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000010000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000020000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000040000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000080000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000100000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000200000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000400000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000800000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000001000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000002000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000004000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000008000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000010000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000020000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000040000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000080000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000100000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000200000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000400000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000800000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000001000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000002000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000004000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000008000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000010000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000020000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000040000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000080000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000100000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000200000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000400000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000800000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000001000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000002000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000004000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000008000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000010000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000020000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000040000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000080000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000100000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000200000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000400000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000800000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000001000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000002000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000004000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000008000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000010000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000020000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000040000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000080000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000100000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000200000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000400000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000800000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000001000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000002000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000004000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000008000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000010000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000020000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000040000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000080000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000100000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000200000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000400000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000800000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000001000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000002000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000004000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000008000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000010000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000020000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000040000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000080000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000100000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000200000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000400000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000800000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000001000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000002000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000004000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000008000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000010000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000020000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000040000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000080000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000100000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000200000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000400000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000800000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000001000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000002000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000004000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000008000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000010000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000020000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000040000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000080000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000100000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000200000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000400000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000800000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000001000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000002000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000004000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000008000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000010000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000020000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000040000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000080000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000100000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000200000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000400000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000800000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000001000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000002000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000004000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000008000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000010000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000020000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000040000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000080000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000100000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000200000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000400000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000800000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000001000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000002000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000004000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000008000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000010000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000020000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000040000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000080000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000100000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000200000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000400000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000800000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000001000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000002000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000004000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000008000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000010000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000020000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000040000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000080000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000100000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000200000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000400000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000800000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000001000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000002000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000004000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000008000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000010000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000020000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000040000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000080000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000100000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000200000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000400000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000800000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000001000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000002000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000004000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000008000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000010000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000020000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000040000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000080000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000100000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000200000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000400000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000800000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000001000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000002000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000004000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000008000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000010000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000020000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000040000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000080000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000100000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000200000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000400000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000800000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000001000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000002000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000004000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000008000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000010000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000020000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000040000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000080000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000100000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000200000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000400000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000800000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000001000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000002000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000004000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000008000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000010000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000020000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000040000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000080000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000100000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000200000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000400000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000800000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0001000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0002000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0004000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0008000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0010000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0020000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0040000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0080000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0100000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0200000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0400000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0800000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"1000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"2000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"4000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"8000000000000000000000000000000000000000000000000000000000000000")};
#include <xrpl/basics/base_uint.h>
#include <array>
#include <cstdint>
namespace ripple {
/** UINT256_BIT[i] is the 256-bit value with only bit i set (LSB is bit 0).
Used to encode and test the per-transaction-type bits of the HookOn and
HookCanEmit fields. Built at compile time.
*/
inline constexpr auto UINT256_BIT = []() consteval {
std::array<ripple::uint256, 256> table{};
for (int i = 0; i < 256; ++i)
{
std::array<std::uint8_t, 32> bytes{};
bytes[31 - (i / 8)] = 1 << (i % 8);
table[i] = ripple::uint256(bytes);
}
return table;
}();
} // namespace ripple
#endif
+6 -1
View File
@@ -157,7 +157,12 @@ enum error_code_i {
// Simulate
rpcTX_SIGNED = 97,
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
// ledger_entry
rpcENTRY_NOT_FOUND = 98,
rpcUNEXPECTED_LEDGER_TYPE = 99,
rpcLAST =
rpcUNEXPECTED_LEDGER_TYPE // rpcLAST should always equal the last code.
};
/** Codes returned in the `warnings` array of certain RPC commands.
+3
View File
@@ -84,6 +84,9 @@ std::uint16_t constexpr maxTransferFee = 50000;
/** The maximum length of a URI inside an NFT */
std::size_t constexpr maxTokenURILength = 256;
/** The maximum length of a URI inside an URIToken */
std::size_t constexpr maxURITokenURILength = 256;
/** The maximum length of a Data element inside a DID */
std::size_t constexpr maxDIDDocumentLength = 256;
+42 -83
View File
@@ -24,6 +24,7 @@
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/STExchange.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/json_get_or_throw.h>
#include <xrpl/protocol/tokens.h>
@@ -36,6 +37,26 @@
namespace ripple {
/** Returns the type of public key.
@return std::nullopt If the public key does not
represent a known type.
*/
[[nodiscard]] inline std::optional<KeyType>
publicKeyType(Slice const& slice)
{
if (slice.size() == 33)
{
if (slice[0] == 0xED)
return KeyType::ed25519;
if (slice[0] == 0x02 || slice[0] == 0x03)
return KeyType::secp256k1;
}
return std::nullopt;
}
/** A public key.
Public keys are used in the public-key cryptography
@@ -58,77 +79,47 @@ namespace ripple {
prefix constant 0xED, followed by 32 bytes of
public key data.
*/
class PublicKey
class PublicKey : public detail::KeyBase<PublicKey, 33>
{
protected:
// All the constructed public keys are valid, non-empty and contain 33
// bytes of data.
static constexpr std::size_t size_ = 33;
std::uint8_t buf_[size_]; // should be large enough
public:
using const_iterator = std::uint8_t const*;
public:
PublicKey() = delete;
PublicKey(PublicKey const& other);
PublicKey(PublicKey const&) = default;
PublicKey&
operator=(PublicKey const& other);
operator=(PublicKey const&) = default;
/** Create a public key.
Preconditions:
publicKeyType(slice) != std::nullopt
*/
explicit PublicKey(Slice const& slice);
std::uint8_t const*
data() const noexcept
explicit PublicKey(Slice const& slice) noexcept
{
return buf_;
if (slice.size() < buf_.size())
LogicError("PublicKey::PublicKey: undersized buffer");
if (!publicKeyType(slice))
LogicError("PublicKey::PublicKey: invalid type");
std::copy_n(slice.data(), buf_.size(), buf_.data());
}
std::size_t
size() const noexcept
explicit PublicKey(value_t const& data) noexcept
: PublicKey(makeSlice(data))
{
return size_;
}
const_iterator
begin() const noexcept
{
return buf_;
}
const_iterator
cbegin() const noexcept
{
return buf_;
}
const_iterator
end() const noexcept
{
return buf_ + size_;
}
const_iterator
cend() const noexcept
{
return buf_ + size_;
}
Slice
[[nodiscard]] Slice
slice() const noexcept
{
return {buf_, size_};
return {buf_.data(), buf_.size()};
}
operator Slice() const noexcept
{
return slice();
}
[[nodiscard]] auto
operator<=>(PublicKey const& rhs) const
{
return buf_ <=> rhs.buf_;
}
};
/** Print the public key to a stream.
@@ -136,22 +127,6 @@ public:
std::ostream&
operator<<(std::ostream& os, PublicKey const& pk);
inline bool
operator==(PublicKey const& lhs, PublicKey const& rhs)
{
return std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}
inline bool
operator<(PublicKey const& lhs, PublicKey const& rhs)
{
return std::lexicographical_compare(
lhs.data(),
lhs.data() + lhs.size(),
rhs.data(),
rhs.data() + rhs.size());
}
template <class Hasher>
void
hash_append(Hasher& h, PublicKey const& pk)
@@ -221,22 +196,6 @@ enum class ECDSACanonicality { canonical, fullyCanonical };
std::optional<ECDSACanonicality>
ecdsaCanonicality(Slice const& sig);
/** Returns the type of public key.
@return std::nullopt If the public key does not
represent a known type.
*/
/** @{ */
[[nodiscard]] std::optional<KeyType>
publicKeyType(Slice const& slice);
[[nodiscard]] inline std::optional<KeyType>
publicKeyType(PublicKey const& publicKey)
{
return publicKeyType(publicKey.slice());
}
/** @} */
/** Verify a secp256k1 signature on the digest of a message. */
[[nodiscard]] bool
verifyDigest(
+10 -61
View File
@@ -25,90 +25,39 @@
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h>
#include <array>
#include <cstring>
#include <string>
namespace ripple {
/** A secret key. */
class SecretKey
class SecretKey : public detail::KeyBase<SecretKey, 32>
{
private:
std::uint8_t buf_[32];
public:
using const_iterator = std::uint8_t const*;
SecretKey() = delete;
SecretKey(SecretKey const&) = default;
SecretKey&
operator=(SecretKey const&) = default;
/** Destroy the secret key. This will try to securely erase the buffer. */
~SecretKey();
SecretKey(std::array<std::uint8_t, 32> const& data);
SecretKey(Slice const& slice);
std::uint8_t const*
data() const
SecretKey(Slice const& slice)
{
return buf_;
if (slice.size() != buf_.size())
LogicError("SecretKey::SecretKey: invalid size");
std::copy_n(slice.data(), buf_.size(), buf_.data());
}
std::size_t
size() const
explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
{
return sizeof(buf_);
}
/** Convert the secret key to a hexadecimal string.
@note The operator<< function is deliberately omitted
to avoid accidental exposure of secret key material.
*/
std::string
to_string() const;
const_iterator
begin() const noexcept
{
return buf_;
}
const_iterator
cbegin() const noexcept
{
return buf_;
}
const_iterator
end() const noexcept
{
return buf_ + sizeof(buf_);
}
const_iterator
cend() const noexcept
{
return buf_ + sizeof(buf_);
}
};
inline bool
operator==(SecretKey const& lhs, SecretKey const& rhs)
{
return lhs.size() == rhs.size() &&
std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}
inline bool
operator!=(SecretKey const& lhs, SecretKey const& rhs)
{
return !(lhs == rhs);
}
//------------------------------------------------------------------------------
/** Parse a secret key */
+11 -46
View File
@@ -22,72 +22,37 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array>
#include <optional>
namespace ripple {
/** Seeds are used to generate deterministic secret keys. */
class Seed
class Seed : public detail::KeyBase<Seed, 16>
{
private:
std::array<uint8_t, 16> buf_;
public:
using const_iterator = std::array<uint8_t, 16>::const_iterator;
Seed() = delete;
Seed(Seed const&) = default;
Seed&
operator=(Seed const&) = default;
/** Destroy the seed.
The buffer will first be securely erased.
*/
/** Destroy the seed. This will attempt to securely erase the buffer. */
~Seed();
/** Construct a seed */
/** @{ */
explicit Seed(Slice const& slice);
explicit Seed(uint128 const& seed);
/** @} */
std::uint8_t const*
data() const
explicit Seed(Slice const& slice)
{
return buf_.data();
if (slice.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
std::copy_n(slice.data(), buf_.size(), buf_.data());
}
std::size_t
size() const
explicit Seed(span_t seed) noexcept : KeyBase(seed)
{
return buf_.size();
}
const_iterator
begin() const noexcept
{
return buf_.begin();
}
const_iterator
cbegin() const noexcept
{
return buf_.cbegin();
}
const_iterator
end() const noexcept
{
return buf_.end();
}
const_iterator
cend() const noexcept
{
return buf_.cend();
}
};
+3 -12
View File
@@ -24,18 +24,14 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <string>
#include <string_view>
namespace ripple {
// Various protocol and system specific constant globals.
/* The name of the system. */
static inline std::string const&
systemName()
{
static std::string const name = "xahau";
return name;
}
inline constexpr std::string_view systemName = "xahau";
/** Configure the native currency. */
@@ -58,12 +54,7 @@ isLegalAmountSigned(XRPAmount const& amount)
}
/* The currency code for the native currency. */
static inline std::string const&
systemCurrencyCode()
{
static std::string const code = "XAH";
return code;
}
inline constexpr std::string_view systemCurrencyCode = "XAH";
/** The XRP ledger network's earliest allowed sequence */
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
+166 -57
View File
@@ -24,84 +24,166 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SystemParameters.h>
namespace ripple {
namespace detail {
class CurrencyTag
{
public:
explicit CurrencyTag() = default;
};
class DirectoryTag
{
public:
explicit DirectoryTag() = default;
};
class NodeIDTag
{
public:
explicit NodeIDTag() = default;
};
} // namespace detail
using LedgerHash = uint256;
/** Directory is an index into the directory of offer books.
The last 64 bits of this are the quality. */
using Directory = base_uint<256, detail::DirectoryTag>;
using Directory = base_uint<256, struct DirectoryTag>;
/** Currency is a hash representing a specific currency. */
using Currency = base_uint<160, detail::CurrencyTag>;
using Currency = base_uint<160, struct CurrencyTag>;
/** NodeID is a 160-bit hash representing one node. */
using NodeID = base_uint<160, detail::NodeIDTag>;
using NodeID = base_uint<160, struct NodeIDTag>;
/** MPTID is a 192-bit value representing MPT Issuance ID,
* which is a concatenation of a 32-bit sequence (big endian)
* and a 160-bit account */
/** A Multi-Purpose Token Issuance ID,
The ID is the a concatenation of a 32-bit sequence number,
in big endian, and a 160-bit account.
@note This type is, unfortunately, untagged because the authors
of the original code used a deserialization APIs that did
not support tags. It should be fixed.
*/
using MPTID = base_uint<192>;
namespace detail {
// A table mapping which characters we are willing to allow in the ASCII
// representation of a three-letter currency code.
inline constexpr auto validIsoChars = []() consteval {
std::string_view isoCharSet =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"<>(){}[]|?!@#$%^&*";
std::array<bool, 256> table{};
for (unsigned char c : isoCharSet)
table[c] = true;
return table;
}();
// Determines if the given code is composed entirely of letters valid in
// ISO 4217 codes.
[[nodiscard]] inline constexpr bool
isIsoCode(std::string_view code) noexcept
{
return std::all_of(code.begin(), code.end(), [](char c) {
return validIsoChars[static_cast<unsigned char>(c)];
});
}
// The location (in bytes) of the 3 digit currency inside a 160-bit value
inline constexpr std::size_t isoCodeOffset = 12;
// The length of an ISO-4217 like code
inline constexpr std::size_t isoCodeLength = 3;
inline constexpr Currency isoMaskBits = ~Currency(0xFFFFFF0000000000);
// The special currency identifier for the system currency: all-zero
inline constexpr Currency xrpCurrency{0x0000000000000000};
// The special currency identifier meaning "no currency"
inline constexpr Currency noCurrency{0x0000000000000001};
// The system currency identifier in "ISO4217 format" which is reserved.
// We derive this value dynamically from the system currency code.
inline constexpr Currency badCurrency = []() consteval {
if (systemCurrencyCode.size() != isoCodeLength)
throw "Incorrect systemCurrencyCode size (must be 3 digits)";
std::array<std::uint8_t, Currency::bytes> bytes{};
for (std::size_t i = 0; i < isoCodeLength; ++i)
bytes[isoCodeOffset + i] =
static_cast<std::uint8_t>(systemCurrencyCode[i]);
return Currency(bytes);
}();
// We take advantage of the fact that an ASCII value in the [a-z] range
// transforms into the equivalent character in the [A-Z] range when you
// AND it with 0xDF, so we can compare against all possible variants of
// "XAH" in one go. Every byte outside the ISO code position is kept as-is
// by the mask, so a currency is only "bad" when those bytes are all zero
// (i.e. the value is exactly one of the case variants of the code).
inline constexpr Currency badCurrencyCodeMask =
isoMaskBits | Currency{0xDFDFDF0000000000};
} // namespace detail
/** XRP currency. */
Currency const&
xrpCurrency();
[[nodiscard]] constexpr Currency const&
xrpCurrency() noexcept
{
return detail::xrpCurrency;
}
[[nodiscard]] constexpr bool
isXRP(Currency const& c) noexcept
{
return c == detail::xrpCurrency;
}
/** A placeholder for empty currencies. */
Currency const&
noCurrency();
[[nodiscard]] constexpr Currency const&
noCurrency() noexcept
{
return detail::noCurrency;
}
/** We deliberately disallow the currency that looks like "XAH" because too
many people were using it instead of the correct XAH currency. */
Currency const&
badCurrency();
inline bool
isXRP(Currency const& c)
[[nodiscard]] constexpr Currency const&
badCurrency() noexcept
{
return c == beast::zero;
return detail::badCurrency;
}
inline bool
isBadCurrency(Currency const& c)
[[nodiscard]] constexpr bool
isBadCurrency(Currency const& c) noexcept
{
static const std::set<Currency> badCurrencies{
Currency(0x7861680000000000), // xah
Currency(0x7861480000000000), // xaH
Currency(0x7841680000000000), // xAh
Currency(0x7841480000000000), // xAH
Currency(0x5861680000000000), // Xah
Currency(0x5861480000000000), // XaH
Currency(0x5841680000000000), // XAh
Currency(0x5841480000000000) // XAH
};
return badCurrencies.find(c) != badCurrencies.end();
// We take advantage of the fact that an ASCII value in the [a-z] range
// transforms into the equivalent character in the [A-Z] range when you
// AND it with 0xDF, so we can compare against all possible variants of
// the bad currency code in one go:
return (c & detail::badCurrencyCodeMask) == badCurrency();
}
/** Returns "", "XAH", or three letter ISO code. */
std::string
to_string(Currency const& c);
/** Returns "", "XAH", three letter ISO code or the hex representation. */
[[nodiscard]] inline std::string
to_string(Currency const& currency)
{
if (currency == xrpCurrency())
return std::string{systemCurrencyCode};
if (currency == noCurrency())
return "1";
if ((currency & detail::isoMaskBits) == beast::zero)
{
std::string_view const iso(
reinterpret_cast<char const*>(currency.data()) +
detail::isoCodeOffset,
detail::isoCodeLength);
// Specifying the system currency code using ISO-style representation
// is not allowed. Note that the check is case-sensitive; yet another
// instance of legacy code smell.
if (detail::isIsoCode(iso) && iso != systemCurrencyCode)
return std::string{iso};
}
return strHex(currency);
}
/** Tries to convert a string to a Currency, returns true on success.
@@ -110,8 +192,29 @@ to_string(Currency const& c);
will require very careful checking everywhere and may mean having
to rewrite some unit test code.
*/
bool
to_currency(Currency&, std::string const&);
[[nodiscard]] constexpr bool
to_currency(Currency& currency, std::string_view code) noexcept
{
if (code.empty() || code == systemCurrencyCode)
{
currency = xrpCurrency();
return true;
}
// Handle ISO-4217-like 3-digit character codes.
if (code.size() != detail::isoCodeLength)
return currency.parseHex(code);
if (!detail::isIsoCode(code))
return false;
currency = beast::zero;
std::copy_n(
code.data(), code.size(), currency.begin() + detail::isoCodeOffset);
return true;
}
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
@@ -119,8 +222,14 @@ to_currency(Currency&, std::string const&);
unfortunate; changing this will require very careful checking
everywhere and may mean having to rewrite some unit test code.
*/
Currency
to_currency(std::string const&);
[[nodiscard]] constexpr Currency
to_currency(std::string_view code) noexcept
{
if (Currency currency; to_currency(currency, code))
return currency;
return noCurrency();
}
inline std::ostream&
operator<<(std::ostream& os, Currency const& x)
+114
View File
@@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
/*
This file is part of xahaud: https://github.com/xahau/xahaud
Copyright (c) 2026, the Xahaud developers.
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
#define XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
#include <algorithm>
#include <array>
#include <cstdint>
#include <span>
namespace ripple {
namespace detail {
/** Common base for fixed-size key and seed types.
This CRTP base provides storage, accessors, and equality comparison
for types like seeds and secret/public keys, which wrap a fixed-size
byte array. The Derived parameter ensures that unrelated types with
the same size do not implicitly interoperate with one another.
Equality comparison is provided automatically. Derived classes that
want to can opt in to ordering by declaring operator<=> as a
hidden friend.
@tparam Derived The concrete type inheriting from this base (CRTP).
@tparam N The size of the underlying byte array.
*/
template <class Derived, std::size_t N>
class KeyBase
{
public:
/** The type we use to store the data. */
using value_t = std::array<std::uint8_t, N>;
/** The span type corresponding to the type we use to store the data. */
using span_t = std::span<std::uint8_t, N>;
protected:
value_t buf_;
KeyBase() noexcept = default;
explicit KeyBase(span_t data) noexcept
{
std::copy_n(data.data(), N, buf_.data());
}
public:
[[nodiscard]] std::uint8_t const*
data() const
{
return buf_.data();
}
[[nodiscard]] std::size_t
size() const
{
return buf_.size();
}
[[nodiscard]] auto
begin() const noexcept
{
return buf_.begin();
}
[[nodiscard]] auto
cbegin() const noexcept
{
return buf_.cbegin();
}
[[nodiscard]] auto
end() const noexcept
{
return buf_.end();
}
[[nodiscard]] auto
cend() const noexcept
{
return buf_.cend();
}
[[nodiscard]] friend bool
operator==(Derived const& lhs, Derived const& rhs)
{
// This is not a constant time comparison. This is probably OK
// for the xahaud codebase.
return lhs.buf_ == rhs.buf_;
}
};
} // namespace detail
} // namespace ripple
#endif
+4
View File
@@ -100,11 +100,15 @@ JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(Invalid); //
JSS(Issuer); // in: Credential transactions
JSS(IssuingChainDoor); // field.
JSS(IssuingChainIssue); // field.
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(FirstLedgerSequence); // in: TransactionSign; field
JSS(LastUpdateTime); // field.
JSS(LimitAmount); // field.
JSS(LockingChainDoor); // field.
JSS(LockingChainIssue); // field.
JSS(NetworkID); // field.
JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens
JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens
-58
View File
@@ -1,58 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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 <xrpl/basics/CountedObject.h>
#include <algorithm>
#include <type_traits>
namespace ripple {
CountedObjects&
CountedObjects::getInstance() noexcept
{
static CountedObjects instance;
return instance;
}
CountedObjects::CountedObjects() noexcept : m_count(0), m_head(nullptr)
{
}
CountedObjects::List
CountedObjects::getCounts(int minimumThreshold) const
{
List counts;
// When other operations are concurrent, the count
// might be temporarily less than the actual count.
counts.reserve(m_count.load());
for (auto* ctr = m_head.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
}
std::sort(counts.begin(), counts.end());
return counts;
}
} // namespace ripple
+1 -14
View File
@@ -25,19 +25,6 @@
namespace ripple {
namespace detail {
[[noreturn]] void
accessViolation() noexcept
{
// dereference memory location zero
int volatile* j = 0;
(void)*j;
std::abort();
}
} // namespace detail
void
LogThrow(std::string const& title)
{
@@ -55,7 +42,7 @@ LogicError(std::string const& s) noexcept
// here.
// For the above reasons, we want this contract to stand out.
UNREACHABLE("LogicError", {{"message", s}});
detail::accessViolation();
std::abort();
}
} // namespace ripple
+7 -7
View File
@@ -33,7 +33,7 @@
namespace beast::detail {
inline void
setCurrentThreadNameImpl(std::string_view name)
setCurrentThreadNameImpl(char const* name)
{
#if DEBUG && BOOST_COMP_MSVC
// This technique is documented by Microsoft and works for all versions
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(std::string_view name)
THREADNAME_INFO ni;
ni.dwType = 0x1000;
ni.szName = name.data();
ni.szName = name;
ni.dwThreadID = GetCurrentThreadId();
ni.dwFlags = 0;
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(std::string_view name)
namespace beast::detail {
inline void
setCurrentThreadNameImpl(std::string_view name)
setCurrentThreadNameImpl(char const* name)
{
pthread_setname_np(name.data());
pthread_setname_np(name);
}
} // namespace beast::detail
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(std::string_view name)
namespace beast::detail {
inline void
setCurrentThreadNameImpl(std::string_view name)
setCurrentThreadNameImpl(char const* name)
{
pthread_setname_np(pthread_self(), name.data());
pthread_setname_np(pthread_self(), name);
}
} // namespace beast::detail
@@ -119,7 +119,7 @@ void
setCurrentThreadName(std::string_view name)
{
detail::threadName = name;
detail::setCurrentThreadNameImpl(name);
detail::setCurrentThreadNameImpl(detail::threadName.c_str());
}
} // namespace beast
+4 -3
View File
@@ -29,7 +29,9 @@ bool
is_private(AddressV6 const& addr)
{
return (
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
addr.is_link_local() || // fe80::/10
addr.is_loopback() || // ::1
((addr.to_bytes()[0] & 0xfe) == 0xfc) || // fc00::/7 (all ULA)
(addr.is_v4_mapped() &&
is_private(boost::asio::ip::make_address_v4(
boost::asio::ip::v4_mapped, addr))));
@@ -38,8 +40,7 @@ is_private(AddressV6 const& addr)
bool
is_public(AddressV6 const& addr)
{
// TODO is this correct?
return !is_private(addr) && !addr.is_multicast();
return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
}
} // namespace IP
+5 -1
View File
@@ -23,6 +23,8 @@
#include <xrpl/json/json_writer.h>
#include <xrpl/json/to_string.h>
#include <cmath>
namespace Json {
const Value Value::null;
@@ -672,7 +674,9 @@ Value::isConvertibleTo(ValueType other) const
(other == intValue && value_.real_ >= minInt &&
value_.real_ <= maxInt) ||
(other == uintValue && value_.real_ >= 0 &&
value_.real_ <= maxUInt) ||
value_.real_ <= maxUInt &&
std::fabs(round(value_.real_) - value_.real_) <
std::numeric_limits<double>::epsilon()) ||
other == realValue || other == stringValue ||
other == booleanValue;
+4 -1
View File
@@ -113,7 +113,10 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
{rpcLEDGER_MISSING, "ledgerMissing", "One or more ledgers in the specified range is missing", 406},
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
{rpcENTRY_NOT_FOUND, "entryNotFound", "Entry not found.", 400},
{rpcUNEXPECTED_LEDGER_TYPE, "unexpectedLedgerType", "Unexpected ledger type.", 400},
};
// clang-format on
// Sort and validate unorderedErrorInfos at compile time. Should be
-47
View File
@@ -171,53 +171,6 @@ ed25519Canonical(Slice const& sig)
return std::lexicographical_compare(S, S + 32, Order, Order + 32);
}
//------------------------------------------------------------------------------
PublicKey::PublicKey(Slice const& slice)
{
if (slice.size() < size_)
LogicError(
"PublicKey::PublicKey - Input slice cannot be an undersized "
"buffer");
if (!publicKeyType(slice))
LogicError("PublicKey::PublicKey invalid type");
std::memcpy(buf_, slice.data(), size_);
}
PublicKey::PublicKey(PublicKey const& other)
{
std::memcpy(buf_, other.buf_, size_);
}
PublicKey&
PublicKey::operator=(PublicKey const& other)
{
if (this != &other)
{
std::memcpy(buf_, other.buf_, size_);
}
return *this;
}
//------------------------------------------------------------------------------
std::optional<KeyType>
publicKeyType(Slice const& slice)
{
if (slice.size() == 33)
{
if (slice[0] == 0xED)
return KeyType::ed25519;
if (slice[0] == 0x02 || slice[0] == 0x03)
return KeyType::secp256k1;
}
return std::nullopt;
}
bool
verifyDigest(
PublicKey const& publicKey,
+1 -1
View File
@@ -961,7 +961,7 @@ amountFromJson(SField const& name, Json::Value const& v)
bool const native = !currencyOrMPTID.isString() ||
currencyOrMPTID.asString().empty() ||
(currencyOrMPTID.asString() == systemCurrencyCode());
(currencyOrMPTID.asString() == systemCurrencyCode);
if (native)
{
+8 -10
View File
@@ -95,12 +95,10 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
};
checkExtra(v);
Json::Value const& lockingChainDoorStr =
v[sfLockingChainDoor.getJsonName()];
Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()];
Json::Value const& issuingChainDoorStr =
v[sfIssuingChainDoor.getJsonName()];
Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()];
Json::Value const& lockingChainDoorStr = v[jss::LockingChainDoor];
Json::Value const& lockingChainIssue = v[jss::LockingChainIssue];
Json::Value const& issuingChainDoorStr = v[jss::IssuingChainDoor];
Json::Value const& issuingChainIssue = v[jss::IssuingChainIssue];
if (!lockingChainDoorStr.isString())
{
@@ -158,10 +156,10 @@ Json::Value
STXChainBridge::getJson(JsonOptions jo) const
{
Json::Value v;
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
return v;
}
+3 -21
View File
@@ -25,6 +25,7 @@
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/detail/secp256k1.h>
#include <xrpl/protocol/digest.h>
#include <cstring>
#include <ed25519.h>
@@ -32,25 +33,7 @@ namespace ripple {
SecretKey::~SecretKey()
{
secure_erase(buf_, sizeof(buf_));
}
SecretKey::SecretKey(std::array<std::uint8_t, 32> const& key)
{
std::memcpy(buf_, key.data(), key.size());
}
SecretKey::SecretKey(Slice const& slice)
{
if (slice.size() != sizeof(buf_))
LogicError("SecretKey::SecretKey: invalid size");
std::memcpy(buf_, slice.data(), sizeof(buf_));
}
std::string
SecretKey::to_string() const
{
return strHex(*this);
secure_erase(buf_.data(), buf_.size());
}
namespace detail {
@@ -214,7 +197,6 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
if (publicKeyType(pk.slice()) != KeyType::secp256k1)
LogicError("sign: secp256k1 required for digest signing");
BOOST_ASSERT(sk.size() == 32);
secp256k1_ecdsa_signature sig_imp;
if (secp256k1_ecdsa_sign(
secp256k1Context(),
@@ -226,7 +208,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
LogicError("sign: secp256k1_ecdsa_sign failed");
unsigned char sig[72];
size_t len = sizeof(sig);
auto len = sizeof(sig);
if (secp256k1_ecdsa_signature_serialize_der(
secp256k1Context(), sig, &len, &sig_imp) != 1)
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
+3 -19
View File
@@ -39,28 +39,12 @@ Seed::~Seed()
secure_erase(buf_.data(), buf_.size());
}
Seed::Seed(Slice const& slice)
{
if (slice.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
std::memcpy(buf_.data(), slice.data(), buf_.size());
}
Seed::Seed(uint128 const& seed)
{
if (seed.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
std::memcpy(buf_.data(), seed.data(), buf_.size());
}
//------------------------------------------------------------------------------
Seed
randomSeed()
{
std::array<std::uint8_t, 16> buffer;
beast::rngfill(buffer.data(), buffer.size(), crypto_prng());
Seed seed(makeSlice(buffer));
beast::rngfill(buffer, crypto_prng());
Seed seed(buffer);
secure_erase(buffer.data(), buffer.size());
return seed;
}
@@ -117,7 +101,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
if (RFC1751::getKeyFromEnglish(key, str) == 1)
{
Blob const blob(key.rbegin(), key.rend());
return Seed{uint128{blob}};
return Seed{makeSlice(blob)};
}
}
-135
View File
@@ -1,135 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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 <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/UintTypes.h>
#include <string_view>
namespace ripple {
// For details on the protocol-level serialization please visit
// https://xrpl.org/serialization.html#currency-codes
namespace detail {
// Characters we are willing to allow in the ASCII representation of a
// three-letter currency code.
constexpr std::string_view isoCharSet =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"<>(){}[]|?!@#$%^&*";
// The location (in bytes) of the 3 digit currency inside a 160-bit value
constexpr std::size_t isoCodeOffset = 12;
// The length of an ISO-4217 like code
constexpr std::size_t isoCodeLength = 3;
} // namespace detail
std::string
to_string(Currency const& currency)
{
if (currency == beast::zero)
return systemCurrencyCode();
if (currency == noCurrency())
return "1";
static constexpr Currency sIsoBits(
"FFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFF");
if ((currency & sIsoBits).isZero())
{
std::string const iso(
currency.data() + detail::isoCodeOffset,
currency.data() + detail::isoCodeOffset + detail::isoCodeLength);
// Specifying the system currency code using ISO-style representation
// is not allowed.
if ((iso != systemCurrencyCode()) &&
(iso.find_first_not_of(detail::isoCharSet) == std::string::npos))
{
return iso;
}
}
return strHex(currency);
}
bool
to_currency(Currency& currency, std::string const& code)
{
if (code.empty() || !code.compare(systemCurrencyCode()))
{
currency = beast::zero;
return true;
}
// Handle ISO-4217-like 3-digit character codes.
if (code.size() == detail::isoCodeLength)
{
if (code.find_first_not_of(detail::isoCharSet) != std::string::npos)
return false;
currency = beast::zero;
std::copy(
code.begin(), code.end(), currency.begin() + detail::isoCodeOffset);
return true;
}
return currency.parseHex(code);
}
Currency
to_currency(std::string const& code)
{
Currency currency;
if (!to_currency(currency, code))
currency = noCurrency();
return currency;
}
Currency const&
xrpCurrency()
{
static Currency const currency(beast::zero);
return currency;
}
Currency const&
noCurrency()
{
static Currency const currency(1);
return currency;
}
Currency const&
badCurrency()
{
static Currency const currency(0x5841480000000000);
return currency;
}
} // namespace ripple
+2 -2
View File
@@ -69,7 +69,7 @@ HTTPReply(
// CHECKME this returns a different version than the replies below. Is
// this by design or an accident or should it be using
// BuildInfo::getFullVersionString () as well?
output("Server: " + systemName() + "-json-rpc/v1");
output("Server: " + std::string(systemName) + "-json-rpc/v1");
output("\r\n");
// Be careful in modifying this! If you change the contents you MUST
@@ -147,7 +147,7 @@ HTTPReply(
"\r\n"
"Content-Type: application/json; charset=UTF-8\r\n");
output("Server: " + systemName() + "-json-rpc/");
output("Server: " + std::string(systemName) + "-json-rpc/");
output(BuildInfo::getFullVersionString());
output(
"\r\n"
+74
View File
@@ -1271,6 +1271,79 @@ public:
BEAST_EXPECT(table->needValidatedLedger(257));
}
void
testIgnoredUnsupported()
{
testcase("ignore_amendments");
using namespace std::chrono_literals;
weeks constexpr w(1);
test::jtx::Env env{*this, makeConfig()};
static std::vector<AmendmentTable::FeatureInfo> const supported =
makeDefaultYes(yes_);
// Ignore every unsupported_ amendment except the first one.
BEAST_EXPECT(unsupported_.size() > 1);
std::vector<std::string> const ignoredNames(
unsupported_.begin() + 1, unsupported_.end());
auto table = make_AmendmentTable(
env.app(),
w,
supported,
emptySection_,
emptySection_,
journal_,
makeSection(ignoredNames));
for (auto const& a : ignoredNames)
BEAST_EXPECT(table->isSupported(amendmentId(a)));
BEAST_EXPECT(!table->isSupported(amendmentId(unsupported_[0])));
majorityAmendments_t const majority;
// Enabling only ignored amendments must not block.
std::set<uint256> enabled;
for (auto const& a : ignoredNames)
enabled.insert(amendmentId(a));
table->doValidatedLedger(1, enabled, majority);
BEAST_EXPECT(!table->hasUnsupportedEnabled());
BEAST_EXPECT(table->unsupportedAmendments().empty());
// An ignored amendment must never be voted for.
auto const desired = table->getDesired();
for (auto const& a : ignoredNames)
BEAST_EXPECT(
std::find(desired.begin(), desired.end(), amendmentId(a)) ==
desired.end());
// A non-ignored unsupported amendment still blocks.
enabled.insert(amendmentId(unsupported_[0]));
table->doValidatedLedger(2, enabled, majority);
BEAST_EXPECT(table->hasUnsupportedEnabled());
// Bad entries are rejected.
Section bad("ignore_amendments");
bad.append("NotARealFeatureName");
try
{
make_AmendmentTable(
env.app(),
w,
supported,
emptySection_,
emptySection_,
journal_,
bad);
fail("expected bad [ignore_amendments] entry to throw");
}
catch (std::runtime_error const&)
{
pass();
}
}
void
testFeature(FeatureBitset const& feat)
{
@@ -1294,6 +1367,7 @@ public:
testBadConfig();
testEnableVeto();
testHasUnsupported();
testIgnoredUnsupported();
testFeature(all - fixMajorityCalc);
testFeature(all);
}
+4 -9
View File
@@ -529,11 +529,10 @@ public:
std::vector<JobName> jobs;
{
auto const& jobTypes = JobTypes::instance();
jobs.reserve(jobTypes.size());
for (auto const& job : jobTypes)
{
jobs.emplace_back(job.first, job.second.name());
jobs.emplace_back(job.type, std::string(job.name));
}
}
std::shuffle(jobs.begin(), jobs.end(), default_prng());
@@ -865,14 +864,10 @@ public:
JobType jobType;
std::string jobTypeName;
{
auto const& jobTypes = JobTypes::instance();
auto index = rand_int<std::size_t>(0, jobTypes.size() - 1);
std::uniform_int_distribution<> dis(0, jobTypes.size() - 1);
auto iter{jobTypes.begin()};
std::advance(iter, dis(default_prng()));
jobType = iter->second.type();
jobTypeName = iter->second.name();
jobType = jobTypes[index].type;
jobTypeName = jobTypes[index].name;
}
// Say there's one worker thread.
+4 -7
View File
@@ -307,11 +307,9 @@ struct base_uint_test : beast::unit_test::suite
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::invalid_argument const& e)
catch (std::exception const& e)
{
BEAST_EXPECT(
e.what() ==
std::string("invalid length for hex string"));
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
caught = true;
}
BEAST_EXPECT(caught);
@@ -327,10 +325,9 @@ struct base_uint_test : beast::unit_test::suite
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::range_error const& e)
catch (std::exception const& e)
{
BEAST_EXPECT(
e.what() == std::string("invalid hex character"));
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
caught = true;
}
BEAST_EXPECT(caught);
+29
View File
@@ -324,6 +324,35 @@ public:
BEAST_EXPECT(!is_loopback(ep));
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep));
// a link-local IPv6 is private
ep = Endpoint(boost::asio::ip::make_address_v6("fe80::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(is_private(ep));
BEAST_EXPECT(!is_loopback(ep));
// the IPv6 loopback is private
ep = Endpoint(boost::asio::ip::make_address_v6("::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(is_private(ep));
BEAST_EXPECT(is_loopback(ep));
// a global unicast IPv6 is public
ep = Endpoint(boost::asio::ip::make_address_v6("2001:db8::1"));
BEAST_EXPECT(!is_unspecified(ep));
BEAST_EXPECT(is_public(ep));
BEAST_EXPECT(!is_private(ep));
BEAST_EXPECT(!is_multicast(ep));
// multicast and unspecified IPv6 are neither public nor private
ep = Endpoint(boost::asio::ip::make_address_v6("ff02::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(!is_private(ep));
BEAST_EXPECT(is_multicast(ep));
ep = Endpoint(AddressV6{});
BEAST_EXPECT(is_unspecified(ep));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(!is_private(ep));
{
ep = Endpoint::from_string("192.0.2.112");
BEAST_EXPECT(!is_unspecified(ep));
+11 -3
View File
@@ -120,23 +120,28 @@ public:
auto& jq = env.app().getJobQueue();
static int const N = 4;
std::array<std::shared_ptr<JobQueue::Coro>, N> a;
// The test assumes that all the threads and coroutines it launches
// all run in a single thread.
BEAST_EXPECT(jq.thread_count() == 1);
std::array<std::shared_ptr<JobQueue::Coro>, 4> a;
LocalValue<int> lv(-1);
BEAST_EXPECT(*lv == -1);
gate g;
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
this->BEAST_EXPECT(*lv == -1);
*lv = -2;
this->BEAST_EXPECT(*lv == -2);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1);
for (int i = 0; i < N; ++i)
for (int i = 0; i < a.size(); ++i)
{
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
a[id] = c;
@@ -154,12 +159,14 @@ public:
BEAST_EXPECT(g.wait_for(5s));
a[i]->join();
}
for (auto const& c : a)
{
c->post();
BEAST_EXPECT(g.wait_for(5s));
c->join();
}
for (auto const& c : a)
{
c->post();
@@ -170,6 +177,7 @@ public:
this->BEAST_EXPECT(*lv == -2);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1);
}
+81 -33
View File
@@ -21,6 +21,11 @@
#include <xrpld/core/JobQueue.h>
#include <xrpl/beast/unit_test.h>
#include <future>
#include <mutex>
#include <string>
#include <vector>
namespace ripple {
namespace test {
@@ -45,22 +50,19 @@ class JobQueue_test : public beast::unit_test::suite
while (jobRan == false)
;
}
{
// If the JobQueue is stopped, we should no
// longer be able to add Jobs (and calling addJob() should
// return false).
using namespace std::chrono_literals;
jQueue.stop();
// The Job should never run, so having the Job access this
// unprotected variable on the stack should be completely safe.
// Not recommended for the faint of heart...
bool unprotected;
BEAST_EXPECT(
jQueue.addJob(jtCLIENT, "JobAddTest2", [&unprotected]() {
unprotected = false;
}) == false);
}
// If the JobQueue is stopped or in the process of stopping, attempting
// to schedule a coroutine should fail.
jQueue.stop();
BEAST_EXPECT(jQueue.isStopping());
auto const coro = jQueue.postCoro(
jtCLIENT,
"JobAddTest2",
[](std::shared_ptr<JobQueue::Coro> const&) {
LogicError("Unscheduled coroutine improperly invoked!");
});
BEAST_EXPECT(coro == nullptr);
}
void
@@ -128,31 +130,77 @@ class JobQueue_test : public beast::unit_test::suite
}
BEAST_EXPECT(yieldCount == 4);
}
{
// If the JobQueue is stopped, we should no
// longer be able to add a Coro (and calling postCoro() should
// return false).
using namespace std::chrono_literals;
jQueue.stop();
// The Coro should never run, so having the Coro access this
// unprotected variable on the stack should be completely safe.
// Not recommended for the faint of heart...
bool unprotected;
auto const coro = jQueue.postCoro(
jtCLIENT,
"PostCoroTest3",
[&unprotected](std::shared_ptr<JobQueue::Coro> const&) {
unprotected = false;
});
BEAST_EXPECT(coro == nullptr);
}
// If the JobQueue is stopped or in the process of stopping, attempting
// to schedule a coroutine should fail.
jQueue.stop();
BEAST_EXPECT(jQueue.isStopping());
auto const coro = jQueue.postCoro(
jtCLIENT,
"PostCoroTest3",
[](std::shared_ptr<JobQueue::Coro> const&) {
LogicError("Unscheduled coroutine improperly invoked!");
});
BEAST_EXPECT(coro == nullptr);
}
void
testPriority()
{
testcase("priority order");
jtx::Env env{*this};
JobQueue& jQueue = env.app().getJobQueue();
// A standalone Env runs the JobQueue on a single worker, which makes
// dispatch order deterministic.
if (!BEAST_EXPECT(jQueue.thread_count() == 1))
return;
std::promise<void> started;
auto startedFuture = started.get_future();
std::promise<void> release;
std::shared_future<void> const releaseFuture =
release.get_future().share();
// Occupy the only worker so the jobs below queue up behind it.
BEAST_EXPECT(
jQueue.addJob(jtCLIENT, "blocker", [&started, releaseFuture]() {
started.set_value();
releaseFuture.wait();
}));
startedFuture.wait();
std::mutex m;
std::vector<std::string> order;
auto record = [&](std::string tag) {
return [&m, &order, tag]() {
std::lock_guard lock(m);
order.push_back(tag);
};
};
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low1", record("low1")));
BEAST_EXPECT(jQueue.addJob(jtPUBLEDGER, "high", record("high")));
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low2", record("low2")));
BEAST_EXPECT(jQueue.addJob(jtADMIN, "highest", record("highest")));
release.set_value();
jQueue.rendezvous();
// Higher job types run first; equal types run in FIFO order.
std::vector<std::string> const expected{
"highest", "high", "low1", "low2"};
std::lock_guard lock(m);
BEAST_EXPECT(order == expected);
}
public:
void
run() override
{
testPriority();
testAddJob();
testPostCoro();
}
-179
View File
@@ -1,179 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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 <xrpld/core/JobTypes.h>
#include <xrpld/core/detail/Workers.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/json/json_value.h>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
namespace ripple {
/**
* Dummy class for unit tests.
*/
namespace perf {
class PerfLogTest : public PerfLog
{
void
rpcStart(std::string const& method, std::uint64_t requestId) override
{
}
void
rpcFinish(std::string const& method, std::uint64_t requestId) override
{
}
void
rpcError(std::string const& method, std::uint64_t dur) override
{
}
void
jobQueue(JobType const type) override
{
}
void
jobStart(
JobType const type,
std::chrono::microseconds dur,
std::chrono::time_point<std::chrono::steady_clock> startTime,
int instance) override
{
}
void
jobFinish(JobType const type, std::chrono::microseconds dur, int instance)
override
{
}
Json::Value
countersJson() const override
{
return Json::Value();
}
Json::Value
currentJson() const override
{
return Json::Value();
}
void
resizeJobs(int const resize) override
{
}
void
rotate() override
{
}
};
} // namespace perf
//------------------------------------------------------------------------------
class Workers_test : public beast::unit_test::suite
{
public:
struct TestCallback : Workers::Callback
{
void
processTask(int instance) override
{
std::lock_guard lk{mut};
if (--count == 0)
cv.notify_all();
}
std::condition_variable cv;
std::mutex mut;
int count = 0;
};
void
testThreads(int const tc1, int const tc2, int const tc3)
{
testcase(
"threadCounts: " + std::to_string(tc1) + " -> " +
std::to_string(tc2) + " -> " + std::to_string(tc3));
TestCallback cb;
std::unique_ptr<perf::PerfLog> perfLog =
std::make_unique<perf::PerfLogTest>();
Workers w(cb, perfLog.get(), "Test", tc1);
BEAST_EXPECT(w.getNumberOfThreads() == tc1);
auto testForThreadCount = [this, &cb, &w](int const threadCount) {
// Prepare the callback.
cb.count = threadCount;
// Execute the test.
w.setNumberOfThreads(threadCount);
BEAST_EXPECT(w.getNumberOfThreads() == threadCount);
for (int i = 0; i < threadCount; ++i)
w.addTask();
// 10 seconds should be enough to finish on any system
//
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lk{cb.mut};
bool const signaled =
cb.cv.wait_for(lk, 10s, [&cb] { return cb.count == 0; });
BEAST_EXPECT(signaled);
BEAST_EXPECT(cb.count == 0);
};
testForThreadCount(tc1);
testForThreadCount(tc2);
testForThreadCount(tc3);
w.stop();
// We had better finished all our work!
BEAST_EXPECT(cb.count == 0);
}
void
run() override
{
testThreads(0, 0, 0);
testThreads(1, 0, 1);
testThreads(2, 1, 2);
testThreads(4, 3, 5);
testThreads(16, 4, 15);
testThreads(64, 3, 65);
}
};
BEAST_DEFINE_TESTSUITE(Workers, core, ripple);
} // namespace ripple
+8 -8
View File
@@ -44,10 +44,10 @@ bridge(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
return jv;
}
@@ -60,10 +60,10 @@ bridge_rpc(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
return jv;
}
+1 -9
View File
@@ -52,18 +52,10 @@ public:
for (std::uint8_t i = 0; i < 64; i++)
{
beast::rngfill(src, sizeof(src), default_prng());
Seed const seed({src, sizeof(src)});
Seed const seed(src);
BEAST_EXPECT(memcmp(seed.data(), src, sizeof(src)) == 0);
}
}
for (int i = 0; i < 64; i++)
{
uint128 src;
beast::rngfill(src.data(), src.size(), default_prng());
Seed const seed(src);
BEAST_EXPECT(memcmp(seed.data(), src.data(), src.size()) == 0);
}
}
std::string
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -469,7 +469,7 @@ class PWAServe_test : public beast::unit_test::suite,
BEAST_EXPECT(html(r));
BEAST_EXPECT(r.body() == doc());
BEAST_EXPECT(hdr(r, "Connection") == "close");
BEAST_EXPECT(hdr(r, "Server") == systemName());
BEAST_EXPECT(hdr(r, "Server") == systemName);
BEAST_EXPECT(
hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0);
BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY");
@@ -695,7 +695,7 @@ class PWAServe_test : public beast::unit_test::suite,
yield);
BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0);
BEAST_EXPECT(
s.find("\r\nServer: " + systemName() + "\r\n") !=
s.find("\r\nServer: " + std::string(systemName) + "\r\n") !=
std::string::npos);
}
}
+17 -1
View File
@@ -1260,13 +1260,29 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
Section const& ignored = config_->section(SECTION_IGNORE_AMENDMENTS);
// [ignore_amendments] exists to keep long-lived test networks syncable
// after an amendment they activated is removed from the codebase. It
// must never be usable on a production network.
if (!ignored.lines().empty() &&
(config_->NETWORK_ID == 0 || config_->NETWORK_ID == 21337))
{
JLOG(m_journal.fatal()) << "[" SECTION_IGNORE_AMENDMENTS
"] is not permitted on "
"network_id "
<< config_->NETWORK_ID;
return false;
}
m_amendmentTable = make_AmendmentTable(
*this,
config().AMENDMENT_MAJORITY_TIME,
supported,
upVoted,
downVoted,
logs_->journal("Amendments"));
logs_->journal("Amendments"),
ignored);
}
Pathfinder::initPathTable();
+1 -1
View File
@@ -132,7 +132,7 @@ void
printHelp(const po::options_description& desc)
{
std::cerr
<< systemName() << "d [options] <command> <params>\n"
<< systemName << "d [options] <command> <params>\n"
<< desc << std::endl
<< "Commands: \n"
" account_currencies <account> [<ledger>]\n"
+2 -1
View File
@@ -211,7 +211,8 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
beast::Journal journal,
Section const& ignored = Section{});
} // namespace ripple
+3 -5
View File
@@ -1621,12 +1621,10 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
#ifdef DEBUG
if (!isTesSuccess(e.result))
{
std::string token, human;
if (transResultInfo(e.result, token, human))
if (std::string token = transToken(e.result); token != "-")
{
JLOG(m_journal.info())
<< "TransactionResult: " << token << ": " << human;
JLOG(m_journal.debug()) << "Tx " << e.transaction->getID()
<< ", result: " << token;
}
}
#endif
+79 -7
View File
@@ -59,7 +59,7 @@ parseSection(Section const& section)
uint256 id;
if (!id.parseHex(match[1]))
if (!id.parseHex(match[1].str()))
Throw<std::runtime_error>(
"Invalid amendment ID '" + match[1] + "' in [" +
section.name() + "]");
@@ -302,6 +302,15 @@ struct AmendmentState
/** Indicates an amendment that this server has code support for. */
bool supported = false;
/** Indicates an amendment listed in [ignore_amendments]. The server has
no code for it, but must not become amendment blocked if it is (or
becomes) enabled on the ledger. Such an amendment is reported as
`supported` internally so every blocking check (LedgerMaster,
NetworkOPs, Change) passes, but is never voted for and is shown as
unsupported + ignored over RPC.
*/
bool ignored = false;
/** The name of this amendment, possibly empty. */
std::string name;
@@ -476,7 +485,8 @@ public:
std::vector<FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
beast::Journal journal,
Section const& ignored);
uint256
find(std::string const& name) const override;
@@ -543,7 +553,8 @@ AmendmentTableImpl::AmendmentTableImpl(
std::vector<FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal)
beast::Journal journal,
Section const& ignored)
: lastUpdateSeq_(0)
, majorityTime_(majorityTime)
, unsupportedEnabled_(false)
@@ -586,6 +597,59 @@ AmendmentTableImpl::AmendmentTableImpl(
<< " voted by default if not enabled on the ledger.";
}
// Parse ignored amendments from config. Each line is either a registered
// feature name (e.g. "HookOnV2") or "<64 hex amendment ID> [name]". These
// are deliberately NOT persisted to wallet.db: removing the line from the
// config must restore normal amendment-blocking behaviour.
for (auto const& line : ignored.lines())
{
std::string const trimmed = boost::algorithm::trim_copy(line);
if (trimmed.empty())
continue;
std::vector<std::string> parts;
boost::algorithm::split(
parts,
trimmed,
boost::algorithm::is_space(),
boost::algorithm::token_compress_on);
uint256 id;
std::string name;
if (id.parseHex(parts[0]))
name = parts.size() > 1 ? parts[1] : std::string{};
else if (auto const f = getRegisteredFeature(parts[0]))
{
id = *f;
name = parts[0];
}
else
Throw<std::runtime_error>(
"Invalid entry '" + line + "' in [" + ignored.name() +
"]: expected a registered feature name or amendment ID");
AmendmentState& s = add(id, lock);
if (s.supported)
{
JLOG(j_.warn()) << "[" << ignored.name() << "] lists amendment "
<< id << " (" << s.name
<< ") which this server supports; entry has no "
"effect.";
continue;
}
if (!name.empty())
s.name = name;
s.supported = true;
s.ignored = true;
s.vote = AmendmentVote::obsolete;
JLOG(j_.warn()) << "Amendment " << id << " (" << s.name
<< ") is unsupported but listed in [" << ignored.name()
<< "]: this server will NOT become amendment blocked "
"if it is enabled on the ledger.";
}
hash_set<uint256> detect_conflict;
// Parse enabled amendments from config
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
@@ -775,7 +839,12 @@ AmendmentTableImpl::enable(uint256 const& amendment)
s.enabled = true;
if (!s.supported)
if (s.ignored)
{
JLOG(j_.warn()) << "Ignored unsupported amendment " << amendment << " ("
<< s.name << ") is enabled on the ledger.";
}
else if (!s.supported)
{
JLOG(j_.error()) << "Unsupported amendment " << amendment
<< " activated.";
@@ -1044,7 +1113,9 @@ AmendmentTableImpl::injectJson(
if (!fs.name.empty())
v[jss::name] = fs.name;
v[jss::supported] = fs.supported;
v[jss::supported] = fs.supported && !fs.ignored;
if (fs.ignored)
v["ignored"] = true;
if (!fs.enabled && isAdmin)
{
if (fs.vote == AmendmentVote::obsolete)
@@ -1113,10 +1184,11 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal)
beast::Journal journal,
Section const& ignored)
{
return std::make_unique<AmendmentTableImpl>(
app, majorityTime, supported, enabled, vetoed, journal);
app, majorityTime, supported, enabled, vetoed, journal, ignored);
}
} // namespace ripple
+1 -1
View File
@@ -198,7 +198,7 @@ PathRequest::isValid(std::shared_ptr<RippleLineCache> const& crCache)
if (!sleDest)
{
jvDestCur.append(Json::Value(systemCurrencyCode()));
jvDestCur.append(Json::Value(std::string(systemCurrencyCode)));
if (!saDstAmount.native())
{
// Only XRP can be send to a non-existent account.
+2 -1
View File
@@ -26,6 +26,7 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STPathSet.h>
#include <optional>
namespace ripple {
@@ -206,7 +207,7 @@ private:
bool convert_all_;
std::shared_ptr<ReadView const> mLedger;
std::unique_ptr<LoadEvent> m_loadEvent;
std::optional<LoadEvent> m_loadEvent;
std::shared_ptr<RippleLineCache> mRLCache;
STPathElement mSource;
+1 -1
View File
@@ -91,7 +91,7 @@ URIToken::preflight(PreflightContext const& ctx)
{
auto const uri = ctx.tx.getFieldVL(sfURI);
if (uri.size() < 1 || uri.size() > 256)
if (uri.size() < 1 || uri.size() > maxURITokenURILength)
{
JLOG(ctx.j.warn())
<< "Malformed transaction. URI must be at least 1 "
+3
View File
@@ -102,6 +102,9 @@ struct ConfigSection
#define SECTION_VALIDATORS "validators"
#define SECTION_VALIDATOR_TOKEN "validator_token"
#define SECTION_VETO_AMENDMENTS "veto_amendments"
// Testnet-only: amendments enabled on-ledger that this node should not treat
// as unsupported (i.e. should not become amendment blocked over).
#define SECTION_IGNORE_AMENDMENTS "ignore_amendments"
#define SECTION_WORKERS "workers"
} // namespace ripple
+8 -13
View File
@@ -26,7 +26,7 @@ namespace ripple {
template <class F>
JobQueue::Coro::Coro(
Coro_create_t,
JobQueue::CoroCreator,
JobQueue& jq,
JobType type,
std::string const& name,
@@ -48,6 +48,7 @@ JobQueue::Coro::Coro(
},
boost::coroutines::attributes(megabytes(1)))
{
++jq_.totalCoroutines_;
}
inline JobQueue::Coro::~Coro()
@@ -55,15 +56,14 @@ inline JobQueue::Coro::~Coro()
#ifndef NDEBUG
XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished");
#endif
--jq_.totalCoroutines_;
}
inline void
JobQueue::Coro::yield() const
{
{
std::lock_guard lock(jq_.m_mutex);
++jq_.nSuspend_;
}
++jq_.suspendedCoroutines_;
(*yield_)();
}
@@ -78,9 +78,7 @@ JobQueue::Coro::post()
// sp keeps 'this' alive
if (jq_.addJob(
type_, name_, [this, sp = shared_from_this()]() { resume(); }))
{
return true;
}
// The coroutine will not run. Clean up running_.
std::lock_guard lk(mutex_run_);
@@ -96,10 +94,7 @@ JobQueue::Coro::resume()
std::lock_guard lk(mutex_run_);
running_ = true;
}
{
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
}
--jq_.suspendedCoroutines_;
auto saved = detail::getLocalValues().release();
detail::getLocalValues().reset(&lvs_);
std::lock_guard lock(mutex_);
@@ -133,8 +128,8 @@ JobQueue::Coro::expectEarlyExit()
//
// That said, since we're outside the Coro's stack, we need to
// decrement the nSuspend that the Coro's call to yield caused.
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
--jq_.suspendedCoroutines_;
#ifndef NDEBUG
finished_ = true;
#endif
+34 -42
View File
@@ -21,9 +21,8 @@
#define RIPPLE_CORE_JOB_H_INCLUDED
#include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/LoadMonitor.h>
#include <xrpld/core/LoadEvent.h>
#include <xrpl/basics/CountedObject.h>
#include <functional>
#include <functional>
@@ -32,10 +31,7 @@ namespace ripple {
// Note that this queue should only be used for CPU-bound jobs
// It is primarily intended for signature checking
enum JobType {
// Special type indicating an invalid job - will go away soon.
jtINVALID = -1,
enum JobType : std::uint32_t {
// Job types - the position in this enum indicates the job priority with
// earlier jobs having lower priority than later jobs. If you wish to
// insert a job at a specific priority, simply add it at the right location.
@@ -99,27 +95,15 @@ class Job : public CountedObject<Job>
public:
using clock_type = std::chrono::steady_clock;
/** Default constructor.
Job() = delete;
Allows Job to be used as a container type.
This is used to allow things like jobMap [key] = value.
*/
// VFALCO NOTE I'd prefer not to have a default constructed object.
// What is the semantic meaning of a Job with no associated
// function? Having the invariant "all Job objects refer to
// a job" would reduce the number of states.
//
Job();
Job(JobType type, std::uint64_t index);
// VFALCO TODO try to remove the dependency on LoadMonitor.
Job(JobType type,
std::string const& name,
std::string name,
std::uint64_t index,
LoadMonitor& lm,
std::function<void()> const& job);
std::reference_wrapper<LoadSampler const> sampler,
std::function<void()> job);
Job(Job&& other);
JobType
getType() const;
@@ -128,27 +112,35 @@ public:
clock_type::time_point const&
queue_time() const;
void
doJob();
/** A description of this specific job.
// These comparison operators make the jobs sort in priority order
// in the job set
bool
operator<(const Job& j) const;
bool
operator>(const Job& j) const;
bool
operator<=(const Job& j) const;
bool
operator>=(const Job& j) const;
Unlike the name associated with the type of this job, which is fixed,
the description may include additional information or context that
distinguishes this from other jobs of the same type.
*/
[[maybe_unused]] std::string const&
description() const;
void
execute();
// Sort jobs in dispatch order: JobQueue runs the first runnable job in
// the set, so higher-priority types (later in the JobType enum) must sort
// first, and jobs of the same type run in the order they were queued.
auto
operator<=>(Job const& other) const
{
if (type_ != other.type_)
return other.type_ <=> type_;
return index_ <=> other.index_;
}
private:
JobType mType;
std::uint64_t mJobIndex;
std::function<void()> mJob;
std::shared_ptr<LoadEvent> m_loadEvent;
std::string mName;
clock_type::time_point m_queue_time;
JobType const type_;
std::uint64_t const index_;
clock_type::time_point const queued_;
std::function<void()> work_;
LoadEvent loadEvent_;
};
using JobCounter = ClosureCounter<void>;
+127 -108
View File
@@ -21,14 +21,15 @@
#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
#include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/JobTypeData.h>
#include <xrpld/core/JobTypes.h>
#include <xrpld/core/detail/Workers.h>
#include <xrpl/basics/LocalValue.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/json/json_value.h>
#include <boost/coroutine/all.hpp>
#include <boost/range/begin.hpp> // workaround for boost 1.72 bug
#include <boost/range/end.hpp> // workaround for boost 1.72 bug
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
namespace ripple {
@@ -37,10 +38,6 @@ class PerfLog;
}
class Logs;
struct Coro_create_t
{
explicit Coro_create_t() = default;
};
/** A pool of threads to perform work.
@@ -54,15 +51,23 @@ struct Coro_create_t
*/
class JobQueue : private Workers::Callback
{
// A small class used to prevent construction of coroutines without
// going through the JobQueue APIs.
struct CoroCreator
{
explicit constexpr CoroCreator() = default;
};
public:
/** Coroutines must run to completion. */
class Coro : public std::enable_shared_from_this<Coro>
class Coro : public std::enable_shared_from_this<Coro>,
public CountedObject<Coro>
{
private:
detail::LocalValues lvs_;
JobQueue& jq_;
JobType type_;
std::string name_;
JobType const type_;
std::string const name_;
bool running_;
std::mutex mutex_;
std::mutex mutex_run_;
@@ -76,14 +81,23 @@ public:
public:
// Private: Used in the implementation
template <class F>
Coro(Coro_create_t, JobQueue&, JobType, std::string const&, F&&);
Coro(
JobQueue::CoroCreator,
JobQueue&,
JobType,
std::string const&,
F&&);
// Not copy-constructible or assignable
Coro(Coro const&) = delete;
Coro&
operator=(Coro const&) = delete;
~Coro();
Coro(Coro&&) = delete;
Coro&
operator=(Coro&&) = delete;
virtual ~Coro();
/** Suspend coroutine execution.
Effects:
@@ -167,9 +181,7 @@ public:
{
if (auto optionalCountedJob =
jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
{
return addRefCountedJob(type, name, std::move(*optionalCountedJob));
}
return false;
}
@@ -177,10 +189,10 @@ public:
@param t The type of job.
@param name Name of the job.
@param f Has a signature of void(std::shared_ptr<Coro>). Called when the
job executes.
@param f The actual coroutine body; has a signature of:
void(std::shared_ptr<Coro>)
@return shared_ptr to posted Coro. nullptr if post was not successful.
@return shared_ptr to posted Coro. nullptr if post was not successful.
*/
template <class F>
std::shared_ptr<Coro>
@@ -201,10 +213,9 @@ public:
int
getJobCountGE(JobType t) const;
/** Return a scoped LoadEvent.
*/
std::unique_ptr<LoadEvent>
makeLoadEvent(JobType t, std::string const& name);
/** Returns a new load event for the particular job. */
LoadEvent
makeLoadEvent(JobType t, std::string name);
/** Add multiple load events.
*/
@@ -226,52 +237,96 @@ public:
void
stop();
bool
isStopping() const
[[nodiscard]] bool
isStopping() const noexcept
{
return stopping_;
return state_.load(std::memory_order_acquire) >= state::stopping;
}
// We may be able to move away from this, but we can keep it during the
// transition.
bool
isStopped() const;
[[nodiscard]] bool
isStopped() const noexcept
{
return state_.load(std::memory_order_acquire) == state::stopped;
}
/** Returns the number of threads that this job queue is configured with. */
unsigned int
thread_count() const noexcept
{
return m_workers.count();
}
private:
friend class Coro;
using JobDataMap = std::map<JobType, JobTypeData>;
struct Data
{
int waiting = 0;
int running = 0;
int deferred = 0;
beast::insight::Event dequeue;
beast::insight::Event execute;
LoadMonitor load;
Data(
JobTypeInfo const& info,
beast::insight::Collector::ptr const& collector,
Logs& logs)
: load(
info.averageLatency,
info.peakLatency,
logs.journal("LoadMonitor"))
{
if (!info.special())
{
dequeue = collector->make_event(std::string(info.name) + "_q");
execute = collector->make_event(std::string(info.name));
}
}
Data(Data const&) = delete;
Data&
operator=(Data const&) = delete;
Data(Data&&) = delete;
Data&
operator=(Data&&) = delete;
};
beast::Journal m_journal;
mutable std::mutex m_mutex;
std::uint64_t m_lastJob;
std::uint64_t m_lastJob = 0;
std::set<Job> m_jobSet;
JobCounter jobCounter_;
std::atomic_bool stopping_{false};
std::atomic_bool stopped_{false};
JobDataMap m_jobData;
JobTypeData m_invalidJobData;
// The number of jobs currently in processTask()
int m_processCount;
enum class state : std::uint8_t { running = 0, stopping = 1, stopped = 2 };
std::atomic<state> state_ = state::running;
std::array<Data, jobTypes.size()> data_;
int activeThreads_ = 0;
// The number of coroutines (active or suspended)
std::atomic<int> totalCoroutines_ = 0;
// The number of suspended coroutines
int nSuspend_ = 0;
std::atomic<int> suspendedCoroutines_ = 0;
Workers m_workers;
// Statistics tracking
perf::PerfLog& perfLog_;
// Statistics tracking
beast::insight::Collector::ptr m_collector;
beast::insight::Gauge job_count;
beast::insight::Gauge activeThreadsGauge_;
beast::insight::Hook hook;
std::condition_variable cv_;
void
collect();
JobTypeData&
getJobTypeData(JobType type);
// Adds a reference counted job to the JobQueue.
//
@@ -282,46 +337,7 @@ private:
//
// return true if func added to queue.
bool
addRefCountedJob(
JobType type,
std::string const& name,
JobFunction const& func);
// Returns the next Job we should run now.
//
// RunnableJob:
// A Job in the JobSet whose slots count for its type is greater than zero.
//
// Pre-conditions:
// mJobSet must not be empty.
// mJobSet holds at least one RunnableJob
//
// Post-conditions:
// job is a valid Job object.
// job is removed from mJobQueue.
// Waiting job count of its type is decremented
// Running job count of its type is incremented
//
// Invariants:
// The calling thread owns the JobLock
void
getNextJob(Job& job);
// Indicates that a running Job has completed its task.
//
// Pre-conditions:
// Job must not exist in mJobSet.
// The JobType must not be invalid.
//
// Post-conditions:
// The running count of that JobType is decremented
// A new task is signaled if there are more waiting Jobs than the limit, if
// any.
//
// Invariants:
// <none>
void
finishJob(JobType type);
addRefCountedJob(JobType type, std::string const& name, JobFunction func);
// Runs the next appropriate waiting Job.
//
@@ -334,13 +350,14 @@ private:
// Invariants:
// <none>
void
processTask(int instance) override;
processTask(unsigned int instance) override;
// Returns the limit of running jobs for the given job type.
// For jobs with no limit, we return the largest int. Hopefully that
// will be enough.
int
getJobLimit(JobType type);
// Called when an uncaught exception occurs while processing a task
//
// Invariants:
// <none>
void
uncaughtException(unsigned int instance, std::exception_ptr eptr) override;
};
/*
@@ -354,11 +371,11 @@ private:
coro_ member is initialized (a boost::coroutines::pull_type), execution
automatically passes to the coroutine, which we don't want at this point,
since we are still in the handler thread context. It's important to note
here that construction of a boost pull_type automatically passes execution to
the coroutine. A pull_type object automatically generates a push_type that is
passed as a parameter (do_yield) in the signature of the function the
pull_type was created with. This function is immediately called during coro_
construction and within it, Coro::yield_ is assigned the push_type
here that construction of a boost pull_type automatically passes execution
to the coroutine. A pull_type object automatically generates a push_type
that is passed as a parameter (do_yield) in the signature of the function
the pull_type was created with. This function is immediately called during
coro_ construction and within it, Coro::yield_ is assigned the push_type
parameter (do_yield) address and called (yield()) so we can return execution
back to the caller's stack.
@@ -385,19 +402,20 @@ private:
The race condition occurs as follows:
1- The coroutine is running.
2- The coroutine is about to suspend, but before it can do so, it must
arrange for some event to wake it up.
3- The coroutine arranges for some event to wake it up.
4- Before the coroutine can suspend, that event occurs and the
resumption of the coroutine is scheduled on the job queue. 5- Again, before
the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
Again, before the coroutine can suspend, the resumption code runs the
coroutine.
The coroutine is now running in two threads.
1. The coroutine is running.
2. The coroutine is about to suspend, but before it can do so, it must
arrange for some event to wake it up.
3. The coroutine arranges for some event to wake it up.
4. Before the coroutine can suspend, that event occurs and the
resumption of the coroutine is scheduled on the job queue.
5. Again, before the coroutine can suspend, the resumption of the coroutine
is dispatched.
6. Again, before the coroutine can suspend, the resumption code runs the
coroutine.
The lock prevents this from happening as step 6 will block until the
lock is released which only happens after the coroutine completes.
The coroutine is now running in two threads. The lock prevents this from
happening as step 6 will block until the lock is released which only
happens after the coroutine completes.
*/
} // namespace ripple
@@ -410,12 +428,12 @@ template <class F>
std::shared_ptr<JobQueue::Coro>
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
{
/* First param is a detail type to make construction private.
Last param is the function the coroutine runs. Signature of
void(std::shared_ptr<Coro>).
*/
if (state_.load(std::memory_order_acquire) != state::running)
return nullptr;
auto coro = std::make_shared<Coro>(
Coro_create_t{}, *this, t, name, std::forward<F>(f));
CoroCreator{}, *this, t, name, std::forward<F>(f));
if (!coro->post())
{
// The Coro was not successfully posted. Disable it so it's destructor
@@ -423,6 +441,7 @@ JobQueue::postCoro(JobType t, std::string const& name, F&& f)
coro->expectEarlyExit();
coro.reset();
}
return coro;
}
-107
View File
@@ -1,107 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED
#define RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED
#include <xrpld/core/JobTypeInfo.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/insight/Collector.h>
namespace ripple {
struct JobTypeData
{
private:
LoadMonitor m_load;
/* Support for insight */
beast::insight::Collector::ptr m_collector;
public:
/* The job category which we represent */
JobTypeInfo const& info;
/* The number of jobs waiting */
int waiting;
/* The number presently running */
int running;
/* And the number we deferred executing because of job limits */
int deferred;
/* Notification callbacks */
beast::insight::Event dequeue;
beast::insight::Event execute;
JobTypeData(
JobTypeInfo const& info_,
beast::insight::Collector::ptr const& collector,
Logs& logs) noexcept
: m_load(logs.journal("LoadMonitor"))
, m_collector(collector)
, info(info_)
, waiting(0)
, running(0)
, deferred(0)
{
m_load.setTargetLatency(
info.getAverageLatency(), info.getPeakLatency());
if (!info.special())
{
dequeue = m_collector->make_event(info.name() + "_q");
execute = m_collector->make_event(info.name());
}
}
/* Not copy-constructible or assignable */
JobTypeData(JobTypeData const& other) = delete;
JobTypeData&
operator=(JobTypeData const& other) = delete;
std::string
name() const
{
return info.name();
}
JobType
type() const
{
return info.type();
}
LoadMonitor&
load()
{
return m_load;
}
LoadMonitor::Stats
stats()
{
return m_load.getStats();
}
};
} // namespace ripple
#endif
-102
View File
@@ -1,102 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED
#define RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED
#include <xrpld/core/Job.h>
namespace ripple {
/** Holds all the 'static' information about a job, which does not change */
class JobTypeInfo
{
private:
JobType const m_type;
std::string const m_name;
/** The limit on the number of running jobs for this job type.
A limit of 0 marks this as a "special job" which is not
dispatched via the job queue.
*/
int const m_limit;
/** Average and peak latencies for this job type. 0 is none specified */
std::chrono::milliseconds const m_avgLatency;
std::chrono::milliseconds const m_peakLatency;
public:
// Not default constructible
JobTypeInfo() = delete;
JobTypeInfo(
JobType type,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency)
: m_type(type)
, m_name(std::move(name))
, m_limit(limit)
, m_avgLatency(avgLatency)
, m_peakLatency(peakLatency)
{
}
JobType
type() const
{
return m_type;
}
std::string const&
name() const
{
return m_name;
}
int
limit() const
{
return m_limit;
}
bool
special() const
{
return m_limit == 0;
}
std::chrono::milliseconds
getAverageLatency() const
{
return m_avgLatency;
}
std::chrono::milliseconds
getPeakLatency() const
{
return m_peakLatency;
}
};
} // namespace ripple
#endif
+112 -153
View File
@@ -21,178 +21,137 @@
#define RIPPLE_CORE_JOBTYPES_H_INCLUDED
#include <xrpld/core/Job.h>
#include <xrpld/core/JobTypeInfo.h>
#include <map>
#include <array>
#include <chrono>
#include <string>
#include <type_traits>
#include <unordered_map>
namespace ripple {
class JobTypes
/** Defines the fixed attributes of a particular type of job.
These attributes are defined at compile time and never change. They
include things like the name of the job, the limit of concurrent jobs
of this type, etc.
*/
struct JobTypeInfo
{
public:
using Map = std::map<JobType, JobTypeInfo>;
using const_iterator = Map::const_iterator;
JobType const type;
private:
JobTypes()
: m_unknown(
jtINVALID,
"invalid",
0,
std::chrono::milliseconds{0},
std::chrono::milliseconds{0})
/// The job's display name
std::string_view const name;
/** The limit on the number of running jobs for this job type.
A limit of 0 marks this as a "special job" which is not
dispatched via the job queue.
*/
int const limit;
/** Average and peak latencies for this job type, if relevant. */
/** @{ */
std::optional<std::chrono::milliseconds> const averageLatency;
std::optional<std::chrono::milliseconds> const peakLatency;
/** @} */
// Not default constructible
JobTypeInfo() = delete;
constexpr JobTypeInfo(
JobType type_,
std::string_view const name_,
int limit_,
std::optional<std::chrono::milliseconds> averageLatency_ = {},
std::optional<std::chrono::milliseconds> peakLatency_ = {})
: type(type_)
, name(name_)
, limit(limit_)
, averageLatency(averageLatency_)
, peakLatency(peakLatency_)
{
using namespace std::chrono_literals;
int maxLimit = std::numeric_limits<int>::max();
}
auto add = [this](
JobType jt,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency) {
XRPL_ASSERT(
m_map.find(jt) == m_map.end(),
"ripple::JobTypes::JobTypes::add : unique job type input");
bool
special() const
{
return limit == 0;
}
};
[[maybe_unused]] auto const inserted =
m_map
.emplace(
std::piecewise_construct,
std::forward_as_tuple(jt),
std::forward_as_tuple(
jt, name, limit, avgLatency, peakLatency))
.second;
XRPL_ASSERT(
inserted == true,
"ripple::JobTypes::JobTypes::add : input is inserted");
};
inline auto constexpr jobTypes = []() consteval {
using namespace std::chrono_literals;
constexpr JobTypeInfo jobs[] = {
// clang-format off
// avg peak
// JobType name limit latency latency
add(jtPACK, "makeFetchPack", 1, 0ms, 0ms);
add(jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms);
add(jtVALIDATION_ut, "untrustedValidation", maxLimit, 2000ms, 5000ms);
add(jtMANIFEST, "manifest", maxLimit, 2000ms, 5000ms);
add(jtTRANSACTION_l, "localTransaction", maxLimit, 100ms, 500ms);
add(jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms);
add(jtLEDGER_REQ, "ledgerRequest", 3, 0ms, 0ms);
add(jtPROPOSAL_ut, "untrustedProposal", maxLimit, 500ms, 1250ms);
add(jtREPLAY_TASK, "ledgerReplayTask", maxLimit, 0ms, 0ms);
add(jtLEDGER_DATA, "ledgerData", 3, 0ms, 0ms);
add(jtCLIENT, "clientCommand", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_SUBSCRIBE, "clientSubscribe", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_FEE_CHANGE, "clientFeeChange", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_CONSENSUS, "clientConsensus", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_ACCT_HIST, "clientAccountHistory", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_RPC, "clientRPC", maxLimit, 2000ms, 5000ms);
// avg peak
// JobType name limit latency latency
{ jtPACK, "makeFetchPack", 1, },
{ jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms },
{ jtCLIENT, "clientCommand", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_SUBSCRIBE, "clientSubscribe", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_FEE_CHANGE, "clientFeeChange", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_CONSENSUS, "clientConsensus", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_ACCT_HIST, "clientAccountHistory", std::numeric_limits<int>::max(), 2000ms, 5000ms },
// Web traffic from pwa ports: at most two workers, and no latency
// targets, so that it can never move the node's load factor.
add(jtCLIENT_PWA, "clientPWA", 2, 0ms, 0ms);
add(jtCLIENT_PWADNS, "clientPWADNS", 2, 0ms, 0ms);
add(jtCLIENT_WEBSOCKET, "clientWebsocket", maxLimit, 2000ms, 5000ms);
add(jtRPC, "RPC", maxLimit, 0ms, 0ms);
add(jtUPDATE_PF, "updatePaths", 1, 0ms, 0ms);
add(jtTRANSACTION, "transaction", maxLimit, 250ms, 1000ms);
add(jtBATCH, "batch", maxLimit, 250ms, 1000ms);
add(jtADVANCE, "advanceLedger", maxLimit, 0ms, 0ms);
add(jtPUBLEDGER, "publishNewLedger", maxLimit, 3000ms, 4500ms);
add(jtTXN_DATA, "fetchTxnData", 5, 0ms, 0ms);
add(jtWAL, "writeAhead", maxLimit, 1000ms, 2500ms);
add(jtVALIDATION_t, "trustedValidation", maxLimit, 500ms, 1500ms);
add(jtWRITE, "writeObjects", maxLimit, 1750ms, 2500ms);
add(jtACCEPT, "acceptLedger", maxLimit, 0ms, 0ms);
add(jtPROPOSAL_t, "trustedProposal", maxLimit, 100ms, 500ms);
add(jtSWEEP, "sweep", 1, 0ms, 0ms);
add(jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms);
add(jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms);
add(jtADMIN, "administration", maxLimit, 0ms, 0ms);
add(jtMISSING_TXN, "handleHaveTransactions", 1200, 0ms, 0ms);
add(jtREQUESTED_TXN, "doTransactions", 1200, 0ms, 0ms);
add(jtPEER, "peerCommand", 0, 200ms, 2500ms);
add(jtDISK, "diskAccess", 0, 500ms, 1000ms);
add(jtTXN_PROC, "processTransaction", 0, 0ms, 0ms);
add(jtOB_SETUP, "orderBookSetup", 0, 0ms, 0ms);
add(jtPATH_FIND, "pathFind", 0, 0ms, 0ms);
add(jtHO_READ, "nodeRead", 0, 0ms, 0ms);
add(jtHO_WRITE, "nodeWrite", 0, 0ms, 0ms);
add(jtGENERIC, "generic", 0, 0ms, 0ms);
add(jtNS_SYNC_READ, "SyncReadNode", 0, 0ms, 0ms);
add(jtNS_ASYNC_READ, "AsyncReadNode", 0, 0ms, 0ms);
add(jtNS_WRITE, "WriteNode", 0, 0ms, 0ms);
{ jtCLIENT_PWA, "clientPWA", 2, },
{ jtCLIENT_PWADNS, "clientPWADNS", 2, },
{ jtCLIENT_RPC, "clientRPC", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_WEBSOCKET, "clientWebsocket", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtRPC, "RPC", std::numeric_limits<int>::max(), },
{ jtSWEEP, "sweep", 1, },
{ jtVALIDATION_ut, "untrustedValidation", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtMANIFEST, "manifest", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtUPDATE_PF, "updatePaths", 1, },
{ jtTRANSACTION_l, "localTransaction", std::numeric_limits<int>::max(), 100ms, 500ms },
{ jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms },
{ jtLEDGER_REQ, "ledgerRequest", 3, },
{ jtPROPOSAL_ut, "untrustedProposal", std::numeric_limits<int>::max(), 500ms, 1250ms },
{ jtREPLAY_TASK, "ledgerReplayTask", std::numeric_limits<int>::max(), },
{ jtTRANSACTION, "transaction", std::numeric_limits<int>::max(), 250ms, 1000ms },
{ jtMISSING_TXN, "handleHaveTransactions", 1200, },
{ jtREQUESTED_TXN, "doTransactions", 1200, },
{ jtBATCH, "batch", std::numeric_limits<int>::max(), 250ms, 1000ms },
{ jtLEDGER_DATA, "ledgerData", 3, },
{ jtADVANCE, "advanceLedger", std::numeric_limits<int>::max(), },
{ jtPUBLEDGER, "publishNewLedger", std::numeric_limits<int>::max(), 3000ms, 4500ms },
{ jtTXN_DATA, "fetchTxnData", 5, },
{ jtWAL, "writeAhead", std::numeric_limits<int>::max(), 1000ms, 2500ms },
{ jtVALIDATION_t, "trustedValidation", std::numeric_limits<int>::max(), 500ms, 1500ms },
{ jtWRITE, "writeObjects", std::numeric_limits<int>::max(), 1750ms, 2500ms },
{ jtACCEPT, "acceptLedger", std::numeric_limits<int>::max(), },
{ jtPROPOSAL_t, "trustedProposal", std::numeric_limits<int>::max(), 100ms, 500ms },
{ jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms },
{ jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms },
{ jtADMIN, "administration", std::numeric_limits<int>::max(), },
{ jtPEER, "peerCommand", 0, 200ms, 2500ms },
{ jtDISK, "diskAccess", 0, 500ms, 1000ms },
{ jtTXN_PROC, "processTransaction", 0 },
{ jtOB_SETUP, "orderBookSetup", 0 },
{ jtPATH_FIND, "pathFind", 0 },
{ jtHO_READ, "nodeRead", 0 },
{ jtHO_WRITE, "nodeWrite", 0 },
{ jtGENERIC, "generic", 0 },
{ jtNS_SYNC_READ, "SyncReadNode", 0 },
{ jtNS_ASYNC_READ, "AsyncReadNode", 0 },
{ jtNS_WRITE, "WriteNode", 0 }
// clang-format on
}
};
public:
static JobTypes const&
instance()
// Ensure that the order of entries in the table corresponds to the
// order of entries in the enum:
std::underlying_type_t<JobType> idx = 0;
for (auto const& jt : jobs)
{
static JobTypes const types;
return types;
if (static_cast<std::underlying_type_t<JobType>>(jt.type) != idx)
throw "The JobTypes array is improperly ordered";
++idx;
}
static std::string const&
name(JobType jt)
{
return instance().get(jt).name();
}
JobTypeInfo const&
get(JobType jt) const
{
Map::const_iterator const iter(m_map.find(jt));
XRPL_ASSERT(iter != m_map.end(), "ripple::JobTypes::get : valid input");
if (iter != m_map.end())
return iter->second;
return m_unknown;
}
JobTypeInfo const&
getInvalid() const
{
return m_unknown;
}
Map::size_type
size() const
{
return m_map.size();
}
const_iterator
begin() const
{
return m_map.cbegin();
}
const_iterator
cbegin() const
{
return m_map.cbegin();
}
const_iterator
end() const
{
return m_map.cend();
}
const_iterator
cend() const
{
return m_map.cend();
}
JobTypeInfo m_unknown;
Map m_map;
};
return std::to_array(jobs);
}();
} // namespace ripple
+23 -26
View File
@@ -20,24 +20,27 @@
#ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED
#define RIPPLE_CORE_LOADEVENT_H_INCLUDED
#include <xrpld/core/LoadMonitor.h>
#include <chrono>
#include <memory>
#include <string>
namespace ripple {
class LoadMonitor;
// VFALCO NOTE What is the difference between a LoadEvent and a LoadMonitor?
// VFALCO TODO Rename LoadEvent to ScopedLoadSample
//
// This looks like a scoped elapsed time measuring class
//
class LoadEvent
{
public:
// VFALCO TODO remove the dependency on LoadMonitor. Is that possible?
LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart);
LoadEvent(
std::reference_wrapper<LoadSampler const> callback,
std::string name,
bool shouldStart);
LoadEvent(LoadEvent&& other);
LoadEvent&
operator=(LoadEvent&& other);
LoadEvent&
operator=(LoadEvent const&) = delete;
LoadEvent(LoadEvent const&) = delete;
~LoadEvent();
@@ -45,17 +48,6 @@ public:
std::string const&
name() const;
// The time spent waiting.
std::chrono::steady_clock::duration
waitTime() const;
// The time spent running.
std::chrono::steady_clock::duration
runTime() const;
void
setName(std::string const& name);
// Start the measurement. If already started, then
// restart, assigning the elapsed time to the "waiting"
// state.
@@ -69,20 +61,25 @@ public:
stop();
private:
LoadMonitor& monitor_;
// Represents our current state
bool running_;
// The name associated with this event, if any.
// The name for this event.
std::string name_;
// The callback to invoke when we stop. This will only
// be invoked if `neutered_` is `false`.
std::reference_wrapper<LoadSampler const> callback_;
// Represents the time we last transitioned states
std::chrono::steady_clock::time_point mark_;
// The time we spent waiting and running respectively
std::chrono::steady_clock::duration timeWaiting_;
std::chrono::steady_clock::duration timeRunning_;
// Represents our current state
bool running_;
// Determines whether the callback should be invoked
bool neutered_;
};
} // namespace ripple
+47 -34
View File
@@ -20,69 +20,82 @@
#ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#define RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#include <xrpld/core/LoadEvent.h>
#include <xrpl/basics/UptimeClock.h>
#include <xrpl/beast/utility/Journal.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <optional>
namespace ripple {
// Monitors load levels and response times
using LoadSampler = std::function<void(
char const*,
std::chrono::steady_clock::duration,
std::chrono::steady_clock::duration)>;
// VFALCO TODO Rename this. Having both LoadManager and LoadMonitor is
// confusing.
//
/** Monitors load levels and response times
@todo Consider renaming this, since having both a LoadManager and a
LoadMonitor is confusing, especially considering that they do
different things.
*/
class LoadMonitor
{
public:
explicit LoadMonitor(beast::Journal j);
struct Stats
{
std::uint64_t count = 0;
void
addLoadSample(LoadEvent const& sample);
std::chrono::milliseconds averageLatency{};
std::chrono::milliseconds peakLatency{};
bool isOverloaded = false;
};
explicit LoadMonitor(
std::optional<std::chrono::milliseconds> targetAverageLatency,
std::optional<std::chrono::milliseconds> targetPeakLatency,
beast::Journal j);
LoadMonitor(LoadMonitor const& other) = delete;
LoadMonitor&
operator=(LoadMonitor const& other) = delete;
LoadMonitor(LoadMonitor&& other) = delete;
LoadMonitor&
operator=(LoadMonitor&& other) = delete;
void
addSamples(int count, std::chrono::milliseconds latency);
void
setTargetLatency(
std::chrono::milliseconds avg,
std::chrono::milliseconds pk);
bool
isOverTarget(std::chrono::milliseconds avg, std::chrono::milliseconds peak);
// VFALCO TODO make this return the values in a struct.
struct Stats
{
Stats();
std::uint64_t count;
std::chrono::milliseconds latencyAvg;
std::chrono::milliseconds latencyPeak;
bool isOverloaded;
};
Stats
getStats();
bool
isOver();
/** A reference to a function object used to report performance events. */
std::reference_wrapper<LoadSampler const>
sample()
{
return std::cref(eventCallback_);
}
private:
void
update();
std::mutex mutex_;
std::uint64_t mCounts;
int mLatencyEvents;
std::chrono::milliseconds mLatencyMSAvg;
std::chrono::milliseconds mLatencyMSPeak;
std::chrono::milliseconds mTargetLatencyAvg;
std::chrono::milliseconds mTargetLatencyPk;
UptimeClock::time_point mLastUpdate;
std::uint64_t samples_ = 0;
std::chrono::milliseconds averageLatency_{};
std::chrono::milliseconds peakLatency_{};
std::optional<std::chrono::milliseconds> targetAverageLatency_{};
std::optional<std::chrono::milliseconds> targetPeakLatency_{};
UptimeClock::time_point lastUpdate_;
beast::Journal const j_;
LoadSampler eventCallback_;
};
} // namespace ripple
+4 -4
View File
@@ -367,15 +367,15 @@ Config::setup(
strXdgDataHome = strHome + "/.local/share";
}
CONFIG_DIR = strXdgConfigHome + "/" + systemName();
CONFIG_DIR = strXdgConfigHome + "/" + std::string(systemName);
CONFIG_FILE = CONFIG_DIR / strConfFile;
dataDir = strXdgDataHome + "/" + systemName();
dataDir = strXdgDataHome + "/" + std::string(systemName);
if (!boost::filesystem::exists(CONFIG_FILE))
{
CONFIG_DIR = "/etc/opt/" + systemName();
CONFIG_DIR = "/etc/opt/" + std::string(systemName);
CONFIG_FILE = CONFIG_DIR / strConfFile;
dataDir = "/var/opt/" + systemName();
dataDir = "/var/opt/" + std::string(systemName);
}
}
}
+32 -73
View File
@@ -23,101 +23,60 @@
namespace ripple {
Job::Job() : mType(jtINVALID), mJobIndex(0)
{
}
Job::Job(JobType type, std::uint64_t index) : mType(type), mJobIndex(index)
{
}
Job::Job(
JobType type,
std::string const& name,
std::string name,
std::uint64_t index,
LoadMonitor& lm,
std::function<void()> const& job)
: mType(type)
, mJobIndex(index)
, mJob(job)
, mName(name)
, m_queue_time(clock_type::now())
std::reference_wrapper<LoadSampler const> sample,
std::function<void()> job)
: type_(type)
, index_(index)
, queued_(clock_type::now())
, work_(std::move(job))
, loadEvent_(sample, std::move(name), false)
{
}
Job::Job(Job&& other)
: type_(other.type_)
, index_(other.index_)
, queued_(other.queued_)
, work_(std::move(other.work_))
, loadEvent_(std::move(other.loadEvent_))
{
m_loadEvent = std::make_shared<LoadEvent>(std::ref(lm), name, false);
}
JobType
Job::getType() const
{
return mType;
return type_;
}
Job::clock_type::time_point const&
Job::queue_time() const
{
return m_queue_time;
return queued_;
}
std::string const&
Job::description() const
{
return loadEvent_.name();
}
void
Job::doJob()
Job::execute()
{
beast::setCurrentThreadName("doJob: " + mName);
m_loadEvent->start();
m_loadEvent->setName(mName);
beast::setCurrentThreadName("Job: " + loadEvent_.name());
mJob();
loadEvent_.start();
work_();
// Destroy the lambda, otherwise we won't include
// its duration in the time measurement
mJob = nullptr;
}
bool
Job::operator>(const Job& j) const
{
if (mType < j.mType)
return true;
if (mType > j.mType)
return false;
return mJobIndex > j.mJobIndex;
}
bool
Job::operator>=(const Job& j) const
{
if (mType < j.mType)
return true;
if (mType > j.mType)
return false;
return mJobIndex >= j.mJobIndex;
}
bool
Job::operator<(const Job& j) const
{
if (mType < j.mType)
return false;
if (mType > j.mType)
return true;
return mJobIndex < j.mJobIndex;
}
bool
Job::operator<=(const Job& j) const
{
if (mType < j.mType)
return false;
if (mType > j.mType)
return true;
return mJobIndex <= j.mJobIndex;
work_ = nullptr;
}
} // namespace ripple
+178 -268
View File
@@ -20,7 +20,10 @@
#include <xrpld/core/JobQueue.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <mutex>
#include <utility>
namespace ripple {
@@ -31,36 +34,22 @@ JobQueue::JobQueue(
Logs& logs,
perf::PerfLog& perfLog)
: m_journal(journal)
, m_lastJob(0)
, m_invalidJobData(JobTypes::instance().getInvalid(), collector, logs)
, m_processCount(0)
, m_workers(*this, &perfLog, "JobQueue", threadCount)
, data_([&]<std::size_t... I>(std::index_sequence<I...>) {
return std::array<Data, sizeof...(I)>{
Data{jobTypes[I], collector, logs}...};
}(std::make_index_sequence<jobTypes.size()>()))
, m_workers(*this, "JobQueue", threadCount)
, perfLog_(perfLog)
, m_collector(collector)
{
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
hook = m_collector->make_hook(std::bind(&JobQueue::collect, this));
// This is important to do before dispatching any jobs
// so that the logger knows the number of threads.
perfLog_.resizeJobs(threadCount);
hook = m_collector->make_hook([this] { collect(); });
job_count = m_collector->make_gauge("job_count");
{
std::lock_guard lock(m_mutex);
for (auto const& x : JobTypes::instance())
{
JobTypeInfo const& jt = x.second;
// And create dynamic information for all jobs
auto const result(m_jobData.emplace(
std::piecewise_construct,
std::forward_as_tuple(jt.type()),
std::forward_as_tuple(jt, m_collector, logs)));
XRPL_ASSERT(
result.second == true,
"ripple::JobQueue::JobQueue : jobs added");
(void)result.second;
}
}
}
JobQueue::~JobQueue()
@@ -74,65 +63,42 @@ JobQueue::collect()
{
std::lock_guard lock(m_mutex);
job_count = m_jobSet.size();
activeThreadsGauge_ = activeThreads_;
}
bool
JobQueue::addRefCountedJob(
JobType type,
std::string const& name,
JobFunction const& func)
JobFunction func)
{
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::addRefCountedJob : valid input job type");
auto iter(m_jobData.find(type));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::addRefCountedJob : job type found in jobs");
if (iter == m_jobData.end())
return false;
JLOG(m_journal.debug())
<< __func__ << " : Adding job : " << name << " : " << type;
JobTypeData& data(iter->second);
JLOG(m_journal.debug()) << "Adding job '" << name << "' (" << type << ")";
// FIXME: Workaround incorrect client shutdown ordering
// do not add jobs to a queue with no threads
XRPL_ASSERT(
(type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) ||
m_workers.getNumberOfThreads() > 0,
m_workers.count() > 0,
"ripple::JobQueue::addRefCountedJob : threads available or job "
"requires no threads");
{
std::lock_guard lock(m_mutex);
auto result =
m_jobSet.emplace(type, name, ++m_lastJob, data.load(), func);
auto const& job = *result.first;
std::lock_guard lock(m_mutex);
JobType const type(job.getType());
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::addRefCountedJob : has valid job type");
XRPL_ASSERT(
m_jobSet.find(job) != m_jobSet.end(),
"ripple::JobQueue::addRefCountedJob : job found");
perfLog_.jobQueue(type);
auto& d = data_[type];
JobTypeData& data(getJobTypeData(type));
auto r = m_jobSet.emplace(
type, name, ++m_lastJob, d.load.sample(), std::move(func));
assert(r.second && m_jobSet.find(*r.first) != m_jobSet.end());
perfLog_.jobQueue(type);
if (d.waiting + d.running < jobTypes[type].limit)
m_workers.addTask();
else
++d.deferred;
++d.waiting;
if (data.waiting + data.running < getJobLimit(type))
{
m_workers.addTask();
}
else
{
// defer the task until we go below the limit
++data.deferred;
}
++data.waiting;
}
return true;
}
@@ -140,125 +106,100 @@ int
JobQueue::getJobCount(JobType t) const
{
std::lock_guard lock(m_mutex);
JobDataMap::const_iterator c = m_jobData.find(t);
return (c == m_jobData.end()) ? 0 : c->second.waiting;
return data_[t].waiting;
}
int
JobQueue::getJobCountTotal(JobType t) const
{
std::lock_guard lock(m_mutex);
JobDataMap::const_iterator c = m_jobData.find(t);
return (c == m_jobData.end()) ? 0 : (c->second.waiting + c->second.running);
auto const& d = data_[t];
return d.waiting + d.running;
}
int
JobQueue::getJobCountGE(JobType t) const
{
// return the number of jobs at this priority level or greater
int ret = 0;
std::lock_guard lock(m_mutex);
for (auto const& x : m_jobData)
for (std::size_t i = 0; i < data_.size(); ++i)
{
if (x.first >= t)
ret += x.second.waiting;
if (static_cast<JobType>(i) >= t)
ret += data_[i].waiting;
}
return ret;
}
std::unique_ptr<LoadEvent>
JobQueue::makeLoadEvent(JobType t, std::string const& name)
LoadEvent
JobQueue::makeLoadEvent(JobType t, std::string name)
{
JobDataMap::iterator iter(m_jobData.find(t));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::makeLoadEvent : valid job type input");
if (iter == m_jobData.end())
return {};
return std::make_unique<LoadEvent>(iter->second.load(), name, true);
return {data_[t].load.sample(), std::move(name), true};
}
void
JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
{
if (isStopped())
LogicError("JobQueue::addLoadEvents() called after JobQueue stopped");
JobDataMap::iterator iter(m_jobData.find(t));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::addLoadEvents : valid job type input");
iter->second.load().addSamples(count, elapsed);
if (!isStopped()) [[likely]]
data_[t].load.addSamples(count, elapsed);
}
bool
JobQueue::isOverloaded()
{
return std::any_of(m_jobData.begin(), m_jobData.end(), [](auto& entry) {
return entry.second.load().isOver();
});
return std::any_of(
data_.begin(), data_.end(), [](auto& d) { return d.load.isOver(); });
}
Json::Value
JobQueue::getJson(int c)
JobQueue::getJson(int)
{
using namespace std::chrono_literals;
Json::Value ret(Json::objectValue);
ret["threads"] = m_workers.getNumberOfThreads();
ret["threads"] = m_workers.count();
ret["coro.suspended"] = suspendedCoroutines_.load();
ret["coro.total"] = totalCoroutines_.load();
Json::Value priorities = Json::arrayValue;
std::lock_guard lock(m_mutex);
for (auto& x : m_jobData)
for (std::size_t i = 0; i < data_.size(); ++i)
{
XRPL_ASSERT(
x.first != jtINVALID, "ripple::JobQueue::getJson : valid job type");
auto const type = static_cast<JobType>(i);
if (x.first == jtGENERIC)
if (type == jtGENERIC)
continue;
JobTypeData& data(x.second);
auto& d = data_[i];
auto const s = d.load.getStats();
LoadMonitor::Stats stats(data.stats());
int waiting(data.waiting);
int running(data.running);
if ((stats.count != 0) || (waiting != 0) ||
(stats.latencyPeak != 0ms) || (running != 0))
if (s.count || d.waiting || d.running || (s.peakLatency != 0ms))
{
Json::Value& pri = priorities.append(Json::objectValue);
pri["job_type"] = data.name();
pri["job_type"] = std::string(jobTypes[i].name);
if (stats.isOverloaded)
if (s.isOverloaded)
pri["over_target"] = true;
if (waiting != 0)
pri["waiting"] = waiting;
if (d.waiting != 0)
pri["waiting"] = d.waiting;
if (stats.count != 0)
pri["per_second"] = static_cast<int>(stats.count);
if (s.count != 0)
pri["per_second"] = static_cast<int>(s.count);
if (stats.latencyPeak != 0ms)
pri["peak_time"] = static_cast<int>(stats.latencyPeak.count());
if (s.peakLatency != 0ms)
pri["peak_time"] = static_cast<int>(s.peakLatency.count());
if (stats.latencyAvg != 0ms)
pri["avg_time"] = static_cast<int>(stats.latencyAvg.count());
if (s.averageLatency != 0ms)
pri["avg_time"] = static_cast<int>(s.averageLatency.count());
if (running != 0)
pri["in_progress"] = running;
if (d.running != 0)
pri["in_progress"] = d.running;
}
}
@@ -271,179 +212,148 @@ void
JobQueue::rendezvous()
{
std::unique_lock<std::mutex> lock(m_mutex);
cv_.wait(lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
}
JobTypeData&
JobQueue::getJobTypeData(JobType type)
{
JobDataMap::iterator c(m_jobData.find(type));
XRPL_ASSERT(
c != m_jobData.end(),
"ripple::JobQueue::getJobTypeData : valid job type input");
// NIKB: This is ugly and I hate it. We must remove jtINVALID completely
// and use something sane.
if (c == m_jobData.end())
return m_invalidJobData;
return c->second;
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
}
void
JobQueue::stop()
{
stopping_ = true;
using namespace std::chrono_literals;
jobCounter_.join("JobQueue", 1s, m_journal);
if (state expected = state::running; !state_.compare_exchange_strong(
expected, state::stopping, std::memory_order_acq_rel))
{
// After the JobCounter is joined, all jobs have finished executing
// (i.e. returned from `Job::doJob`) and no more are being accepted,
// but there may still be some threads between the return of
// `Job::doJob` and the return of `JobQueue::processTask`. That is why
// we must wait on the condition variable to make these assertions.
std::unique_lock<std::mutex> lock(m_mutex);
cv_.wait(
lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
XRPL_ASSERT(
m_processCount == 0,
"ripple::JobQueue::stop : all processes completed");
XRPL_ASSERT(
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
XRPL_ASSERT(
nSuspend_ == 0, "ripple::JobQueue::stop : no coros suspended");
stopped_ = true;
while (state_.load(std::memory_order_acquire) != state::stopped)
std::this_thread::yield();
return;
}
}
bool
JobQueue::isStopped() const
{
return stopped_;
jobCounter_.join("JobQueue", std::chrono::seconds(1), m_journal);
// All jobs have finished executing (i.e. returned from `Job::doJob`) and
// no more are being accepted, but there may still be some threads between
// the return of `Job::doJob` and the return of `JobQueue::processTask`.
// That is why we must wait on the condition variable to make these
// assertions.
std::unique_lock lock(m_mutex);
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
XRPL_ASSERT(
activeThreads_ == 0,
"ripple::JobQueue::stop : all processes completed");
XRPL_ASSERT(
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
XRPL_ASSERT(
suspendedCoroutines_ == 0,
"ripple::JobQueue::stop : no coros suspended");
state_.store(state::stopped, std::memory_order_release);
}
void
JobQueue::getNextJob(Job& job)
JobQueue::uncaughtException(unsigned int instance, std::exception_ptr eptr)
{
XRPL_ASSERT(
!m_jobSet.empty(), "ripple::JobQueue::getNextJob : non-empty jobs");
std::set<Job>::const_iterator iter;
for (iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
try
{
JobType const type = iter->getType();
XRPL_ASSERT(
type != jtINVALID, "ripple::JobQueue::getNextJob : valid job type");
if (eptr)
std::rethrow_exception(eptr);
JobTypeData& data(getJobTypeData(type));
XRPL_ASSERT(
data.running <= getJobLimit(type),
"ripple::JobQueue::getNextJob : maximum jobs running");
// Run this job if we're running below the limit.
if (data.running < getJobLimit(data.type()))
{
XRPL_ASSERT(
data.waiting > 0,
"ripple::JobQueue::getNextJob : positive data waiting");
--data.waiting;
++data.running;
break;
}
LogicError(
beast::getCurrentThreadName() +
": Uncaught exception handler invoked with no exception_ptr");
}
catch (std::exception const& e)
{
LogicError(
beast::getCurrentThreadName() +
": Exception caught during task processing: " + e.what());
}
catch (...)
{
LogicError(
beast::getCurrentThreadName() +
": Unknown exception caught during task processing");
}
XRPL_ASSERT(
iter != m_jobSet.end(),
"ripple::JobQueue::getNextJob : found next job");
job = *iter;
m_jobSet.erase(iter);
}
void
JobQueue::finishJob(JobType type)
{
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::finishJob : valid input job type");
JobTypeData& data = getJobTypeData(type);
// Queue a deferred task if possible
if (data.deferred > 0)
{
XRPL_ASSERT(
data.running + data.waiting >= getJobLimit(type),
"ripple::JobQueue::finishJob : job limit");
--data.deferred;
m_workers.addTask();
}
--data.running;
}
void
JobQueue::processTask(int instance)
JobQueue::processTask(unsigned int instance)
{
JobType type;
{
Job job = [this] {
std::lock_guard lock(m_mutex);
assert(!m_jobSet.empty());
for (auto iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
{
JobType const t = iter->getType();
auto& d = data_[t];
assert(d.running <= jobTypes[t].limit);
if (d.running < jobTypes[t].limit)
{
assert(d.waiting > 0);
--d.waiting;
++d.running;
++activeThreads_;
return std::move(m_jobSet.extract(iter).value());
}
}
LogicError("Attempt to get a job when none are available");
}();
type = job.getType();
using namespace std::chrono;
Job::clock_type::time_point const start_time(Job::clock_type::now());
auto const start_time = Job::clock_type::now();
auto const q_time = ceil<microseconds>(start_time - job.queue_time());
perfLog_.jobStart(type, q_time, start_time, instance);
JLOG(m_journal.trace())
<< "Starting: " << jobTypes[type].name
<< " (q_time=" << q_time.count() << " microseconds)";
job.execute();
auto const x_time =
ceil<microseconds>(Job::clock_type::now() - start_time);
JLOG(m_journal.trace())
<< "Finished: " << jobTypes[type].name
<< " (x_time=" << x_time.count() << " microseconds)";
if (x_time >= 10ms || q_time >= 10ms)
{
Job job;
{
std::lock_guard lock(m_mutex);
getNextJob(job);
++m_processCount;
}
type = job.getType();
JobTypeData& data(getJobTypeData(type));
JLOG(m_journal.trace()) << "Doing " << data.name() << "job";
// The amount of time that the job was in the queue
auto const q_time =
ceil<microseconds>(start_time - job.queue_time());
perfLog_.jobStart(type, q_time, start_time, instance);
job.doJob();
// The amount of time it took to execute the job
auto const x_time =
ceil<microseconds>(Job::clock_type::now() - start_time);
if (x_time >= 10ms || q_time >= 10ms)
{
getJobTypeData(type).dequeue.notify(q_time);
getJobTypeData(type).execute.notify(x_time);
}
perfLog_.jobFinish(type, x_time, instance);
data_[type].dequeue.notify(q_time);
data_[type].execute.notify(x_time);
}
perfLog_.jobFinish(type, x_time, instance);
// Note that the Job object gets destroyed at this point.
}
std::lock_guard lock(m_mutex);
auto& d = data_[type];
// Queue a deferred task if possible. This doesn't mean the task will
// run next; only that one is now runnable.
if (d.deferred > 0)
{
std::lock_guard lock(m_mutex);
// Job should be destroyed before stopping
// otherwise destructors with side effects can access
// parent objects that are already destroyed.
finishJob(type);
if (--m_processCount == 0 && m_jobSet.empty())
cv_.notify_all();
assert(d.running + d.waiting >= jobTypes[type].limit);
--d.deferred;
m_workers.addTask();
}
// Note that when Job::~Job is called, the last reference
// to the associated LoadEvent object (in the Job) may be destroyed.
}
--d.running;
int
JobQueue::getJobLimit(JobType type)
{
JobTypeInfo const& j(JobTypes::instance().get(type));
XRPL_ASSERT(
j.type() != jtINVALID,
"ripple::JobQueue::getJobLimit : valid job type");
return j.limit();
if (--activeThreads_ == 0 && m_jobSet.empty())
cv_.notify_all();
}
} // namespace ripple
+50 -27
View File
@@ -18,25 +18,63 @@
//==============================================================================
#include <xrpld/core/LoadEvent.h>
#include <xrpld/core/LoadMonitor.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <iomanip>
namespace ripple {
LoadEvent::LoadEvent(
LoadMonitor& monitor,
std::string const& name,
std::reference_wrapper<LoadSampler const> callback,
std::string name,
bool shouldStart)
: monitor_(monitor)
, running_(shouldStart)
, name_(name)
, mark_{std::chrono::steady_clock::now()}
: name_(std::move(name))
, callback_(callback)
, mark_(std::chrono::steady_clock::now())
, timeWaiting_{}
, timeRunning_{}
, running_(shouldStart)
, neutered_(false)
{
}
LoadEvent::LoadEvent(LoadEvent&& other)
: name_(std::move(other.name_))
, callback_(other.callback_)
, mark_(other.mark_)
, timeWaiting_(other.timeWaiting_)
, timeRunning_(other.timeRunning_)
, running_(other.running_)
, neutered_(other.neutered_)
{
other.running_ = false;
other.neutered_ = true;
}
LoadEvent&
LoadEvent::operator=(LoadEvent&& other)
{
if (this != &other)
{
// If we are currently measuring something, report it before we
// take over the other event, so that the sample isn't lost.
if (running_)
stop();
name_ = std::move(other.name_);
callback_ = other.callback_;
running_ = other.running_;
mark_ = other.mark_;
timeWaiting_ = other.timeWaiting_;
timeRunning_ = other.timeRunning_;
neutered_ = other.neutered_;
// Leave the moved-from object in a sane but "neutered" state.
other.running_ = false;
other.neutered_ = true;
}
return *this;
}
LoadEvent::~LoadEvent()
{
if (running_)
@@ -49,27 +87,11 @@ LoadEvent::name() const
return name_;
}
std::chrono::steady_clock::duration
LoadEvent::waitTime() const
{
return timeWaiting_;
}
std::chrono::steady_clock::duration
LoadEvent::runTime() const
{
return timeRunning_;
}
void
LoadEvent::setName(std::string const& name)
{
name_ = name;
}
void
LoadEvent::start()
{
XRPL_ASSERT(!neutered_, "ripple::LoadEvent::start : is neutered");
auto const now = std::chrono::steady_clock::now();
// If we had already called start, this call will
@@ -91,7 +113,8 @@ LoadEvent::stop()
mark_ = now;
running_ = false;
monitor_.addLoadSample(*this);
if (!neutered_)
callback_(name_.c_str(), timeRunning_, timeWaiting_);
}
} // namespace ripple
+90 -131
View File
@@ -23,104 +23,95 @@
namespace ripple {
/*
TODO
----
- Use Journal for logging
*/
//------------------------------------------------------------------------------
LoadMonitor::Stats::Stats()
: count(0), latencyAvg(0), latencyPeak(0), isOverloaded(false)
{
}
//------------------------------------------------------------------------------
LoadMonitor::LoadMonitor(beast::Journal j)
: mCounts(0)
, mLatencyEvents(0)
, mLatencyMSAvg(0)
, mLatencyMSPeak(0)
, mTargetLatencyAvg(0)
, mTargetLatencyPk(0)
, mLastUpdate(UptimeClock::now())
LoadMonitor::LoadMonitor(
std::optional<std::chrono::milliseconds> targetAverageLatency,
std::optional<std::chrono::milliseconds> targetPeakLatency,
beast::Journal j)
: targetAverageLatency_([&targetAverageLatency]() {
using namespace std::chrono_literals;
if (targetAverageLatency.has_value() && targetAverageLatency == 0ms)
targetAverageLatency.reset();
return targetAverageLatency;
}())
, targetPeakLatency_([&targetPeakLatency]() {
using namespace std::chrono_literals;
if (targetPeakLatency.has_value() && targetPeakLatency == 0ms)
targetPeakLatency.reset();
return targetPeakLatency;
}())
, lastUpdate_(UptimeClock::now())
, j_(j)
, eventCallback_([this](
char const* name,
std::chrono::steady_clock::duration run,
std::chrono::steady_clock::duration wait) {
using namespace std::chrono;
auto const latency = [total = run + wait] {
// Don't include "jitter" as part of the latency
if (total < 5ms)
return 0ms;
return round<milliseconds>(total);
}();
if (latency > targetPeakLatency_.value_or(1s))
{
// Log at a higher severity if the latency is much greater than
// the peak latency we want to target.
auto mj = (latency > 2 * targetPeakLatency_.value_or(latency))
? j_.warn()
: j_.info();
JLOG(mj) << "Job: " << name
<< " run: " << round<milliseconds>(run).count() << "ms"
<< " wait: " << round<milliseconds>(wait).count() << "ms";
}
addSamples(1, latency);
})
{
}
// VFALCO NOTE WHY do we need "the mutex?" This dependence on
// a hidden global, especially a synchronization primitive,
// is a flawed design.
// It's not clear exactly which data needs to be protected.
//
// call with the mutex
void
LoadMonitor::update()
{
using namespace std::chrono_literals;
auto now = UptimeClock::now();
if (now == mLastUpdate) // current
return;
// VFALCO TODO Why 8?
if ((now < mLastUpdate) || (now > (mLastUpdate + 8s)))
if (auto const now = UptimeClock::now(); now != lastUpdate_)
{
// way out of date
mCounts = 0;
mLatencyEvents = 0;
mLatencyMSAvg = 0ms;
mLatencyMSPeak = 0ms;
mLastUpdate = now;
return;
// VFALCO TODO Why 8?
if ((now < lastUpdate_) || (now > (lastUpdate_ + 8s)))
{
// way out of date
samples_ = 0;
averageLatency_ = 0ms;
peakLatency_ = 0ms;
lastUpdate_ = now;
return;
}
// do exponential decay
/*
David:
"Imagine if you add 10 to something every second. And you
also reduce it by 1/4 every second. It will "idle" at 40,
correponding to 10 counts per second."
*/
do
{
lastUpdate_ += 1s;
samples_ -= ((samples_ + 3) / 4);
averageLatency_ -= (averageLatency_ / 4);
peakLatency_ -= (peakLatency_ / 4);
} while (lastUpdate_ < now);
}
// do exponential decay
/*
David:
"Imagine if you add 10 to something every second. And you
also reduce it by 1/4 every second. It will "idle" at 40,
correponding to 10 counts per second."
*/
do
{
mLastUpdate += 1s;
mCounts -= ((mCounts + 3) / 4);
mLatencyEvents -= ((mLatencyEvents + 3) / 4);
mLatencyMSAvg -= (mLatencyMSAvg / 4);
mLatencyMSPeak -= (mLatencyMSPeak / 4);
} while (mLastUpdate < now);
}
void
LoadMonitor::addLoadSample(LoadEvent const& s)
{
using namespace std::chrono;
auto const total = s.runTime() + s.waitTime();
// Don't include "jitter" as part of the latency
auto const latency = total < 2ms ? 0ms : round<milliseconds>(total);
if (latency > 500ms)
{
auto mj = (latency > 1s) ? j_.warn() : j_.info();
JLOG(mj) << "Job: " << s.name()
<< " run: " << round<milliseconds>(s.runTime()).count() << "ms"
<< " wait: " << round<milliseconds>(s.waitTime()).count()
<< "ms";
}
addSamples(1, latency);
}
/* Add multiple samples
@param count The number of samples to add
@param latencyMS The total number of milliseconds
@param latency The total number of milliseconds
*/
void
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
@@ -128,77 +119,45 @@ LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
std::lock_guard sl(mutex_);
update();
mCounts += count;
mLatencyEvents += count;
mLatencyMSAvg += latency;
mLatencyMSPeak += latency;
samples_ += count;
averageLatency_ += latency;
peakLatency_ += latency;
auto const latencyPeak = mLatencyEvents * latency * 4 / count;
auto const latencyPeak = samples_ * latency * 4 / count;
if (mLatencyMSPeak < latencyPeak)
mLatencyMSPeak = latencyPeak;
}
void
LoadMonitor::setTargetLatency(
std::chrono::milliseconds avg,
std::chrono::milliseconds pk)
{
mTargetLatencyAvg = avg;
mTargetLatencyPk = pk;
}
bool
LoadMonitor::isOverTarget(
std::chrono::milliseconds avg,
std::chrono::milliseconds peak)
{
using namespace std::chrono_literals;
return (mTargetLatencyPk > 0ms && (peak > mTargetLatencyPk)) ||
(mTargetLatencyAvg > 0ms && (avg > mTargetLatencyAvg));
if (peakLatency_ < latencyPeak)
peakLatency_ = latencyPeak;
}
bool
LoadMonitor::isOver()
{
std::lock_guard sl(mutex_);
update();
if (mLatencyEvents == 0)
return 0;
return isOverTarget(
mLatencyMSAvg / (mLatencyEvents * 4),
mLatencyMSPeak / (mLatencyEvents * 4));
return getStats().isOverloaded;
}
LoadMonitor::Stats
LoadMonitor::getStats()
{
using namespace std::chrono_literals;
Stats stats;
std::lock_guard sl(mutex_);
update();
stats.count = mCounts / 4;
Stats s;
s.count = samples_ / 4;
if (mLatencyEvents == 0)
if (samples_ != 0)
{
stats.latencyAvg = 0ms;
stats.latencyPeak = 0ms;
}
else
{
stats.latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4);
stats.latencyPeak = mLatencyMSPeak / (mLatencyEvents * 4);
s.averageLatency = averageLatency_ / (samples_ * 4);
s.peakLatency = peakLatency_ / (samples_ * 4);
}
stats.isOverloaded = isOverTarget(stats.latencyAvg, stats.latencyPeak);
s.isOverloaded =
(s.averageLatency > targetAverageLatency_.value_or(s.averageLatency)) ||
(s.peakLatency > targetPeakLatency_.value_or(s.peakLatency));
return stats;
return s;
}
} // namespace ripple
+88 -229
View File
@@ -18,265 +18,124 @@
//==============================================================================
#include <xrpld/core/detail/Workers.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <chrono>
#include <thread>
namespace ripple {
Workers::Workers(
Callback& callback,
perf::PerfLog* perfLog,
std::string const& threadNames,
int numberOfThreads)
: m_callback(callback)
, perfLog_(perfLog)
, m_threadNames(threadNames)
, m_allPaused(true)
, m_semaphore(0)
, m_numberOfThreads(0)
, m_activeCount(0)
, m_pauseCount(0)
, m_runningTaskCount(0)
std::string const& name,
unsigned int count)
: callback_(callback)
{
setNumberOfThreads(numberOfThreads);
assert(count != 0);
// It is important that head and tail are equal but non-zero on
// startup: we use zero as a special value to indicate that the
// thread must terminate; if zero was a 'legal' value then we'd
// encounter the ABA problem with std::atomic::wait.
assert(head_ == tail_ && head_ != 0);
while (count--)
{
// We need to increment this outside of the thread, to avoid a
// subtle (if unlikely) race condition during shutdown.
++threads_;
std::thread th(
[this, name](unsigned int instance) {
auto const n = name + ":" + std::to_string(instance);
while (head_)
{
auto t = tail_.load();
auto h = head_.load();
assert((h == 0) || (h >= t));
if (h == t)
{
// Only pay for the thread rename when we are
// actually about to go to sleep.
beast::setCurrentThreadName(n + " [zZz]");
while (h == t)
{
// This will block until we're notified.
head_.wait(h);
t = tail_.load();
h = head_.load();
}
}
// As long as we aren't stopping and there is a task
// that's waiting, we try to do work.
if (h > t && tail_.compare_exchange_strong(t, t + 1))
{
// Restore the name, in case the previous callback
// had changed it.
beast::setCurrentThreadName(n);
// Exceptions should never bubble up to here but
// just in case, if one does catch it.
try
{
callback_.processTask(instance);
}
catch (...)
{
callback_.uncaughtException(
instance, std::current_exception());
}
}
}
// Track number of threads
--threads_;
},
count);
// We detach from this thread; it will continue to run in the
// background until we instruct it to stop.
th.detach();
}
}
Workers::~Workers()
{
stop();
deleteWorkers(m_everyone);
}
int
Workers::getNumberOfThreads() const noexcept
{
return m_numberOfThreads;
}
// VFALCO NOTE if this function is called quickly to reduce then
// increase the number of threads, it could result in
// more paused threads being created than expected.
//
void
Workers::setNumberOfThreads(int numberOfThreads)
{
static int instance{0};
if (m_numberOfThreads == numberOfThreads)
return;
if (perfLog_)
perfLog_->resizeJobs(numberOfThreads);
if (numberOfThreads > m_numberOfThreads)
{
// Increasing the number of working threads
int const amount = numberOfThreads - m_numberOfThreads;
for (int i = 0; i < amount; ++i)
{
// See if we can reuse a paused worker
Worker* worker = m_paused.pop_front();
if (worker != nullptr)
{
// If we got here then the worker thread is at [1]
// This will unblock their call to wait()
//
worker->notify();
}
else
{
worker = new Worker(*this, m_threadNames, instance++);
m_everyone.push_front(worker);
}
}
}
else
{
// Decreasing the number of working threads
int const amount = m_numberOfThreads - numberOfThreads;
for (int i = 0; i < amount; ++i)
{
++m_pauseCount;
// Pausing a thread counts as one "internal task"
m_semaphore.notify();
}
}
m_numberOfThreads = numberOfThreads;
assert(head_.load() == 0 && threads_.load() == 0);
}
void
Workers::stop()
{
setNumberOfThreads(0);
head_.store(0);
head_.notify_all();
std::unique_lock<std::mutex> lk{m_mut};
m_cv.wait(lk, [this] { return m_allPaused; });
lk.unlock();
XRPL_ASSERT(
numberOfCurrentlyRunningTasks() == 0,
"ripple::Workers::stop : zero running tasks");
while (threads_.load() != 0)
std::this_thread::yield();
}
void
Workers::addTask()
{
m_semaphore.notify();
}
auto h = head_.load();
int
Workers::numberOfCurrentlyRunningTasks() const noexcept
{
return m_runningTaskCount.load();
}
void
Workers::deleteWorkers(beast::LockFreeStack<Worker>& stack)
{
for (;;)
while (h != 0)
{
Worker* const worker = stack.pop_front();
if (worker != nullptr)
if (head_.compare_exchange_strong(h, h + 1))
{
// This call blocks until the thread orderly exits
delete worker;
}
else
{
break;
head_.notify_one();
h = 0;
}
}
}
//------------------------------------------------------------------------------
Workers::Worker::Worker(
Workers& workers,
std::string const& threadName,
int const instance)
: m_workers{workers}
, threadName_{threadName}
, instance_{instance}
, wakeCount_{0}
, shouldExit_{false}
{
thread_ = std::thread{&Workers::Worker::run, this};
}
Workers::Worker::~Worker()
{
{
std::lock_guard lock{mutex_};
++wakeCount_;
shouldExit_ = true;
}
wakeup_.notify_one();
thread_.join();
}
void
Workers::Worker::notify()
{
std::lock_guard lock{mutex_};
++wakeCount_;
wakeup_.notify_one();
}
void
Workers::Worker::run()
{
bool shouldExit = true;
do
{
// Increment the count of active workers, and if
// we are the first one then reset the "all paused" event
//
if (++m_workers.m_activeCount == 1)
{
std::lock_guard lk{m_workers.m_mut};
m_workers.m_allPaused = false;
}
for (;;)
{
// Put the name back in case the callback changed it
beast::setCurrentThreadName(threadName_);
// Acquire a task or "internal task."
//
m_workers.m_semaphore.wait();
// See if there's a pause request. This
// counts as an "internal task."
//
int pauseCount = m_workers.m_pauseCount.load();
if (pauseCount > 0)
{
// Try to decrement
pauseCount = --m_workers.m_pauseCount;
if (pauseCount >= 0)
{
// We got paused
break;
}
else
{
// Undo our decrement
++m_workers.m_pauseCount;
}
}
// We couldn't pause so we must have gotten
// unblocked in order to process a task.
//
++m_workers.m_runningTaskCount;
m_workers.m_callback.processTask(instance_);
--m_workers.m_runningTaskCount;
}
// Any worker that goes into the paused list must
// guarantee that it will eventually block on its
// event object.
//
m_workers.m_paused.push_front(this);
// Decrement the count of active workers, and if we
// are the last one then signal the "all paused" event.
//
if (--m_workers.m_activeCount == 0)
{
std::lock_guard lk{m_workers.m_mut};
m_workers.m_allPaused = true;
m_workers.m_cv.notify_all();
}
// Set inactive thread name.
beast::setCurrentThreadName("(" + threadName_ + ")");
// [1] We will be here when the paused list is popped
//
// We block on our condition_variable, wakeup_, a requirement of being
// put into the paused list.
//
// wakeup_ will get signaled by either Worker::notify() or ~Worker.
{
std::unique_lock<std::mutex> lock{mutex_};
wakeup_.wait(lock, [this] { return this->wakeCount_ > 0; });
shouldExit = shouldExit_;
--wakeCount_;
}
} while (!shouldExit);
}
} // namespace ripple
+73 -142
View File
@@ -20,61 +20,33 @@
#ifndef RIPPLE_CORE_WORKERS_H_INCLUDED
#define RIPPLE_CORE_WORKERS_H_INCLUDED
#include <xrpld/core/detail/semaphore.h>
#include <xrpl/beast/core/LockFreeStack.h>
#include <atomic>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <string>
#include <thread>
namespace ripple {
namespace perf {
class PerfLog;
}
/** A simple thread pool.
/**
* `Workers` is effectively a thread pool. The constructor takes a "callback"
* that has a `void processTask(int instance)` method, and a number of
* workers. It creates that many `Worker`s and then waits for calls to
* `Workers::addTask()`. It holds a semaphore that counts the number of
* pending "tasks", and a condition variable for the event when the last
* worker pauses itself.
*
* A "task" is just a call to the callback's `processTask` method.
* "Adding a task" means calling that method now, or remembering to call it in
* the future.
* This is implemented with a semaphore.
* If there are any workers waiting when a task is added, then one will be
* woken to claim the task.
* If not, then the next worker to wait on the semaphore will claim the task.
*
* Creating a `Worker` creates a thread that calls `Worker::run()`. When that
* thread enters `Worker::run`, it increments the count of active workers in
* the parent `Workers` object and then tries to claim a task, which blocks if
* there are none pending.
* It will be unblocked whenever the semaphore is notified (i.e. when the
* number of pending tasks is incremented).
* That only happens in two circumstances: (1) when
* `Workers::addTask` is called and (2) when `Workers` wants to pause some
* workers ("pause one worker" is considered one task), which happens when
* someone wants to stop the workers or shrink the threadpool. No worker
* threads are ever destroyed until `Workers` is destroyed; it merely pauses
* workers until then.
*
* When a waiting worker is woken, it checks whether `Workers` is trying to
* pause workers. If so, it changes its status from active to paused and
* blocks on
* its own condition variable. If not, then it calls `processTask` on the
* "callback" held by `Workers`.
*
* When a paused worker is woken, it checks whether it should exit. The signal
* to exit is only set in the destructor of `Worker`, which unblocks the
* paused thread and waits for it to exit. A `Worker::run` thread checks
* whether it needs to exit only when it is woken from a pause (not when it is
* woken from waiting). This is why the destructor for `Workers` pauses all
* the workers before destroying them.
This class is a simple, fixed-sized thread pool. The pool only tracks
the number of outstanding tasks, and dispatches work. When the pool detects
that there is no work to be done, it puts threads to sleep.
The pool does not decide which task to run; that is handled by a callback
that the pool invokes. This makes it possible to implement the dispatch
strategy (e.g. FIFO or priority queues) that makes sense without requiring
changes in the thread pool itself.
Threads will continue to run (or sleep) until the thread pool is stopped
which can happen via an explicit call to the stop method, or automatically
by the pool's destructor.
Note that servicing existing tasks take priority over stopping and stopping
takes priority over servicing new tasks; this means that once a stop request
has been made, the worker threads will complete their current tasks (if any)
and then exit, potentially leaving work unfinished.
*/
class Workers
{
@@ -90,50 +62,54 @@ public:
/** Perform a task.
The call is made on a thread owned by Workers. It is important
that you only process one task from inside your callback. Each
call to addTask will result in exactly one call to processTask.
The function is invoked precisely once for every call to the
thread pool's addTask method. It executes on one of the thread
pool's threads.
This function should process precisely one task.
@param instance The worker thread instance.
@throws This function should NOT throw an exception; if it does
the exception will be captured and passed to the
uncaughtException callback.
@see Workers::addTask
*/
virtual void
processTask(int instance) = 0;
processTask(unsigned int instance) = 0;
/** Indicates that processTask threw an unexpected exception.
@param instance The worker thread instance.
@param eptr The exception that was thrown.
* */
virtual void
uncaughtException(unsigned int, std::exception_ptr)
{
// Default implementation does nothing
}
};
/** Create the object.
A number of initial threads may be optionally specified. The
default is to create one thread per CPU.
@param threadNames The name given to each created worker thread.
@param callback The task selection & execution algorithm.
@param name The name for this pool (used to name threads)
@param size The number of threads (must not be 0!) for this thread pool
*/
explicit Workers(
Callback& callback,
perf::PerfLog* perfLog,
std::string const& threadNames = "Worker",
int numberOfThreads =
static_cast<int>(std::thread::hardware_concurrency()));
std::string const& name,
unsigned int count);
~Workers();
/** Retrieve the desired number of threads.
This just returns the number of active threads that were requested. If
there was a recent call to setNumberOfThreads, the actual number of
active threads may be temporarily different from what was last requested.
@note This function is not thread-safe.
*/
int
getNumberOfThreads() const noexcept;
/** Set the desired number of threads.
@note This function is not thread-safe.
*/
void
setNumberOfThreads(int numberOfThreads);
/** Retrieve the number of threads in the thread pool. */
unsigned int
count() const noexcept
{
return threads_.load();
}
/** Pause all threads and wait until they are paused.
@@ -157,79 +133,34 @@ public:
void
addTask();
/** Get the number of currently executing calls of Callback::processTask.
While this function is thread-safe, the value may not stay
accurate for very long. It's mainly for diagnostic purposes.
*/
int
numberOfCurrentlyRunningTasks() const noexcept;
//--------------------------------------------------------------------------
private:
struct PausedTag
{
explicit PausedTag() = default;
};
Callback& callback_;
/* A Worker executes tasks on its provided thread.
/// This represents the total number of threads in the thread pool:
std::atomic<unsigned int> threads_ = 0;
These are the states:
/** Queued task tracking
Active: Running the task processing loop.
Idle: Active, but blocked on waiting for a task.
Paused: Blocked waiting to exit or become active.
*/
class Worker : public beast::LockFreeStack<Worker>::Node,
public beast::LockFreeStack<Worker, PausedTag>::Node
{
public:
Worker(
Workers& workers,
std::string const& threadName,
int const instance);
To minimize overhead, we track two things: total number of tasks
added, and total number of tasks dispatched. The key insight is
that both of these numbers are only ever incremented and never
decremented. Along with atomic operations, this allows us to
write lock free code.
~Worker();
The choice of 64-bit unsigned integers helps to avoid overflow. A
32-bit value would overflow too fast even at low queue rates (e.g.
at a 1,000 tasks per second, the counter would overflow in about 50
days). With 64 bits, even at a truly obscene rate of 1,000,000,000
increments per second, this counter is good for over 580 years, at
which point the server is really due for a reboot.
*/
/** @{ */
/// The total number of tasks that have been queued since startup.
std::atomic<std::uint64_t> head_ = 1;
void
notify();
private:
void
run();
private:
Workers& m_workers;
std::string const threadName_;
int const instance_;
std::thread thread_;
std::mutex mutex_;
std::condition_variable wakeup_;
int wakeCount_; // how many times to un-pause
bool shouldExit_;
};
private:
static void
deleteWorkers(beast::LockFreeStack<Worker>& stack);
private:
Callback& m_callback;
perf::PerfLog* perfLog_;
std::string m_threadNames; // The name to give each thread
std::condition_variable m_cv; // signaled when all threads paused
std::mutex m_mut;
bool m_allPaused;
semaphore m_semaphore; // each pending task is 1 resource
int m_numberOfThreads; // how many we want active now
std::atomic<int> m_activeCount; // to know when all are paused
std::atomic<int> m_pauseCount; // how many threads need to pause now
std::atomic<int>
m_runningTaskCount; // how many calls to processTask() active
beast::LockFreeStack<Worker> m_everyone; // holds all created workers
beast::LockFreeStack<Worker, PausedTag>
m_paused; // holds just paused workers
/// The total number of tasks that have been dispatched for processing.
std::atomic<std::uint64_t> tail_ = 1;
/** @} */
};
} // namespace ripple
-83
View File
@@ -1,83 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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.
*/
//==============================================================================
#ifndef RIPPLE_CORE_SEMAPHORE_H_INCLUDED
#define RIPPLE_CORE_SEMAPHORE_H_INCLUDED
#include <condition_variable>
#include <mutex>
namespace ripple {
template <class Mutex, class CondVar>
class basic_semaphore
{
private:
Mutex m_mutex;
CondVar m_cond;
std::size_t m_count;
public:
using size_type = std::size_t;
/** Create the semaphore, with an optional initial count.
If unspecified, the initial count is zero.
*/
explicit basic_semaphore(size_type count = 0) : m_count(count)
{
}
/** Increment the count and unblock one waiting thread. */
void
notify()
{
std::lock_guard lock{m_mutex};
++m_count;
m_cond.notify_one();
}
/** Block until notify is called. */
void
wait()
{
std::unique_lock lock{m_mutex};
while (m_count == 0)
m_cond.wait(lock);
--m_count;
}
/** Perform a non-blocking wait.
@return `true` If the wait would be satisfied.
*/
bool
try_wait()
{
std::lock_guard lock{m_mutex};
if (m_count == 0)
return false;
--m_count;
return true;
}
};
using semaphore = basic_semaphore<std::mutex, std::condition_variable>;
} // namespace ripple
#endif
+1 -1
View File
@@ -74,7 +74,7 @@ createHTTPPost(
// BuildInfo::getFullVersionString () as well?
s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n"
<< "User-Agent: " << systemName() << "-json-rpc/v1\r\n"
<< "User-Agent: " << systemName << "-json-rpc/v1\r\n"
<< "Host: " << strHost << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
+14 -3
View File
@@ -31,12 +31,23 @@ class DummyScheduler : public Scheduler
public:
DummyScheduler() = default;
~DummyScheduler() = default;
void
scheduleTask(Task& task) override;
scheduleTask(Task& task) override
{
// Invoke the task synchronously.
task.performScheduledTask();
}
void
onFetch(FetchReport const& report) override;
onFetch(FetchReport const& report) override
{
}
void
onBatchWrite(BatchWriteReport const& report) override;
onBatchWrite(BatchWriteReport const& report) override
{
}
};
} // namespace NodeStore
@@ -1,43 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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 <xrpld/nodestore/DummyScheduler.h>
namespace ripple {
namespace NodeStore {
void
DummyScheduler::scheduleTask(Task& task)
{
// Invoke the task synchronously.
task.performScheduledTask();
}
void
DummyScheduler::onFetch(const FetchReport& report)
{
}
void
DummyScheduler::onBatchWrite(const BatchWriteReport& report)
{
}
} // namespace NodeStore
} // namespace ripple
+1 -1
View File
@@ -177,7 +177,7 @@ private:
std::queue<std::shared_ptr<Message>> send_queue_;
bool gracefulClose_ = false;
int large_sendq_ = 0;
std::unique_ptr<LoadEvent> load_event_;
std::optional<LoadEvent> load_event_;
// The highest sequence of each PublisherList that has
// been sent to or received from this peer.
hash_map<PublicKey, std::size_t> publisherListSequences_;
+10 -12
View File
@@ -41,9 +41,7 @@
namespace ripple {
namespace perf {
PerfLogImp::Counters::Counters(
std::set<char const*> const& labels,
JobTypes const& jobTypes)
PerfLogImp::Counters::Counters(std::set<char const*> const& labels)
{
{
// populateRpc
@@ -63,9 +61,9 @@ PerfLogImp::Counters::Counters(
{
// populateJq
jq_.reserve(jobTypes.size());
for (auto const& [jobType, _] : jobTypes)
for (auto const& jobType : jobTypes)
{
auto const inserted = jq_.emplace(jobType, Jq()).second;
auto const inserted = jq_.emplace(jobType.type, Jq()).second;
if (!inserted)
{
// Ensure that no other function populates this entry.
@@ -148,7 +146,7 @@ PerfLogImp::Counters::countersJson() const
j[jss::running_duration_us] =
std::to_string(value.runningDuration.count());
totalJq.runningDuration += value.runningDuration;
jqobj[JobTypes::name(proc.first)] = j;
jqobj[std::string(jobTypes[proc.first].name)] = j;
}
if (totalJq.queued)
@@ -185,12 +183,12 @@ PerfLogImp::Counters::currentJson() const
for (auto const& j : jobs)
{
if (j.first == jtINVALID)
if (!j)
continue;
Json::Value jobj(Json::objectValue);
jobj[jss::job] = JobTypes::name(j.first);
jobj[jss::job] = std::string(jobTypes[j->first].name);
jobj[jss::duration_us] = std::to_string(
std::chrono::duration_cast<microseconds>(present - j.second)
std::chrono::duration_cast<microseconds>(present - j->second)
.count());
jobsArray.append(jobj);
}
@@ -414,7 +412,7 @@ PerfLogImp::jobStart(
}
std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance] = {type, startTime};
counters_.jobs_[instance].emplace(type, startTime);
}
void
@@ -434,7 +432,7 @@ PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
}
std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance] = {jtINVALID, steady_time_point()};
counters_.jobs_[instance].reset();
}
void
@@ -442,7 +440,7 @@ PerfLogImp::resizeJobs(int const resize)
{
std::lock_guard lock(counters_.jobsMutex_);
if (resize > counters_.jobs_.size())
counters_.jobs_.resize(resize, {jtINVALID, steady_time_point()});
counters_.jobs_.resize(resize, std::nullopt);
}
void
+6 -3
View File
@@ -25,12 +25,15 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/jss.h>
#include <boost/asio/ip/host_name.hpp>
#include <condition_variable>
#include <cstdint>
#include <fstream>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <type_traits>
@@ -108,12 +111,12 @@ class PerfLogImp : public PerfLog
// keys and values are created before more threads are started.
std::unordered_map<std::string, Locked<Rpc>> rpc_;
std::unordered_map<JobType, Locked<Jq>> jq_;
std::vector<std::pair<JobType, steady_time_point>> jobs_;
std::vector<std::optional<std::pair<JobType, steady_time_point>>> jobs_;
mutable std::mutex jobsMutex_;
std::unordered_map<std::uint64_t, MethodStart> methods_;
mutable std::mutex methodsMutex_;
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes);
Counters(std::set<char const*> const& labels);
Json::Value
countersJson() const;
Json::Value
@@ -124,7 +127,7 @@ class PerfLogImp : public PerfLog
Application& app_;
beast::Journal const j_;
std::function<void()> const signalStop_;
Counters counters_{ripple::RPC::getHandlerNames(), JobTypes::instance()};
Counters counters_{ripple::RPC::getHandlerNames()};
std::ofstream logFile_;
std::thread thread_;
std::mutex mutex_;
+10 -7
View File
@@ -193,7 +193,7 @@ getAccountObjects(
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
// this is a mutable version of limit, used to seemlessly switch
// this is a mutable version of limit, used to seamlessly switch
// to iterating directory entries when nftokenpages are exhausted
uint32_t mlimit = limit;
@@ -459,7 +459,7 @@ ledgerFromRequest(T& ledger, JsonContext& context)
indexValue = legacyLedger;
}
if (hashValue)
if (!hashValue.isNull())
{
if (!hashValue.isString())
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
@@ -470,6 +470,9 @@ ledgerFromRequest(T& ledger, JsonContext& context)
return getLedger(ledger, ledgerHash, context);
}
if (!indexValue.isConvertibleTo(Json::stringValue))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto const index = indexValue.asString();
if (index == "current" || index.empty())
@@ -481,11 +484,11 @@ ledgerFromRequest(T& ledger, JsonContext& context)
if (index == "closed")
return getLedger(ledger, LedgerShortcut::CLOSED, context);
std::uint32_t iVal;
if (beast::lexicalCastChecked(iVal, index))
return getLedger(ledger, iVal, context);
std::uint32_t val;
if (!beast::lexicalCastChecked(val, index))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
return getLedger(ledger, val, context);
}
} // namespace
@@ -701,7 +704,7 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
return Status::OK;
}
// Explicit instantiaion of above three functions
// Explicit instantiation of above three functions
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
+5 -3
View File
@@ -538,7 +538,7 @@ pwaReply(
{
std::string out =
"HTTP/1.1 " + std::to_string(status) + " " + pwaReason(status) + "\r\n";
out += "Server: " + systemName() +
out += "Server: " + std::string(systemName) +
"\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n"
"Referrer-Policy: no-referrer\r\n";
for (auto const& h : headers)
@@ -1985,8 +1985,10 @@ ServerHandler::statusResponse(http_request_type const& request) const
if (app_.serverOkay(reason))
{
msg.result(boost::beast::http::status::ok);
msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
" Test page for rippled</title></head><body><h1>" + systemName() +
msg.body() = "<!DOCTYPE html><html><head><title>" +
std::string(systemName) +
" Test page for rippled</title></head><body><h1>" +
std::string(systemName) +
" Test</h1><p>This page shows rippled http(s) "
"connectivity is working.</p></body></html>";
}
File diff suppressed because it is too large. Load diff
+299
View File
@@ -0,0 +1,299 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or 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 <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_errors.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/jss.h>
#include <functional>
namespace ripple {
namespace LedgerEntryHelpers {
Unexpected<Json::Value>
missingFieldError(
Json::StaticString const field,
std::optional<std::string> err = std::nullopt)
{
Json::Value json = Json::objectValue;
auto error = RPC::missing_field_message(std::string(field.c_str()));
json[jss::error] = err.value_or("malformedRequest");
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
invalidFieldError(
std::string const& err,
Json::StaticString const field,
std::string const& type)
{
Json::Value json = Json::objectValue;
auto error = RPC::expected_field_message(field, type);
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
malformedError(std::string const& err, std::string const& message)
{
Json::Value json = Json::objectValue;
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = message;
return Unexpected(json);
}
Expected<bool, Json::Value>
hasRequired(
Json::Value const& params,
std::initializer_list<Json::StaticString> fields,
std::optional<std::string> err = std::nullopt)
{
for (auto const field : fields)
{
if (!params.isMember(field) || params[field].isNull())
{
return missingFieldError(field, err);
}
}
return true;
}
template <class T>
std::optional<T>
parse(Json::Value const& param);
template <class T>
Expected<T, Json::Value>
required(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err,
std::string const& expectedType)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto obj = parse<T>(params[fieldName]))
{
return *obj;
}
return invalidFieldError(err, fieldName, expectedType);
}
template <>
std::optional<AccountID>
parse(Json::Value const& param)
{
if (!param.isString())
return std::nullopt;
auto const account = parseBase58<AccountID>(param.asString());
if (!account || account->isZero())
{
return std::nullopt;
}
return account;
}
Expected<AccountID, Json::Value>
requiredAccountID(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<AccountID>(params, fieldName, err, "AccountID");
}
std::optional<Blob>
parseHexBlob(Json::Value const& param, std::size_t maxLength)
{
if (!param.isString())
return std::nullopt;
auto const blob = strUnHex(param.asString());
if (!blob || blob->empty() || blob->size() > maxLength)
return std::nullopt;
return blob;
}
Expected<Blob, Json::Value>
requiredHexBlob(
Json::Value const& params,
Json::StaticString const fieldName,
std::size_t maxLength,
std::string const& err)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto blob = parseHexBlob(params[fieldName], maxLength))
{
return *blob;
}
return invalidFieldError(err, fieldName, "hex string");
}
template <>
std::optional<std::uint32_t>
parse(Json::Value const& param)
{
if (param.isUInt() || (param.isInt() && param.asInt() >= 0))
return param.asUInt();
if (param.isString())
{
std::uint32_t v;
if (beast::lexicalCastChecked(v, param.asString()))
return v;
}
return std::nullopt;
}
Expected<std::uint32_t, Json::Value>
requiredUInt32(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<std::uint32_t>(params, fieldName, err, "number");
}
template <>
std::optional<uint256>
parse(Json::Value const& param)
{
uint256 uNodeIndex;
if (!param.isString() || !uNodeIndex.parseHex(param.asString()))
{
return std::nullopt;
}
return uNodeIndex;
}
Expected<uint256, Json::Value>
requiredUInt256(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint256>(params, fieldName, err, "hex string");
}
template <>
std::optional<uint192>
parse(Json::Value const& param)
{
uint192 field;
if (!param.isString() || !field.parseHex(param.asString()))
{
return std::nullopt;
}
return field;
}
Expected<uint192, Json::Value>
requiredUInt192(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint192>(params, fieldName, err, "Hash192");
}
Expected<STXChainBridge, Json::Value>
parseBridgeFields(Json::Value const& params)
{
if (auto const value = hasRequired(
params,
{jss::LockingChainDoor,
jss::LockingChainIssue,
jss::IssuingChainDoor,
jss::IssuingChainIssue});
!value)
{
return Unexpected(value.error());
}
auto const lockingChainDoor = requiredAccountID(
params, jss::LockingChainDoor, "malformedLockingChainDoor");
if (!lockingChainDoor)
{
return Unexpected(lockingChainDoor.error());
}
auto const issuingChainDoor = requiredAccountID(
params, jss::IssuingChainDoor, "malformedIssuingChainDoor");
if (!issuingChainDoor)
{
return Unexpected(issuingChainDoor.error());
}
Issue lockingChainIssue;
try
{
lockingChainIssue = issueFromJson(params[jss::LockingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::LockingChainIssue, "Issue");
}
Issue issuingChainIssue;
try
{
issuingChainIssue = issueFromJson(params[jss::IssuingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::IssuingChainIssue, "Issue");
}
return STXChainBridge(
*lockingChainDoor,
lockingChainIssue,
*issuingChainDoor,
issuingChainIssue);
}
} // namespace LedgerEntryHelpers
} // namespace ripple
+1 -1
View File
@@ -436,7 +436,7 @@ private:
static_cast<uint32_t>(entry.first);
}
ret[jss::native_currency_code] = systemCurrencyCode();
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
// Hook API: enough to regenerate hook/extern.h, hookapi.h keylet
// constants and error.h without the xahaud source tree.
+1 -1
View File
@@ -48,7 +48,7 @@ doServerInfo(RPC::JsonContext& context)
context.params.isMember(jss::counters) &&
context.params[jss::counters].asBool());
ret[jss::native_currency_code] = systemCurrencyCode();
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
return ret;
}
+1 -1
View File
@@ -38,7 +38,7 @@ doServerState(RPC::JsonContext& context)
context.params.isMember(jss::counters) &&
context.params[jss::counters].asBool());
ret[jss::native_currency_code] = systemCurrencyCode();
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
return ret;
}
+1 -1
View File
@@ -33,7 +33,7 @@ Json::Value
doStop(RPC::JsonContext& context)
{
context.app.signalStop();
return RPC::makeObjectValue(systemName() + " server stopping");
return RPC::makeObjectValue(std::string(systemName) + " server stopping");
}
} // namespace ripple
+32 -23
View File
@@ -114,15 +114,15 @@ namespace detail {
// The slab cutoffs and the number of megabytes per allocation are customized
// based on the number of objects of each size we expect to need at any point
// in time and with an eye to minimize the number of slack bytes in a block.
inline SlabAllocatorSet<SHAMapItem> slabber({
{ 128, megabytes(std::size_t(60)) },
{ 192, megabytes(std::size_t(46)) },
{ 272, megabytes(std::size_t(60)) },
{ 384, megabytes(std::size_t(56)) },
{ 564, megabytes(std::size_t(40)) },
{ 772, megabytes(std::size_t(46)) },
{ 1052, megabytes(std::size_t(60)) },
});
inline constinit slab::allocator_t<SHAMapItem,
slab::config<1000000, 128>,
slab::config<1000000, 296>,
slab::config<125000, 392>,
slab::config<125000, 520>,
slab::config<62500, 760>,
slab::config<62500, 856>,
slab::config<31250, 1048>
> slabber;
// clang-format on
} // namespace detail
@@ -130,7 +130,11 @@ inline SlabAllocatorSet<SHAMapItem> slabber({
inline void
intrusive_ptr_add_ref(SHAMapItem const* x)
{
// This can only happen if someone releases the last reference to the
assert(x);
// In order to call this, we must already have an intrusive pointer
// to this item, so it's reference count should be at least 1. This
// would only happen if someone released that last reference to the
// item while we were trying to increment the refcount.
if (x->refcount_++ == 0)
LogicError("SHAMapItem: the reference count is 0!");
@@ -139,20 +143,23 @@ intrusive_ptr_add_ref(SHAMapItem const* x)
inline void
intrusive_ptr_release(SHAMapItem const* x)
{
assert(x);
if (--x->refcount_ == 0)
{
auto p = reinterpret_cast<std::uint8_t const*>(x);
// The SHAMapItem constuctor isn't trivial (because the destructor
// for CountedObject isn't) so we can't avoid calling it here, but
// plan for a future where we might not need to.
if constexpr (!std::is_trivially_destructible_v<SHAMapItem>)
std::destroy_at(x);
// We need to invoke the destructor for this object before we
// release the memory.
std::destroy_at(x);
// If the slabber doens't claim this pointer, it was allocated
// manually, so we free it manually.
// If the slabber doesn't claim this pointer, it was allocated
// manually (with the aligned operator new[] in make_shamapitem),
// so we free it manually with the matching aligned delete.
if (!detail::slabber.deallocate(const_cast<std::uint8_t*>(p)))
delete[] p;
::operator delete[](
const_cast<std::uint8_t*>(p),
std::align_val_t(alignof(SHAMapItem)));
}
}
@@ -168,18 +175,20 @@ make_shamapitem(uint256 const& tag, Slice data)
// If we can't grab memory from the slab allocators, we fall back to
// the standard library and try to grab a precisely-sized memory block:
if (raw == nullptr)
raw = new std::uint8_t[sizeof(SHAMapItem) + data.size()];
raw = new (std::align_val_t(alignof(SHAMapItem)))
std::uint8_t[sizeof(SHAMapItem) + data.size()];
assert(reinterpret_cast<std::uintptr_t>(raw) % alignof(SHAMapItem) == 0);
// We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
// that the refcount can never be zero before incrementing as an
// invariant.
return {new (raw) SHAMapItem{tag, data}, false};
return {
new (std::assume_aligned<alignof(SHAMapItem)>(raw))
SHAMapItem{tag, data},
false};
}
static_assert(alignof(SHAMapItem) != 40);
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4);
inline boost::intrusive_ptr<SHAMapItem>
make_shamapitem(SHAMapItem const& other)
{