Compare commits

...
Author SHA1 Message Date
tequ 37b2d2995c Merge branch 'dev' into refactor/transactions-macro-includes 2026-10-24 09:48:29 +09:00
tequ 86ac135c63 refactor: include transactor headers from transactions.macro
Partial cherry-pick of XRPLF/rippled#5590. Each TRANSACTION entry now
carries its transactor header behind `#if TRANSACTION_INCLUDE`, and
applySteps.cpp pulls them in by expanding the macro with
TRANSACTION_INCLUDE defined. Adding a transaction type no longer
requires touching the include list in applySteps.cpp.

ApplyContext.h (reached via Transactor.h) and XahauGenesis.h (unused
there) are dropped from applySteps.cpp.
2026-10-22 15:44:30 +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
74 changed files with 3053 additions and 4180 deletions

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ Loop: xrpld.core xrpld.net
xrpld.net > xrpld.core xrpld.net > xrpld.core
Loop: xrpld.core xrpld.perflog Loop: xrpld.core xrpld.perflog
xrpld.perflog == xrpld.core xrpld.perflog ~= xrpld.core
Loop: xrpld.net xrpld.rpc Loop: xrpld.net xrpld.rpc
xrpld.rpc > xrpld.net xrpld.rpc > xrpld.net
-2
View File
@@ -52,8 +52,6 @@ test.core > test.toplevel
test.core > test.unit_test test.core > test.unit_test
test.core > xrpl.basics test.core > xrpl.basics
test.core > xrpld.core test.core > xrpld.core
test.core > xrpld.perflog
test.core > xrpl.json
test.core > xrpl.server test.core > xrpl.server
test.csf > xrpl.basics test.csf > xrpl.basics
test.csf > xrpld.consensus test.csf > xrpld.consensus
+1 -1
View File
@@ -88,7 +88,7 @@ git remote -v >> release.info &&
echo "Git status:" >> release.info && echo "Git status:" >> release.info &&
git status -v >> release.info && git status -v >> release.info &&
echo "Git log [last 20]:" >> 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 if [[ "$4" == "" ]]; then
# Non GH, local building # 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 #ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED #define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#include <xrpl/beast/type_name.h> #include <xrpl/beast/type_name.h>
#include <atomic> #include <atomic>
#include <cstddef>
#include <iterator>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
namespace ripple { namespace ripple {
/** Manages all counted object types. */
class CountedObjects class CountedObjects
{ {
public: 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 class Counter
{ {
public: public:
Counter(std::string name) noexcept : name_(std::move(name)), count_(0) Counter(std::string name) noexcept;
{
// 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;
int int
increment() noexcept increment() noexcept
{ {
return ++count_; auto const newCount = ++count_;
if (auto maxCount = maxCount_.load(); newCount > maxCount)
maxCount_.compare_exchange_strong(maxCount, newCount);
return newCount;
} }
int int
@@ -79,78 +37,161 @@ public:
} }
int int
getCount() const noexcept count() const noexcept
{ {
return count_.load(); return count_.load();
} }
Counter* int
getNext() const noexcept max() const noexcept
{ {
return next_; return std::max(count_.load(), maxCount_.load());
} }
std::string const& std::string const&
getName() const noexcept name() const noexcept
{ {
return name_; return name_;
} }
private: private:
std::string const name_; friend class CountedObjects;
std::atomic<int> count_;
Counter* next_; Counter* next_;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
}; };
private: class Iterator
CountedObjects() noexcept; {
~CountedObjects() noexcept = default; 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: private:
std::atomic<int> m_count; friend class Counter;
std::atomic<Counter*> m_head;
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> template <class Object>
class CountedObject class CountedObject
{ {
private: static CountedObjects::Counter counter_;
static auto&
getCounter() noexcept
{
static CountedObjects::Counter c{beast::type_name<Object>()};
return c;
}
public: public:
CountedObject() noexcept CountedObject() noexcept
{ {
getCounter().increment(); counter_.increment();
} }
CountedObject(CountedObject const&) noexcept CountedObject(CountedObject const&) noexcept
{ {
getCounter().increment(); counter_.increment();
} }
CountedObject& CountedObject&
operator=(CountedObject const&) noexcept = default; operator=(CountedObject const&) noexcept = default;
~CountedObject() noexcept ~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 } // namespace ripple
#endif #endif
+1 -1
View File
@@ -113,7 +113,7 @@ template <>
inline std::size_t inline std::size_t
extract(SHAMapHash const& key) extract(SHAMapHash const& key)
{ {
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data()); return extract(key.as_uint256());
} }
} // namespace ripple } // namespace ripple
+528 -305
View File
@@ -21,195 +21,443 @@
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED #define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
#include <xrpl/basics/ByteUtilities.h> #include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/type_name.h> #include <xrpl/basics/spinlock.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <boost/align.hpp> #include <boost/align.hpp>
#include <boost/container/static_vector.hpp> #include <boost/predef/os.h>
#include <boost/predef.h>
#include <algorithm> #include <array>
#include <atomic> #include <atomic>
#include <cstdint> #include <bit>
#include <cstring>
#include <mutex>
#include <vector>
#if BOOST_OS_LINUX #include <limits>
#include <new>
#ifdef BOOST_OS_LINUX
#include <sys/mman.h> #include <sys/mman.h>
#endif #endif
namespace ripple { namespace ripple {
namespace slab {
namespace detail {
template <typename Type> /// Alignment for hugepage support.
class SlabAllocator 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( auto ptr = reinterpret_cast<std::uint8_t*>(
sizeof(Type) >= sizeof(std::uint8_t*), boost::alignment::aligned_alloc(pageSize, size));
"SlabAllocator: the requested object must be larger than a pointer.");
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 */ return ptr;
struct SlabBlock }
/** 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: item_t* const next;
std::mutex m_;
// A linked list of appropriately sized free buffers: constexpr explicit item_t(item_t* n) noexcept : next(n)
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)
{ {
// 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: // We need this to be trivially destructible so that we do not
// A linked list of slabs // have to explicitly invoke the destructor when popping items
std::atomic<SlabBlock*> slabs_ = nullptr; // 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: item_t* head_ = nullptr;
std::size_t const itemAlignment_; 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 /** Construct a block that is unlinked. */
// any padding needed for alignment purposes: constexpr slab_t() noexcept = default;
std::size_t const itemSize_;
// The size of each individual slab: /** Construct a block with a link to the next block in the chain.
std::size_t const slabSize_;
@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: public:
/** Constructs a slab allocator able to allocate objects of a fixed size /** Constructs a slab allocator able to allocate objects of a fixed size
@param count the number of items the slab allocator can allocate; note @param extra Extra bytes per item beyond sizeof(Type).
that a count of 0 is valid and means that the allocator @param minItems Minimum number of items per slab (rounded up to 2MB).
is, effectively, disabled. This can be very useful in some
contexts (e.g. when mimimal memory usage is needed) and
allows for graceful failure.
*/ */
constexpr explicit SlabAllocator( constexpr explicit sized_allocator_t(
std::size_t extra, std::size_t extra,
std::size_t alloc = 0, std::size_t minItems)
std::size_t align = 0) : itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
: itemAlignment_(align ? align : alignof(Type)) , slabSize_(boost::alignment::align_up(
, itemSize_( itemSize_ * minItems,
boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_)) detail::pageSize))
, slabSize_(alloc)
{ {
XRPL_ASSERT(
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
"ripple::SlabAllocator::SlabAllocator : valid alignment");
} }
SlabAllocator(SlabAllocator const& other) = delete; // Data buffers are intentionally not released. C++ destruction order
SlabAllocator& // does not guarantee that all items allocated from this allocator
operator=(SlabAllocator const& other) = delete; // have been freed by the time this destructor runs.
~sized_allocator_t() = default;
SlabAllocator(SlabAllocator&& other) = delete; sized_allocator_t(sized_allocator_t const&) = delete;
SlabAllocator& sized_allocator_t&
operator=(SlabAllocator&& other) = delete; operator=(sized_allocator_t const&) = delete;
sized_allocator_t(sized_allocator_t&&) = delete;
sized_allocator_t&
operator=(sized_allocator_t&&) = delete;
~SlabAllocator() /** Returns the size of memory blocks returned by this allocator.
{
// 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 the memory block this allocator returns. */ @return Size of each allocated block in bytes.
constexpr std::size_t */
[[nodiscard]] constexpr std::size_t
size() const noexcept size() const noexcept
{ {
return itemSize_; return itemSize_;
@@ -217,87 +465,36 @@ public:
/** Returns a suitably aligned pointer, if one is available. /** Returns a suitably aligned pointer, if one is available.
@return a pointer to a block of memory from the allocator, or @return Pointer to allocated memory, or nullptr on failure.
nullptr if the allocator can't satisfy this request.
@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 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()) // Slow path: need new capacity. Grab the lock to serialize access
return ret; // to this code path because it is expensive. Then re-check in case
// another thread added capacity while we waited.
slab = slab->next_; spin_lock(blockLock_);
ret = slow_allocate();
spin_unlock(blockLock_);
} }
// No slab can satisfy our request, so we attempt to allocate a new return ret;
// 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();
} }
/** Returns the memory block to the allocator. /** Returns the memory block to the allocator.
@param ptr A pointer to a memory block. @param ptr Pointer to memory block.
@param size If non-zero, a hint as to the size of the block. @return true if the block belonged to this allocator and was freed.
@return true if this memory block belonged to the allocator and has
been released; false otherwise.
*/ */
bool [[nodiscard]] bool
deallocate(std::uint8_t* ptr) noexcept deallocate(std::uint8_t* ptr) noexcept
{ {
XRPL_ASSERT( XRPL_ASSERT(
@@ -305,112 +502,135 @@ public:
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null " "ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
"input"); "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)) if (slab->try_deallocate(ptr, slabSize_, release))
{
slab->deallocate(ptr);
return true; return true;
}
} }
return false; 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: static constexpr std::size_t minItems = MinItems;
// The list of allocators that belong to this set static constexpr std::size_t extra = Extra;
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_; };
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: /** Validate slab configurations at compile time.
class SlabConfig
{
friend class SlabAllocatorSet;
private: Checks that configurations produce strictly increasing sizes after
std::size_t extra; alignment. This catches both unsorted configs and configs that
std::size_t alloc; collapse to the same size after alignment.
std::size_t align;
public: @tparam Type The type used to determine base size.
constexpr SlabConfig( @tparam Align Alignment for allocated blocks.
std::size_t extra_, @tparam Configs Configuration types to validate.
std::size_t alloc_ = 0, @return true if configurations are valid, false otherwise.
std::size_t align_ = alignof(Type)) */
: extra(extra_), alloc(alloc_), align(align_) 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) constexpr std::array<std::size_t, sizeof...(Configs)> sizes{
{ align_up(sizeof(Type) + Configs::extra, Align)...};
// 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;
});
// We should never have two slabs of the same size for (std::size_t i = 1; i < sizes.size(); ++i)
if (std::adjacent_find( if (sizes[i - 1] >= sizes[i])
std::begin(cfg), return false;
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 (auto const& c : cfg) return true;
{ }
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
if (a.size() > maxSize_) //------------------------------------------------------------------------------
maxSize_ = a.size();
}
}
SlabAllocatorSet(SlabAllocatorSet const& other) = delete; /** A collection of slab allocators for different sizes.
SlabAllocatorSet&
operator=(SlabAllocatorSet const& other) = delete;
SlabAllocatorSet(SlabAllocatorSet&& other) = delete; Manages multiple sized_allocator_t instances, each configured for a
SlabAllocatorSet& different block size. Allocations are routed to the smallest allocator
operator=(SlabAllocatorSet&& other) = delete; 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 aligned_allocator_t(aligned_allocator_t const&) = delete;
the object, that should be returned by the allocator. 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 /** Allocate memory for an object with extra bytes.
can't satisfy this request.
@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 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; return nullptr;
@@ -418,12 +638,10 @@ public:
/** Returns the memory block to the allocator. /** Returns the memory block to the allocator.
@param ptr A pointer to a memory block. @param ptr Pointer to memory block.
@return true if memory belonged to this set and was freed.
@return true if this memory block belonged to one of the allocators
in this set and has been released; false otherwise.
*/ */
bool [[nodiscard]] bool
deallocate(std::uint8_t* ptr) noexcept deallocate(std::uint8_t* ptr) noexcept
{ {
for (auto& a : allocators_) for (auto& a : allocators_)
@@ -431,11 +649,16 @@ public:
if (a.deallocate(ptr)) if (a.deallocate(ptr))
return true; return true;
} }
return false; 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 } // namespace ripple
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED #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 <xrpl/beast/type_name.h>
#include <exception> #include <exception>
#include <string> #include <string>
#include <type_traits>
#include <typeinfo> #include <typeinfo>
#include <utility> #include <utility>
@@ -52,7 +53,7 @@ Rethrow()
} }
template <class E, class... Args> template <class E, class... Args>
[[noreturn]] inline void [[noreturn]] constexpr void
Throw(Args&&... args) Throw(Args&&... args)
{ {
static_assert( static_assert(
@@ -60,10 +61,15 @@ Throw(Args&&... args)
"Exception must derive from std::exception."); "Exception must derive from std::exception.");
E e(std::forward<Args>(args)...); E e(std::forward<Args>(args)...);
LogThrow(
std::string( // Skip logging when evaluated at compile time, so that `Throw` can be
"Throwing exception of type " + beast::type_name<E>() + ": ") + // called from constexpr/consteval functions.
e.what()); if (!std::is_constant_evaluated())
LogThrow(
std::string(
"Throwing exception of type " + beast::type_name<E>() + ": ") +
e.what());
throw e; throw e;
} }
+139 -96
View File
@@ -20,6 +20,7 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <atomic> #include <atomic>
#include <concepts>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
@@ -30,6 +31,7 @@
namespace ripple { namespace ripple {
namespace detail { namespace detail {
/** Inform the processor that we are in a tight spin-wait loop. /** Inform the processor that we are in a tight spin-wait loop.
Spinlocks caught in tight loops can result in the processor's pipeline Spinlocks caught in tight loops can result in the processor's pipeline
@@ -52,11 +54,130 @@ spin_pause() noexcept
} // namespace detail } // namespace detail
/** @{ */ /** Attempt to acquire a spinlock without blocking.
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
Packed spinlocks allow for tremendously space-efficient lock-sharding @tparam T An unsigned integral type.
but they come at a cost. @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 First, the implementation is necessarily low-level and uses advanced
features like memory ordering and highly platform-specific tricks to features like memory ordering and highly platform-specific tricks to
@@ -78,26 +199,19 @@ spin_pause() noexcept
that it can, usually, outperform spinlocks. that it can, usually, outperform spinlocks.
@tparam T An unsigned integral type (e.g. std::uint16_t) @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: This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/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 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_; std::atomic<T>& bits_;
T const mask_; T const mask_;
@@ -114,7 +228,7 @@ public:
@note For performance reasons, you should strive to have `lock` be @note For performance reasons, you should strive to have `lock` be
on a cacheline by itself. 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) : bits_(lock), mask_(static_cast<T>(1) << index)
{ {
XRPL_ASSERT( XRPL_ASSERT(
@@ -123,13 +237,13 @@ public:
} }
[[nodiscard]] bool [[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 void
lock() lock() noexcept
{ {
while (!try_lock()) while (!try_lock())
{ {
@@ -137,89 +251,18 @@ public:
// serves to help reduce cache coherency traffic during times // serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not // of contention by avoiding writes that would definitely not
// result in the lock being acquired. // 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(); detail::spin_pause();
} }
} }
void 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 } // namespace ripple
#endif #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 #ifndef HOOKMISC_INCLUDED
#define HOOKMISC_INCLUDED 1 #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 = { #include <xrpl/basics/base_uint.h>
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000001"), #include <array>
ripple::uint256( #include <cstdint>
"0000000000000000000000000000000000000000000000000000000000000002"),
ripple::uint256( namespace ripple {
"0000000000000000000000000000000000000000000000000000000000000004"),
ripple::uint256( /** UINT256_BIT[i] is the 256-bit value with only bit i set (LSB is bit 0).
"0000000000000000000000000000000000000000000000000000000000000008"),
ripple::uint256( Used to encode and test the per-transaction-type bits of the HookOn and
"0000000000000000000000000000000000000000000000000000000000000010"), HookCanEmit fields. Built at compile time.
ripple::uint256( */
"0000000000000000000000000000000000000000000000000000000000000020"), inline constexpr auto UINT256_BIT = []() consteval {
ripple::uint256( std::array<ripple::uint256, 256> table{};
"0000000000000000000000000000000000000000000000000000000000000040"),
ripple::uint256( for (int i = 0; i < 256; ++i)
"0000000000000000000000000000000000000000000000000000000000000080"), {
ripple::uint256( std::array<std::uint8_t, 32> bytes{};
"0000000000000000000000000000000000000000000000000000000000000100"), bytes[31 - (i / 8)] = 1 << (i % 8);
ripple::uint256( table[i] = ripple::uint256(bytes);
"0000000000000000000000000000000000000000000000000000000000000200"), }
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000400"), return table;
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")};
} // namespace ripple } // namespace ripple
#endif #endif
+42 -83
View File
@@ -24,6 +24,7 @@
#include <xrpl/protocol/KeyType.h> #include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/STExchange.h> #include <xrpl/protocol/STExchange.h>
#include <xrpl/protocol/UintTypes.h> #include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/json_get_or_throw.h> #include <xrpl/protocol/json_get_or_throw.h>
#include <xrpl/protocol/tokens.h> #include <xrpl/protocol/tokens.h>
@@ -36,6 +37,26 @@
namespace ripple { 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. /** A public key.
Public keys are used in the public-key cryptography Public keys are used in the public-key cryptography
@@ -58,77 +79,47 @@ namespace ripple {
prefix constant 0xED, followed by 32 bytes of prefix constant 0xED, followed by 32 bytes of
public key data. 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: public:
PublicKey() = delete; PublicKey() = delete;
PublicKey(PublicKey const& other); PublicKey(PublicKey const&) = default;
PublicKey& PublicKey&
operator=(PublicKey const& other); operator=(PublicKey const&) = default;
/** Create a public key. explicit PublicKey(Slice const& slice) noexcept
Preconditions:
publicKeyType(slice) != std::nullopt
*/
explicit PublicKey(Slice const& slice);
std::uint8_t const*
data() const 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 explicit PublicKey(value_t const& data) noexcept
size() const noexcept : PublicKey(makeSlice(data))
{ {
return size_;
} }
const_iterator [[nodiscard]] Slice
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
slice() const noexcept slice() const noexcept
{ {
return {buf_, size_}; return {buf_.data(), buf_.size()};
} }
operator Slice() const noexcept operator Slice() const noexcept
{ {
return slice(); return slice();
} }
[[nodiscard]] auto
operator<=>(PublicKey const& rhs) const
{
return buf_ <=> rhs.buf_;
}
}; };
/** Print the public key to a stream. /** Print the public key to a stream.
@@ -136,22 +127,6 @@ public:
std::ostream& std::ostream&
operator<<(std::ostream& os, PublicKey const& pk); 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> template <class Hasher>
void void
hash_append(Hasher& h, PublicKey const& pk) hash_append(Hasher& h, PublicKey const& pk)
@@ -221,22 +196,6 @@ enum class ECDSACanonicality { canonical, fullyCanonical };
std::optional<ECDSACanonicality> std::optional<ECDSACanonicality>
ecdsaCanonicality(Slice const& sig); 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. */ /** Verify a secp256k1 signature on the digest of a message. */
[[nodiscard]] bool [[nodiscard]] bool
verifyDigest( verifyDigest(
+10 -61
View File
@@ -25,90 +25,39 @@
#include <xrpl/protocol/KeyType.h> #include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h> #include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Seed.h> #include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h> #include <xrpl/protocol/tokens.h>
#include <array> #include <array>
#include <cstring>
#include <string> #include <string>
namespace ripple { namespace ripple {
/** A secret key. */ /** A secret key. */
class SecretKey class SecretKey : public detail::KeyBase<SecretKey, 32>
{ {
private:
std::uint8_t buf_[32];
public: public:
using const_iterator = std::uint8_t const*;
SecretKey() = delete; SecretKey() = delete;
SecretKey(SecretKey const&) = default; SecretKey(SecretKey const&) = default;
SecretKey& SecretKey&
operator=(SecretKey const&) = default; operator=(SecretKey const&) = default;
/** Destroy the secret key. This will try to securely erase the buffer. */
~SecretKey(); ~SecretKey();
SecretKey(std::array<std::uint8_t, 32> const& data); SecretKey(Slice const& slice)
SecretKey(Slice const& slice);
std::uint8_t const*
data() const
{ {
return buf_; if (slice.size() != buf_.size())
LogicError("SecretKey::SecretKey: invalid size");
std::copy_n(slice.data(), buf_.size(), buf_.data());
} }
std::size_t explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
size() const
{ {
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 */ /** Parse a secret key */
+11 -46
View File
@@ -22,72 +22,37 @@
#include <xrpl/basics/Slice.h> #include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h> #include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array> #include <array>
#include <optional> #include <optional>
namespace ripple { namespace ripple {
/** Seeds are used to generate deterministic secret keys. */ /** 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: public:
using const_iterator = std::array<uint8_t, 16>::const_iterator;
Seed() = delete; Seed() = delete;
Seed(Seed const&) = default; Seed(Seed const&) = default;
Seed& Seed&
operator=(Seed const&) = default; operator=(Seed const&) = default;
/** Destroy the seed. /** Destroy the seed. This will attempt to securely erase the buffer. */
The buffer will first be securely erased.
*/
~Seed(); ~Seed();
/** Construct a seed */ explicit Seed(Slice const& slice)
/** @{ */
explicit Seed(Slice const& slice);
explicit Seed(uint128 const& seed);
/** @} */
std::uint8_t const*
data() const
{ {
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 explicit Seed(span_t seed) noexcept : KeyBase(seed)
size() const
{ {
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 <xrpl/protocol/XRPAmount.h>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <string_view>
namespace ripple { namespace ripple {
// Various protocol and system specific constant globals. // Various protocol and system specific constant globals.
/* The name of the system. */ /* The name of the system. */
static inline std::string const& inline constexpr std::string_view systemName = "xahau";
systemName()
{
static std::string const name = "xahau";
return name;
}
/** Configure the native currency. */ /** Configure the native currency. */
@@ -58,12 +54,7 @@ isLegalAmountSigned(XRPAmount const& amount)
} }
/* The currency code for the native currency. */ /* The currency code for the native currency. */
static inline std::string const& inline constexpr std::string_view systemCurrencyCode = "XAH";
systemCurrencyCode()
{
static std::string const code = "XAH";
return code;
}
/** The XRP ledger network's earliest allowed sequence */ /** The XRP ledger network's earliest allowed sequence */
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U}; 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/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h> #include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SystemParameters.h>
namespace ripple { namespace ripple {
namespace detail {
class CurrencyTag using LedgerHash = uint256;
{
public:
explicit CurrencyTag() = default;
};
class DirectoryTag
{
public:
explicit DirectoryTag() = default;
};
class NodeIDTag
{
public:
explicit NodeIDTag() = default;
};
} // namespace detail
/** Directory is an index into the directory of offer books. /** Directory is an index into the directory of offer books.
The last 64 bits of this are the quality. */ 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. */ /** 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. */ /** 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, /** A Multi-Purpose Token Issuance ID,
* which is a concatenation of a 32-bit sequence (big endian)
* and a 160-bit account */ 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>; 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. */ /** XRP currency. */
Currency const& [[nodiscard]] constexpr Currency const&
xrpCurrency(); xrpCurrency() noexcept
{
return detail::xrpCurrency;
}
[[nodiscard]] constexpr bool
isXRP(Currency const& c) noexcept
{
return c == detail::xrpCurrency;
}
/** A placeholder for empty currencies. */ /** A placeholder for empty currencies. */
Currency const& [[nodiscard]] constexpr Currency const&
noCurrency(); noCurrency() noexcept
{
return detail::noCurrency;
}
/** We deliberately disallow the currency that looks like "XAH" because too /** We deliberately disallow the currency that looks like "XAH" because too
many people were using it instead of the correct XAH currency. */ many people were using it instead of the correct XAH currency. */
Currency const& [[nodiscard]] constexpr Currency const&
badCurrency(); badCurrency() noexcept
inline bool
isXRP(Currency const& c)
{ {
return c == beast::zero; return detail::badCurrency;
} }
inline bool [[nodiscard]] constexpr bool
isBadCurrency(Currency const& c) isBadCurrency(Currency const& c) noexcept
{ {
static const std::set<Currency> badCurrencies{ // We take advantage of the fact that an ASCII value in the [a-z] range
Currency(0x7861680000000000), // xah // transforms into the equivalent character in the [A-Z] range when you
Currency(0x7861480000000000), // xaH // AND it with 0xDF, so we can compare against all possible variants of
Currency(0x7841680000000000), // xAh // the bad currency code in one go:
Currency(0x7841480000000000), // xAH return (c & detail::badCurrencyCodeMask) == badCurrency();
Currency(0x5861680000000000), // Xah
Currency(0x5861480000000000), // XaH
Currency(0x5841680000000000), // XAh
Currency(0x5841480000000000) // XAH
};
return badCurrencies.find(c) != badCurrencies.end();
} }
/** Returns "", "XAH", or three letter ISO code. */ /** Returns "", "XAH", three letter ISO code or the hex representation. */
std::string [[nodiscard]] inline std::string
to_string(Currency const& c); 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. /** 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 will require very careful checking everywhere and may mean having
to rewrite some unit test code. to rewrite some unit test code.
*/ */
bool [[nodiscard]] constexpr bool
to_currency(Currency&, std::string const&); 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. /** 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 unfortunate; changing this will require very careful checking
everywhere and may mean having to rewrite some unit test code. everywhere and may mean having to rewrite some unit test code.
*/ */
Currency [[nodiscard]] constexpr Currency
to_currency(std::string const&); to_currency(std::string_view code) noexcept
{
if (Currency currency; to_currency(currency, code))
return currency;
return noCurrency();
}
inline std::ostream& inline std::ostream&
operator<<(std::ostream& os, Currency const& x) 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
+167 -1
View File
@@ -24,11 +24,21 @@
/** /**
* TRANSACTION(tag, value, name, fields) * TRANSACTION(tag, value, name, fields)
* *
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
*
* You must define a transactor class in the `ripple` namespace named `name`, * You must define a transactor class in the `ripple` namespace named `name`,
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`. * and include its header alongside the TRANSACTOR definition using this
* format:
* #if TRANSACTION_INCLUDE
* # include <xrpld/app/tx/detail/HEADER.h>
* #endif
*/ */
/** This transaction type executes a payment. */ /** This transaction type executes a payment. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Payment.h>
#endif
TRANSACTION(ttPAYMENT, 0, Payment, ({ TRANSACTION(ttPAYMENT, 0, Payment, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
@@ -41,6 +51,9 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
})) }))
/** This transaction type creates an escrow object. */ /** This transaction type creates an escrow object. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
#endif
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -62,6 +75,9 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
/** This transaction type adjusts various account settings. */ /** This transaction type adjusts various account settings. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetAccount.h>
#endif
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
{sfEmailHash, soeOPTIONAL}, {sfEmailHash, soeOPTIONAL},
{sfWalletLocator, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL},
@@ -85,6 +101,9 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
})) }))
/** This transaction type sets or clears an account's "regular key". */ /** This transaction type sets or clears an account's "regular key". */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRegularKey.h>
#endif
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
{sfRegularKey, soeOPTIONAL}, {sfRegularKey, soeOPTIONAL},
})) }))
@@ -92,6 +111,9 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
// 6 deprecated // 6 deprecated
/** This transaction type creates an offer to trade one asset for another. */ /** This transaction type creates an offer to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateOffer.h>
#endif
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
{sfTakerPays, soeREQUIRED}, {sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED}, {sfTakerGets, soeREQUIRED},
@@ -101,6 +123,9 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
})) }))
/** This transaction type cancels existing offers to trade one asset for another. */ /** This transaction type cancels existing offers to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelOffer.h>
#endif
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
{sfOfferSequence, soeOPTIONAL}, {sfOfferSequence, soeOPTIONAL},
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq {sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
@@ -109,6 +134,9 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
// 9 deprecated // 9 deprecated
/** This transaction type creates a new set of tickets. */ /** This transaction type creates a new set of tickets. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateTicket.h>
#endif
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
{sfTicketCount, soeREQUIRED}, {sfTicketCount, soeREQUIRED},
})) }))
@@ -118,12 +146,18 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
/** This transaction type modifies the signer list associated with an account. */ /** This transaction type modifies the signer list associated with an account. */
// The SignerEntries are optional because a SignerList is deleted by // The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries. // setting the SignerQuorum to zero and omitting SignerEntries.
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetSignerList.h>
#endif
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
{sfSignerQuorum, soeREQUIRED}, {sfSignerQuorum, soeREQUIRED},
{sfSignerEntries, soeOPTIONAL}, {sfSignerEntries, soeOPTIONAL},
})) }))
/** This transaction type creates a new unidirectional XRP payment channel. */ /** This transaction type creates a new unidirectional XRP payment channel. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PayChan.h>
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -151,6 +185,9 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
})) }))
/** This transaction type creates a new check. */ /** This transaction type creates a new check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateCheck.h>
#endif
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED}, {sfSendMax, soeREQUIRED},
@@ -160,6 +197,9 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
})) }))
/** This transaction type cashes an existing check. */ /** This transaction type cashes an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CashCheck.h>
#endif
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({ TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
{sfCheckID, soeREQUIRED}, {sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL}, {sfAmount, soeOPTIONAL},
@@ -167,11 +207,17 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
})) }))
/** This transaction type cancels an existing check. */ /** This transaction type cancels an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelCheck.h>
#endif
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
{sfCheckID, soeREQUIRED}, {sfCheckID, soeREQUIRED},
})) }))
/** This transaction type grants or revokes authorization to transfer funds. */ /** This transaction type grants or revokes authorization to transfer funds. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DepositPreauth.h>
#endif
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
{sfAuthorize, soeOPTIONAL}, {sfAuthorize, soeOPTIONAL},
{sfUnauthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL},
@@ -180,6 +226,9 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
})) }))
/** This transaction type modifies a trustline between two accounts. */ /** This transaction type modifies a trustline between two accounts. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetTrust.h>
#endif
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({ TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
{sfLimitAmount, soeOPTIONAL}, {sfLimitAmount, soeOPTIONAL},
{sfQualityIn, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL},
@@ -187,6 +236,9 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
})) }))
/** This transaction type deletes an existing account. */ /** This transaction type deletes an existing account. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteAccount.h>
#endif
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL},
@@ -194,11 +246,17 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
})) }))
/** This transaction type installs a hook. */ /** This transaction type installs a hook. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetHook.h>
#endif
TRANSACTION(ttHOOK_SET, 22, SetHook, ({ TRANSACTION(ttHOOK_SET, 22, SetHook, ({
{sfHooks, soeREQUIRED}, {sfHooks, soeREQUIRED},
})) }))
/** This transaction mints a new NFT. */ /** This transaction mints a new NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenMint.h>
#endif
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
{sfNFTokenTaxon, soeREQUIRED}, {sfNFTokenTaxon, soeREQUIRED},
{sfTransferFee, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL},
@@ -210,12 +268,18 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
})) }))
/** This transaction burns (i.e. destroys) an existing NFT. */ /** This transaction burns (i.e. destroys) an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenBurn.h>
#endif
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
})) }))
/** This transaction creates a new offer to buy or sell an NFT. */ /** This transaction creates a new offer to buy or sell an NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
@@ -225,11 +289,17 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
})) }))
/** This transaction cancels an existing offer to buy or sell an existing NFT. */ /** This transaction cancels an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
{sfNFTokenOffers, soeREQUIRED}, {sfNFTokenOffers, soeREQUIRED},
})) }))
/** This transaction accepts an existing offer to buy or sell an existing NFT. */ /** This transaction accepts an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
#endif
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
{sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenBuyOffer, soeOPTIONAL},
{sfNFTokenSellOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL},
@@ -237,12 +307,18 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
})) }))
/** This transaction claws back issued tokens. */ /** This transaction claws back issued tokens. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Clawback.h>
#endif
TRANSACTION(ttCLAWBACK, 30, Clawback, ({ TRANSACTION(ttCLAWBACK, 30, Clawback, ({
{sfAmount, soeREQUIRED, soeMPTSupported}, {sfAmount, soeREQUIRED, soeMPTSupported},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
})) }))
/** This transaction claws back tokens from an AMM pool. */ /** This transaction claws back tokens from an AMM pool. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMClawback.h>
#endif
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
{sfHolder, soeREQUIRED}, {sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
@@ -251,6 +327,9 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
})) }))
/** This transaction type creates an AMM instance */ /** This transaction type creates an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMCreate.h>
#endif
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
{sfAmount, soeREQUIRED}, {sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED}, {sfAmount2, soeREQUIRED},
@@ -258,6 +337,9 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
})) }))
/** This transaction type deposits into an AMM instance */ /** This transaction type deposits into an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDeposit.h>
#endif
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -269,6 +351,9 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
})) }))
/** This transaction type withdraws from an AMM instance */ /** This transaction type withdraws from an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMWithdraw.h>
#endif
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -279,6 +364,9 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
})) }))
/** This transaction type votes for the trading fee */ /** This transaction type votes for the trading fee */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMVote.h>
#endif
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -286,6 +374,9 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
})) }))
/** This transaction type bids for the auction slot */ /** This transaction type bids for the auction slot */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMBid.h>
#endif
TRANSACTION(ttAMM_BID, 39, AMMBid, ({ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
@@ -295,12 +386,18 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
})) }))
/** This transaction type deletes AMM in the empty state */ /** This transaction type deletes AMM in the empty state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDelete.h>
#endif
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
})) }))
/** This transaction mints/burns/buys/sells a URI TOKEN */ /** This transaction mints/burns/buys/sells a URI TOKEN */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/URIToken.h>
#endif
TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({ TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({
{sfURI, soeREQUIRED}, {sfURI, soeREQUIRED},
{sfDigest, soeOPTIONAL}, {sfDigest, soeOPTIONAL},
@@ -328,6 +425,9 @@ TRANSACTION(ttURITOKEN_CANCEL_SELL_OFFER, 49, URITokenCancelSellOffer, ({
})) }))
/** This transactions creates a crosschain sequence number */ /** This transactions creates a crosschain sequence number */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/XChainBridge.h>
#endif
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({
{sfXChainBridge, soeREQUIRED}, {sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED}, {sfSignatureReward, soeREQUIRED},
@@ -407,6 +507,9 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 57, XChainCreateBridge, ({
})) }))
/** This transaction type creates or updates a DID */ /** This transaction type creates or updates a DID */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DID.h>
#endif
TRANSACTION(ttDID_SET, 58, DIDSet, ({ TRANSACTION(ttDID_SET, 58, DIDSet, ({
{sfDIDDocument, soeOPTIONAL}, {sfDIDDocument, soeOPTIONAL},
{sfURI, soeOPTIONAL}, {sfURI, soeOPTIONAL},
@@ -417,6 +520,9 @@ TRANSACTION(ttDID_SET, 58, DIDSet, ({
TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({})) TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({}))
/** This transaction type creates an Oracle instance */ /** This transaction type creates an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetOracle.h>
#endif
TRANSACTION(ttORACLE_SET, 60, OracleSet, ({ TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
{sfOracleDocumentID, soeREQUIRED}, {sfOracleDocumentID, soeREQUIRED},
{sfProvider, soeOPTIONAL}, {sfProvider, soeOPTIONAL},
@@ -427,17 +533,26 @@ TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
})) }))
/** This transaction type deletes an Oracle instance */ /** This transaction type deletes an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteOracle.h>
#endif
TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({ TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({
{sfOracleDocumentID, soeREQUIRED}, {sfOracleDocumentID, soeREQUIRED},
})) }))
/** This transaction type fixes a problem in the ledger state */ /** This transaction type fixes a problem in the ledger state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LedgerStateFix.h>
#endif
TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({ TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({
{sfLedgerFixType, soeREQUIRED}, {sfLedgerFixType, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
})) }))
/** This transaction type creates a MPTokensIssuance instance */ /** This transaction type creates a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
{sfAssetScale, soeOPTIONAL}, {sfAssetScale, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL},
@@ -446,23 +561,35 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
})) }))
/** This transaction type destroys a MPTokensIssuance instance */ /** This transaction type destroys a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
})) }))
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
})) }))
/** This transaction type authorizes a MPToken instance */ /** This transaction type authorizes a MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#endif
TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({ TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
})) }))
/** This transaction type create an Credential instance */ /** This transaction type create an Credential instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Credentials.h>
#endif
TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({ TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({
{sfSubject, soeREQUIRED}, {sfSubject, soeREQUIRED},
{sfCredentialType, soeREQUIRED}, {sfCredentialType, soeREQUIRED},
@@ -484,6 +611,9 @@ TRANSACTION(ttCREDENTIAL_DELETE, 69, CredentialDelete, ({
})) }))
/** This transaction type modify a NFToken */ /** This transaction type modify a NFToken */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenModify.h>
#endif
TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({ TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
{sfNFTokenID, soeREQUIRED}, {sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL}, {sfOwner, soeOPTIONAL},
@@ -491,12 +621,18 @@ TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
})) }))
/** This transaction type creates or modifies a Permissioned Domain */ /** This transaction type creates or modifies a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({
{sfDomainID, soeOPTIONAL}, {sfDomainID, soeOPTIONAL},
{sfAcceptedCredentials, soeREQUIRED}, {sfAcceptedCredentials, soeREQUIRED},
})) }))
/** This transaction type deletes a Permissioned Domain */ /** This transaction type deletes a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
{sfDomainID, soeREQUIRED}, {sfDomainID, soeREQUIRED},
})) }))
@@ -510,6 +646,9 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
open ledger, so a reveal only becomes public after the revealing open ledger, so a reveal only becomes public after the revealing
validator's transaction set is locked. validator's transaction set is locked.
*/ */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Change.h>
#endif
TRANSACTION(ttENTROPY, 89, Entropy, ({ TRANSACTION(ttENTROPY, 89, Entropy, ({
{sfLedgerSequence, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED},
{sfPublicKey, soeREQUIRED}, {sfPublicKey, soeREQUIRED},
@@ -519,17 +658,26 @@ TRANSACTION(ttENTROPY, 89, Entropy, ({
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well /* sfAccount is supplied by TxFormats::commonFields; listing it here as well
makes the SOTemplate reject the format at construction. */ makes the SOTemplate reject the format at construction. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetManifest.h>
#endif
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({ TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
{sfManifest, soeREQUIRED}, {sfManifest, soeREQUIRED},
})) }))
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */ /* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Cron.h>
#endif
TRANSACTION(ttCRON, 92, Cron, ({ TRANSACTION(ttCRON, 92, Cron, ({
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED},
})) }))
/* Sechedule an alarm for later */ /* Sechedule an alarm for later */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CronSet.h>
#endif
TRANSACTION(ttCRON_SET, 93, CronSet, ({ TRANSACTION(ttCRON_SET, 93, CronSet, ({
{sfDelaySeconds, soeOPTIONAL}, {sfDelaySeconds, soeOPTIONAL},
{sfRepeatCount, soeOPTIONAL}, {sfRepeatCount, soeOPTIONAL},
@@ -537,6 +685,9 @@ TRANSACTION(ttCRON_SET, 93, CronSet, ({
})) }))
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */ /* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRemarks.h>
#endif
TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({ TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
{sfObjectID, soeREQUIRED}, {sfObjectID, soeREQUIRED},
{sfRemarks, soeREQUIRED}, {sfRemarks, soeREQUIRED},
@@ -544,6 +695,9 @@ TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed /* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
* that the sender pays for. */ * that the sender pays for. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Remit.h>
#endif
TRANSACTION(ttREMIT, 95, Remit, ({ TRANSACTION(ttREMIT, 95, Remit, ({
{sfDestination, soeREQUIRED}, {sfDestination, soeREQUIRED},
{sfAmounts, soeOPTIONAL}, {sfAmounts, soeOPTIONAL},
@@ -558,12 +712,18 @@ TRANSACTION(ttREMIT, 95, Remit, ({
/** This transaction can only be used by the genesis account, which is controlled exclusively by /** This transaction can only be used by the genesis account, which is controlled exclusively by
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations, * rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
* according to reward schedule */ * according to reward schedule */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/GenesisMint.h>
#endif
TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({ TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({
{sfGenesisMints, soeREQUIRED}, {sfGenesisMints, soeREQUIRED},
})) }))
/** This transaction accepts a proof of burn from an external network as a basis /** This transaction accepts a proof of burn from an external network as a basis
* for minting according to featureImport */ * for minting according to featureImport */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Import.h>
#endif
TRANSACTION(ttIMPORT, 97, Import, ({ TRANSACTION(ttIMPORT, 97, Import, ({
{sfBlob, soeREQUIRED}, {sfBlob, soeREQUIRED},
{sfIssuer, soeOPTIONAL}, {sfIssuer, soeOPTIONAL},
@@ -571,12 +731,18 @@ TRANSACTION(ttIMPORT, 97, Import, ({
/** This transaction resets accumulator/counters and claims a reward for holding an average balance /** This transaction resets accumulator/counters and claims a reward for holding an average balance
* from a specified hook */ * from a specified hook */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/ClaimReward.h>
#endif
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({ TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
{sfIssuer, soeOPTIONAL}, {sfIssuer, soeOPTIONAL},
{sfClaimCurrency, soeOPTIONAL}, {sfClaimCurrency, soeOPTIONAL},
})) }))
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/ /** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Invoke.h>
#endif
TRANSACTION(ttINVOKE, 99, Invoke, ({ TRANSACTION(ttINVOKE, 99, Invoke, ({
{sfBlob, soeOPTIONAL}, {sfBlob, soeOPTIONAL},
{sfDestination, soeOPTIONAL}, {sfDestination, soeOPTIONAL},
-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 ripple {
namespace detail {
[[noreturn]] void
accessViolation() noexcept
{
// dereference memory location zero
int volatile* j = 0;
(void)*j;
std::abort();
}
} // namespace detail
void void
LogThrow(std::string const& title) LogThrow(std::string const& title)
{ {
@@ -55,7 +42,7 @@ LogicError(std::string const& s) noexcept
// here. // here.
// For the above reasons, we want this contract to stand out. // For the above reasons, we want this contract to stand out.
UNREACHABLE("LogicError", {{"message", s}}); UNREACHABLE("LogicError", {{"message", s}});
detail::accessViolation(); std::abort();
} }
} // namespace ripple } // namespace ripple
+7 -7
View File
@@ -33,7 +33,7 @@
namespace beast::detail { namespace beast::detail {
inline void inline void
setCurrentThreadNameImpl(std::string_view name) setCurrentThreadNameImpl(char const* name)
{ {
#if DEBUG && BOOST_COMP_MSVC #if DEBUG && BOOST_COMP_MSVC
// This technique is documented by Microsoft and works for all versions // This technique is documented by Microsoft and works for all versions
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(std::string_view name)
THREADNAME_INFO ni; THREADNAME_INFO ni;
ni.dwType = 0x1000; ni.dwType = 0x1000;
ni.szName = name.data(); ni.szName = name;
ni.dwThreadID = GetCurrentThreadId(); ni.dwThreadID = GetCurrentThreadId();
ni.dwFlags = 0; ni.dwFlags = 0;
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(std::string_view name)
namespace beast::detail { namespace beast::detail {
inline void inline void
setCurrentThreadNameImpl(std::string_view name) setCurrentThreadNameImpl(char const* name)
{ {
pthread_setname_np(name.data()); pthread_setname_np(name);
} }
} // namespace beast::detail } // namespace beast::detail
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(std::string_view name)
namespace beast::detail { namespace beast::detail {
inline void 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 } // namespace beast::detail
@@ -119,7 +119,7 @@ void
setCurrentThreadName(std::string_view name) setCurrentThreadName(std::string_view name)
{ {
detail::threadName = name; detail::threadName = name;
detail::setCurrentThreadNameImpl(name); detail::setCurrentThreadNameImpl(detail::threadName.c_str());
} }
} // namespace beast } // namespace beast
+4 -3
View File
@@ -29,7 +29,9 @@ bool
is_private(AddressV6 const& addr) is_private(AddressV6 const& addr)
{ {
return ( 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() && (addr.is_v4_mapped() &&
is_private(boost::asio::ip::make_address_v4( is_private(boost::asio::ip::make_address_v4(
boost::asio::ip::v4_mapped, addr)))); boost::asio::ip::v4_mapped, addr))));
@@ -38,8 +40,7 @@ is_private(AddressV6 const& addr)
bool bool
is_public(AddressV6 const& addr) is_public(AddressV6 const& addr)
{ {
// TODO is this correct? return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
return !is_private(addr) && !addr.is_multicast();
} }
} // namespace IP } // namespace IP
-47
View File
@@ -171,53 +171,6 @@ ed25519Canonical(Slice const& sig)
return std::lexicographical_compare(S, S + 32, Order, Order + 32); 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 bool
verifyDigest( verifyDigest(
PublicKey const& publicKey, PublicKey const& publicKey,
+1 -1
View File
@@ -961,7 +961,7 @@ amountFromJson(SField const& name, Json::Value const& v)
bool const native = !currencyOrMPTID.isString() || bool const native = !currencyOrMPTID.isString() ||
currencyOrMPTID.asString().empty() || currencyOrMPTID.asString().empty() ||
(currencyOrMPTID.asString() == systemCurrencyCode()); (currencyOrMPTID.asString() == systemCurrencyCode);
if (native) if (native)
{ {
+3 -21
View File
@@ -25,6 +25,7 @@
#include <xrpl/protocol/SecretKey.h> #include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/detail/secp256k1.h> #include <xrpl/protocol/detail/secp256k1.h>
#include <xrpl/protocol/digest.h> #include <xrpl/protocol/digest.h>
#include <cstring> #include <cstring>
#include <ed25519.h> #include <ed25519.h>
@@ -32,25 +33,7 @@ namespace ripple {
SecretKey::~SecretKey() SecretKey::~SecretKey()
{ {
secure_erase(buf_, sizeof(buf_)); secure_erase(buf_.data(), buf_.size());
}
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);
} }
namespace detail { namespace detail {
@@ -214,7 +197,6 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
if (publicKeyType(pk.slice()) != KeyType::secp256k1) if (publicKeyType(pk.slice()) != KeyType::secp256k1)
LogicError("sign: secp256k1 required for digest signing"); LogicError("sign: secp256k1 required for digest signing");
BOOST_ASSERT(sk.size() == 32);
secp256k1_ecdsa_signature sig_imp; secp256k1_ecdsa_signature sig_imp;
if (secp256k1_ecdsa_sign( if (secp256k1_ecdsa_sign(
secp256k1Context(), secp256k1Context(),
@@ -226,7 +208,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
LogicError("sign: secp256k1_ecdsa_sign failed"); LogicError("sign: secp256k1_ecdsa_sign failed");
unsigned char sig[72]; unsigned char sig[72];
size_t len = sizeof(sig); auto len = sizeof(sig);
if (secp256k1_ecdsa_signature_serialize_der( if (secp256k1_ecdsa_signature_serialize_der(
secp256k1Context(), sig, &len, &sig_imp) != 1) secp256k1Context(), sig, &len, &sig_imp) != 1)
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed"); LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
+3 -19
View File
@@ -39,28 +39,12 @@ Seed::~Seed()
secure_erase(buf_.data(), buf_.size()); 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 Seed
randomSeed() randomSeed()
{ {
std::array<std::uint8_t, 16> buffer; std::array<std::uint8_t, 16> buffer;
beast::rngfill(buffer.data(), buffer.size(), crypto_prng()); beast::rngfill(buffer, crypto_prng());
Seed seed(makeSlice(buffer)); Seed seed(buffer);
secure_erase(buffer.data(), buffer.size()); secure_erase(buffer.data(), buffer.size());
return seed; return seed;
} }
@@ -117,7 +101,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
if (RFC1751::getKeyFromEnglish(key, str) == 1) if (RFC1751::getKeyFromEnglish(key, str) == 1)
{ {
Blob const blob(key.rbegin(), key.rend()); 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 // CHECKME this returns a different version than the replies below. Is
// this by design or an accident or should it be using // this by design or an accident or should it be using
// BuildInfo::getFullVersionString () as well? // BuildInfo::getFullVersionString () as well?
output("Server: " + systemName() + "-json-rpc/v1"); output("Server: " + std::string(systemName) + "-json-rpc/v1");
output("\r\n"); output("\r\n");
// Be careful in modifying this! If you change the contents you MUST // Be careful in modifying this! If you change the contents you MUST
@@ -147,7 +147,7 @@ HTTPReply(
"\r\n" "\r\n"
"Content-Type: application/json; charset=UTF-8\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(BuildInfo::getFullVersionString());
output( output(
"\r\n" "\r\n"
+74
View File
@@ -1271,6 +1271,79 @@ public:
BEAST_EXPECT(table->needValidatedLedger(257)); 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 void
testFeature(FeatureBitset const& feat) testFeature(FeatureBitset const& feat)
{ {
@@ -1294,6 +1367,7 @@ public:
testBadConfig(); testBadConfig();
testEnableVeto(); testEnableVeto();
testHasUnsupported(); testHasUnsupported();
testIgnoredUnsupported();
testFeature(all - fixMajorityCalc); testFeature(all - fixMajorityCalc);
testFeature(all); testFeature(all);
} }
+4 -9
View File
@@ -529,11 +529,10 @@ public:
std::vector<JobName> jobs; std::vector<JobName> jobs;
{ {
auto const& jobTypes = JobTypes::instance();
jobs.reserve(jobTypes.size()); jobs.reserve(jobTypes.size());
for (auto const& job : jobTypes) 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()); std::shuffle(jobs.begin(), jobs.end(), default_prng());
@@ -865,14 +864,10 @@ public:
JobType jobType; JobType jobType;
std::string jobTypeName; 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); jobType = jobTypes[index].type;
auto iter{jobTypes.begin()}; jobTypeName = jobTypes[index].name;
std::advance(iter, dis(default_prng()));
jobType = iter->second.type();
jobTypeName = iter->second.name();
} }
// Say there's one worker thread. // 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()); std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView); [[maybe_unused]] test96 t96(sView);
} }
catch (std::invalid_argument const& e) catch (std::exception const& e)
{ {
BEAST_EXPECT( BEAST_EXPECT(e.what() == std::string("invalid hex string"));
e.what() ==
std::string("invalid length for hex string"));
caught = true; caught = true;
} }
BEAST_EXPECT(caught); BEAST_EXPECT(caught);
@@ -327,10 +325,9 @@ struct base_uint_test : beast::unit_test::suite
std::string_view sView(str.data(), str.size()); std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView); [[maybe_unused]] test96 t96(sView);
} }
catch (std::range_error const& e) catch (std::exception const& e)
{ {
BEAST_EXPECT( BEAST_EXPECT(e.what() == std::string("invalid hex string"));
e.what() == std::string("invalid hex character"));
caught = true; caught = true;
} }
BEAST_EXPECT(caught); BEAST_EXPECT(caught);
+29
View File
@@ -324,6 +324,35 @@ public:
BEAST_EXPECT(!is_loopback(ep)); BEAST_EXPECT(!is_loopback(ep));
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(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"); ep = Endpoint::from_string("192.0.2.112");
BEAST_EXPECT(!is_unspecified(ep)); BEAST_EXPECT(!is_unspecified(ep));
+11 -3
View File
@@ -120,23 +120,28 @@ public:
auto& jq = env.app().getJobQueue(); auto& jq = env.app().getJobQueue();
static int const N = 4; // The test assumes that all the threads and coroutines it launches
std::array<std::shared_ptr<JobQueue::Coro>, N> a; // 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); LocalValue<int> lv(-1);
BEAST_EXPECT(*lv == -1); BEAST_EXPECT(*lv == -1);
gate g; gate g;
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() { jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
this->BEAST_EXPECT(*lv == -1); this->BEAST_EXPECT(*lv == -1);
*lv = -2; *lv = -2;
this->BEAST_EXPECT(*lv == -2); this->BEAST_EXPECT(*lv == -2);
g.signal(); g.signal();
}); });
BEAST_EXPECT(g.wait_for(5s)); BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1); 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) { jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
a[id] = c; a[id] = c;
@@ -154,12 +159,14 @@ public:
BEAST_EXPECT(g.wait_for(5s)); BEAST_EXPECT(g.wait_for(5s));
a[i]->join(); a[i]->join();
} }
for (auto const& c : a) for (auto const& c : a)
{ {
c->post(); c->post();
BEAST_EXPECT(g.wait_for(5s)); BEAST_EXPECT(g.wait_for(5s));
c->join(); c->join();
} }
for (auto const& c : a) for (auto const& c : a)
{ {
c->post(); c->post();
@@ -170,6 +177,7 @@ public:
this->BEAST_EXPECT(*lv == -2); this->BEAST_EXPECT(*lv == -2);
g.signal(); g.signal();
}); });
BEAST_EXPECT(g.wait_for(5s)); BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1); BEAST_EXPECT(*lv == -1);
} }
+81 -33
View File
@@ -21,6 +21,11 @@
#include <xrpld/core/JobQueue.h> #include <xrpld/core/JobQueue.h>
#include <xrpl/beast/unit_test.h> #include <xrpl/beast/unit_test.h>
#include <future>
#include <mutex>
#include <string>
#include <vector>
namespace ripple { namespace ripple {
namespace test { namespace test {
@@ -45,22 +50,19 @@ class JobQueue_test : public beast::unit_test::suite
while (jobRan == false) 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 // If the JobQueue is stopped or in the process of stopping, attempting
// unprotected variable on the stack should be completely safe. // to schedule a coroutine should fail.
// Not recommended for the faint of heart... jQueue.stop();
bool unprotected; BEAST_EXPECT(jQueue.isStopping());
BEAST_EXPECT(
jQueue.addJob(jtCLIENT, "JobAddTest2", [&unprotected]() { auto const coro = jQueue.postCoro(
unprotected = false; jtCLIENT,
}) == false); "JobAddTest2",
} [](std::shared_ptr<JobQueue::Coro> const&) {
LogicError("Unscheduled coroutine improperly invoked!");
});
BEAST_EXPECT(coro == nullptr);
} }
void void
@@ -128,31 +130,77 @@ class JobQueue_test : public beast::unit_test::suite
} }
BEAST_EXPECT(yieldCount == 4); 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 // If the JobQueue is stopped or in the process of stopping, attempting
// unprotected variable on the stack should be completely safe. // to schedule a coroutine should fail.
// Not recommended for the faint of heart... jQueue.stop();
bool unprotected; BEAST_EXPECT(jQueue.isStopping());
auto const coro = jQueue.postCoro(
jtCLIENT, auto const coro = jQueue.postCoro(
"PostCoroTest3", jtCLIENT,
[&unprotected](std::shared_ptr<JobQueue::Coro> const&) { "PostCoroTest3",
unprotected = false; [](std::shared_ptr<JobQueue::Coro> const&) {
}); LogicError("Unscheduled coroutine improperly invoked!");
BEAST_EXPECT(coro == nullptr); });
} 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: public:
void void
run() override run() override
{ {
testPriority();
testAddJob(); testAddJob();
testPostCoro(); 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
+1 -9
View File
@@ -52,18 +52,10 @@ public:
for (std::uint8_t i = 0; i < 64; i++) for (std::uint8_t i = 0; i < 64; i++)
{ {
beast::rngfill(src, sizeof(src), default_prng()); 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); 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 std::string
+2 -2
View File
@@ -469,7 +469,7 @@ class PWAServe_test : public beast::unit_test::suite,
BEAST_EXPECT(html(r)); BEAST_EXPECT(html(r));
BEAST_EXPECT(r.body() == doc()); BEAST_EXPECT(r.body() == doc());
BEAST_EXPECT(hdr(r, "Connection") == "close"); BEAST_EXPECT(hdr(r, "Connection") == "close");
BEAST_EXPECT(hdr(r, "Server") == systemName()); BEAST_EXPECT(hdr(r, "Server") == systemName);
BEAST_EXPECT( BEAST_EXPECT(
hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0); hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0);
BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY"); BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY");
@@ -695,7 +695,7 @@ class PWAServe_test : public beast::unit_test::suite,
yield); yield);
BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0); BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0);
BEAST_EXPECT( BEAST_EXPECT(
s.find("\r\nServer: " + systemName() + "\r\n") != s.find("\r\nServer: " + std::string(systemName) + "\r\n") !=
std::string::npos); 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& 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( m_amendmentTable = make_AmendmentTable(
*this, *this,
config().AMENDMENT_MAJORITY_TIME, config().AMENDMENT_MAJORITY_TIME,
supported, supported,
upVoted, upVoted,
downVoted, downVoted,
logs_->journal("Amendments")); logs_->journal("Amendments"),
ignored);
} }
Pathfinder::initPathTable(); Pathfinder::initPathTable();
+1 -1
View File
@@ -132,7 +132,7 @@ void
printHelp(const po::options_description& desc) printHelp(const po::options_description& desc)
{ {
std::cerr std::cerr
<< systemName() << "d [options] <command> <params>\n" << systemName << "d [options] <command> <params>\n"
<< desc << std::endl << desc << std::endl
<< "Commands: \n" << "Commands: \n"
" account_currencies <account> [<ledger>]\n" " account_currencies <account> [<ledger>]\n"
+2 -1
View File
@@ -211,7 +211,8 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported, std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled, Section const& enabled,
Section const& vetoed, Section const& vetoed,
beast::Journal journal); beast::Journal journal,
Section const& ignored = Section{});
} // namespace ripple } // namespace ripple
+3 -5
View File
@@ -1621,12 +1621,10 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
#ifdef DEBUG #ifdef DEBUG
if (!isTesSuccess(e.result)) if (!isTesSuccess(e.result))
{ {
std::string token, human; if (std::string token = transToken(e.result); token != "-")
if (transResultInfo(e.result, token, human))
{ {
JLOG(m_journal.info()) JLOG(m_journal.debug()) << "Tx " << e.transaction->getID()
<< "TransactionResult: " << token << ": " << human; << ", result: " << token;
} }
} }
#endif #endif
+79 -7
View File
@@ -59,7 +59,7 @@ parseSection(Section const& section)
uint256 id; uint256 id;
if (!id.parseHex(match[1])) if (!id.parseHex(match[1].str()))
Throw<std::runtime_error>( Throw<std::runtime_error>(
"Invalid amendment ID '" + match[1] + "' in [" + "Invalid amendment ID '" + match[1] + "' in [" +
section.name() + "]"); section.name() + "]");
@@ -302,6 +302,15 @@ struct AmendmentState
/** Indicates an amendment that this server has code support for. */ /** Indicates an amendment that this server has code support for. */
bool supported = false; 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. */ /** The name of this amendment, possibly empty. */
std::string name; std::string name;
@@ -476,7 +485,8 @@ public:
std::vector<FeatureInfo> const& supported, std::vector<FeatureInfo> const& supported,
Section const& enabled, Section const& enabled,
Section const& vetoed, Section const& vetoed,
beast::Journal journal); beast::Journal journal,
Section const& ignored);
uint256 uint256
find(std::string const& name) const override; find(std::string const& name) const override;
@@ -543,7 +553,8 @@ AmendmentTableImpl::AmendmentTableImpl(
std::vector<FeatureInfo> const& supported, std::vector<FeatureInfo> const& supported,
Section const& enabled, Section const& enabled,
Section const& vetoed, Section const& vetoed,
beast::Journal journal) beast::Journal journal,
Section const& ignored)
: lastUpdateSeq_(0) : lastUpdateSeq_(0)
, majorityTime_(majorityTime) , majorityTime_(majorityTime)
, unsupportedEnabled_(false) , unsupportedEnabled_(false)
@@ -586,6 +597,59 @@ AmendmentTableImpl::AmendmentTableImpl(
<< " voted by default if not enabled on the ledger."; << " 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; hash_set<uint256> detect_conflict;
// Parse enabled amendments from config // Parse enabled amendments from config
for (std::pair<uint256, std::string> const& a : parseSection(enabled)) for (std::pair<uint256, std::string> const& a : parseSection(enabled))
@@ -775,7 +839,12 @@ AmendmentTableImpl::enable(uint256 const& amendment)
s.enabled = true; 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 JLOG(j_.error()) << "Unsupported amendment " << amendment
<< " activated."; << " activated.";
@@ -1044,7 +1113,9 @@ AmendmentTableImpl::injectJson(
if (!fs.name.empty()) if (!fs.name.empty())
v[jss::name] = fs.name; 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.enabled && isAdmin)
{ {
if (fs.vote == AmendmentVote::obsolete) if (fs.vote == AmendmentVote::obsolete)
@@ -1113,10 +1184,11 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported, std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled, Section const& enabled,
Section const& vetoed, Section const& vetoed,
beast::Journal journal) beast::Journal journal,
Section const& ignored)
{ {
return std::make_unique<AmendmentTableImpl>( return std::make_unique<AmendmentTableImpl>(
app, majorityTime, supported, enabled, vetoed, journal); app, majorityTime, supported, enabled, vetoed, journal, ignored);
} }
} // namespace ripple } // namespace ripple
+1 -1
View File
@@ -198,7 +198,7 @@ PathRequest::isValid(std::shared_ptr<RippleLineCache> const& crCache)
if (!sleDest) if (!sleDest)
{ {
jvDestCur.append(Json::Value(systemCurrencyCode())); jvDestCur.append(Json::Value(std::string(systemCurrencyCode)));
if (!saDstAmount.native()) if (!saDstAmount.native())
{ {
// Only XRP can be send to a non-existent account. // 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/basics/CountedObject.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STPathSet.h> #include <xrpl/protocol/STPathSet.h>
#include <optional>
namespace ripple { namespace ripple {
@@ -206,7 +207,7 @@ private:
bool convert_all_; bool convert_all_;
std::shared_ptr<ReadView const> mLedger; std::shared_ptr<ReadView const> mLedger;
std::unique_ptr<LoadEvent> m_loadEvent; std::optional<LoadEvent> m_loadEvent;
std::shared_ptr<RippleLineCache> mRLCache; std::shared_ptr<RippleLineCache> mRLCache;
STPathElement mSource; STPathElement mSource;
+15 -55
View File
@@ -18,61 +18,21 @@
//============================================================================== //==============================================================================
#include <xrpld/app/tx/applySteps.h> #include <xrpld/app/tx/applySteps.h>
#include <xrpld/app/tx/detail/AMMBid.h> #pragma push_macro("TRANSACTION")
#include <xrpld/app/tx/detail/AMMClawback.h> #undef TRANSACTION
#include <xrpld/app/tx/detail/AMMCreate.h>
#include <xrpld/app/tx/detail/AMMDelete.h> // Do nothing
#include <xrpld/app/tx/detail/AMMDeposit.h> #define TRANSACTION(...)
#include <xrpld/app/tx/detail/AMMVote.h> #define TRANSACTION_INCLUDE 1
#include <xrpld/app/tx/detail/AMMWithdraw.h>
#include <xrpld/app/tx/detail/ApplyContext.h> #include <xrpl/protocol/detail/transactions.macro>
#include <xrpld/app/tx/detail/CancelCheck.h>
#include <xrpld/app/tx/detail/CancelOffer.h> #undef TRANSACTION
#include <xrpld/app/tx/detail/CashCheck.h> #pragma pop_macro("TRANSACTION")
#include <xrpld/app/tx/detail/Change.h>
#include <xrpld/app/tx/detail/ClaimReward.h> // DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
#include <xrpld/app/tx/detail/Clawback.h> // See the instructions at the top of transactions.macro instead.
#include <xrpld/app/tx/detail/CreateCheck.h>
#include <xrpld/app/tx/detail/CreateOffer.h>
#include <xrpld/app/tx/detail/CreateTicket.h>
#include <xrpld/app/tx/detail/Credentials.h>
#include <xrpld/app/tx/detail/Cron.h>
#include <xrpld/app/tx/detail/CronSet.h>
#include <xrpld/app/tx/detail/DID.h>
#include <xrpld/app/tx/detail/DeleteAccount.h>
#include <xrpld/app/tx/detail/DeleteOracle.h>
#include <xrpld/app/tx/detail/DepositPreauth.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/GenesisMint.h>
#include <xrpld/app/tx/detail/Import.h>
#include <xrpld/app/tx/detail/Invoke.h>
#include <xrpld/app/tx/detail/LedgerStateFix.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
#include <xrpld/app/tx/detail/NFTokenBurn.h>
#include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
#include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
#include <xrpld/app/tx/detail/NFTokenMint.h>
#include <xrpld/app/tx/detail/NFTokenModify.h>
#include <xrpld/app/tx/detail/PayChan.h>
#include <xrpld/app/tx/detail/Payment.h>
#include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
#include <xrpld/app/tx/detail/Remit.h>
#include <xrpld/app/tx/detail/SetAccount.h>
#include <xrpld/app/tx/detail/SetHook.h>
#include <xrpld/app/tx/detail/SetManifest.h>
#include <xrpld/app/tx/detail/SetOracle.h>
#include <xrpld/app/tx/detail/SetRegularKey.h>
#include <xrpld/app/tx/detail/SetRemarks.h>
#include <xrpld/app/tx/detail/SetSignerList.h>
#include <xrpld/app/tx/detail/SetTrust.h>
#include <xrpld/app/tx/detail/URIToken.h>
#include <xrpld/app/tx/detail/XChainBridge.h>
#include <xrpld/app/tx/detail/XahauGenesis.h>
#include <xrpl/protocol/TxFormats.h> #include <xrpl/protocol/TxFormats.h>
#include <stdexcept> #include <stdexcept>
+3
View File
@@ -102,6 +102,9 @@ struct ConfigSection
#define SECTION_VALIDATORS "validators" #define SECTION_VALIDATORS "validators"
#define SECTION_VALIDATOR_TOKEN "validator_token" #define SECTION_VALIDATOR_TOKEN "validator_token"
#define SECTION_VETO_AMENDMENTS "veto_amendments" #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" #define SECTION_WORKERS "workers"
} // namespace ripple } // namespace ripple
+8 -13
View File
@@ -26,7 +26,7 @@ namespace ripple {
template <class F> template <class F>
JobQueue::Coro::Coro( JobQueue::Coro::Coro(
Coro_create_t, JobQueue::CoroCreator,
JobQueue& jq, JobQueue& jq,
JobType type, JobType type,
std::string const& name, std::string const& name,
@@ -48,6 +48,7 @@ JobQueue::Coro::Coro(
}, },
boost::coroutines::attributes(megabytes(1))) boost::coroutines::attributes(megabytes(1)))
{ {
++jq_.totalCoroutines_;
} }
inline JobQueue::Coro::~Coro() inline JobQueue::Coro::~Coro()
@@ -55,15 +56,14 @@ inline JobQueue::Coro::~Coro()
#ifndef NDEBUG #ifndef NDEBUG
XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished"); XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished");
#endif #endif
--jq_.totalCoroutines_;
} }
inline void inline void
JobQueue::Coro::yield() const JobQueue::Coro::yield() const
{ {
{ ++jq_.suspendedCoroutines_;
std::lock_guard lock(jq_.m_mutex);
++jq_.nSuspend_;
}
(*yield_)(); (*yield_)();
} }
@@ -78,9 +78,7 @@ JobQueue::Coro::post()
// sp keeps 'this' alive // sp keeps 'this' alive
if (jq_.addJob( if (jq_.addJob(
type_, name_, [this, sp = shared_from_this()]() { resume(); })) type_, name_, [this, sp = shared_from_this()]() { resume(); }))
{
return true; return true;
}
// The coroutine will not run. Clean up running_. // The coroutine will not run. Clean up running_.
std::lock_guard lk(mutex_run_); std::lock_guard lk(mutex_run_);
@@ -96,10 +94,7 @@ JobQueue::Coro::resume()
std::lock_guard lk(mutex_run_); std::lock_guard lk(mutex_run_);
running_ = true; running_ = true;
} }
{ --jq_.suspendedCoroutines_;
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
}
auto saved = detail::getLocalValues().release(); auto saved = detail::getLocalValues().release();
detail::getLocalValues().reset(&lvs_); detail::getLocalValues().reset(&lvs_);
std::lock_guard lock(mutex_); 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 // That said, since we're outside the Coro's stack, we need to
// decrement the nSuspend that the Coro's call to yield caused. // decrement the nSuspend that the Coro's call to yield caused.
std::lock_guard lock(jq_.m_mutex); --jq_.suspendedCoroutines_;
--jq_.nSuspend_;
#ifndef NDEBUG #ifndef NDEBUG
finished_ = true; finished_ = true;
#endif #endif
+34 -42
View File
@@ -21,9 +21,8 @@
#define RIPPLE_CORE_JOB_H_INCLUDED #define RIPPLE_CORE_JOB_H_INCLUDED
#include <xrpld/core/ClosureCounter.h> #include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/LoadMonitor.h> #include <xrpld/core/LoadEvent.h>
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <functional>
#include <functional> #include <functional>
@@ -32,10 +31,7 @@ namespace ripple {
// Note that this queue should only be used for CPU-bound jobs // Note that this queue should only be used for CPU-bound jobs
// It is primarily intended for signature checking // It is primarily intended for signature checking
enum JobType { enum JobType : std::uint32_t {
// Special type indicating an invalid job - will go away soon.
jtINVALID = -1,
// Job types - the position in this enum indicates the job priority with // Job types - the position in this enum indicates the job priority with
// earlier jobs having lower priority than later jobs. If you wish to // 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. // insert a job at a specific priority, simply add it at the right location.
@@ -99,27 +95,15 @@ class Job : public CountedObject<Job>
public: public:
using clock_type = std::chrono::steady_clock; 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, Job(JobType type,
std::string const& name, std::string name,
std::uint64_t index, std::uint64_t index,
LoadMonitor& lm, std::reference_wrapper<LoadSampler const> sampler,
std::function<void()> const& job); std::function<void()> job);
Job(Job&& other);
JobType JobType
getType() const; getType() const;
@@ -128,27 +112,35 @@ public:
clock_type::time_point const& clock_type::time_point const&
queue_time() const; queue_time() const;
void /** A description of this specific job.
doJob();
// These comparison operators make the jobs sort in priority order Unlike the name associated with the type of this job, which is fixed,
// in the job set the description may include additional information or context that
bool distinguishes this from other jobs of the same type.
operator<(const Job& j) const; */
bool [[maybe_unused]] std::string const&
operator>(const Job& j) const; description() const;
bool
operator<=(const Job& j) const; void
bool execute();
operator>=(const Job& j) const;
// 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: private:
JobType mType; JobType const type_;
std::uint64_t mJobIndex; std::uint64_t const index_;
std::function<void()> mJob; clock_type::time_point const queued_;
std::shared_ptr<LoadEvent> m_loadEvent; std::function<void()> work_;
std::string mName; LoadEvent loadEvent_;
clock_type::time_point m_queue_time;
}; };
using JobCounter = ClosureCounter<void>; using JobCounter = ClosureCounter<void>;
+127 -108
View File
@@ -21,14 +21,15 @@
#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED #define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
#include <xrpld/core/ClosureCounter.h> #include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/JobTypeData.h>
#include <xrpld/core/JobTypes.h> #include <xrpld/core/JobTypes.h>
#include <xrpld/core/detail/Workers.h> #include <xrpld/core/detail/Workers.h>
#include <xrpl/basics/LocalValue.h> #include <xrpl/basics/LocalValue.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <boost/coroutine/all.hpp> #include <boost/coroutine/all.hpp>
#include <boost/range/begin.hpp> // workaround for boost 1.72 bug #include <boost/range/begin.hpp>
#include <boost/range/end.hpp> // workaround for boost 1.72 bug #include <boost/range/end.hpp>
namespace ripple { namespace ripple {
@@ -37,10 +38,6 @@ class PerfLog;
} }
class Logs; class Logs;
struct Coro_create_t
{
explicit Coro_create_t() = default;
};
/** A pool of threads to perform work. /** A pool of threads to perform work.
@@ -54,15 +51,23 @@ struct Coro_create_t
*/ */
class JobQueue : private Workers::Callback 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: public:
/** Coroutines must run to completion. */ /** 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: private:
detail::LocalValues lvs_; detail::LocalValues lvs_;
JobQueue& jq_; JobQueue& jq_;
JobType type_; JobType const type_;
std::string name_; std::string const name_;
bool running_; bool running_;
std::mutex mutex_; std::mutex mutex_;
std::mutex mutex_run_; std::mutex mutex_run_;
@@ -76,14 +81,23 @@ public:
public: public:
// Private: Used in the implementation // Private: Used in the implementation
template <class F> 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 // Not copy-constructible or assignable
Coro(Coro const&) = delete; Coro(Coro const&) = delete;
Coro& Coro&
operator=(Coro const&) = delete; operator=(Coro const&) = delete;
~Coro(); Coro(Coro&&) = delete;
Coro&
operator=(Coro&&) = delete;
virtual ~Coro();
/** Suspend coroutine execution. /** Suspend coroutine execution.
Effects: Effects:
@@ -167,9 +181,7 @@ public:
{ {
if (auto optionalCountedJob = if (auto optionalCountedJob =
jobCounter_.wrap(std::forward<JobHandler>(jobHandler))) jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
{
return addRefCountedJob(type, name, std::move(*optionalCountedJob)); return addRefCountedJob(type, name, std::move(*optionalCountedJob));
}
return false; return false;
} }
@@ -177,10 +189,10 @@ public:
@param t The type of job. @param t The type of job.
@param name Name of the job. @param name Name of the job.
@param f Has a signature of void(std::shared_ptr<Coro>). Called when the @param f The actual coroutine body; has a signature of:
job executes. 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> template <class F>
std::shared_ptr<Coro> std::shared_ptr<Coro>
@@ -201,10 +213,9 @@ public:
int int
getJobCountGE(JobType t) const; getJobCountGE(JobType t) const;
/** Return a scoped LoadEvent. /** Returns a new load event for the particular job. */
*/ LoadEvent
std::unique_ptr<LoadEvent> makeLoadEvent(JobType t, std::string name);
makeLoadEvent(JobType t, std::string const& name);
/** Add multiple load events. /** Add multiple load events.
*/ */
@@ -226,52 +237,96 @@ public:
void void
stop(); stop();
bool [[nodiscard]] bool
isStopping() const 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 [[nodiscard]] bool
// transition. isStopped() const noexcept
bool {
isStopped() const; 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: private:
friend class Coro; 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; beast::Journal m_journal;
mutable std::mutex m_mutex; mutable std::mutex m_mutex;
std::uint64_t m_lastJob; std::uint64_t m_lastJob = 0;
std::set<Job> m_jobSet; std::set<Job> m_jobSet;
JobCounter jobCounter_; 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() enum class state : std::uint8_t { running = 0, stopping = 1, stopped = 2 };
int m_processCount;
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 // The number of suspended coroutines
int nSuspend_ = 0; std::atomic<int> suspendedCoroutines_ = 0;
Workers m_workers; Workers m_workers;
// Statistics tracking
perf::PerfLog& perfLog_; perf::PerfLog& perfLog_;
// Statistics tracking
beast::insight::Collector::ptr m_collector; beast::insight::Collector::ptr m_collector;
beast::insight::Gauge job_count; beast::insight::Gauge job_count;
beast::insight::Gauge activeThreadsGauge_;
beast::insight::Hook hook; beast::insight::Hook hook;
std::condition_variable cv_; std::condition_variable cv_;
void void
collect(); collect();
JobTypeData&
getJobTypeData(JobType type);
// Adds a reference counted job to the JobQueue. // Adds a reference counted job to the JobQueue.
// //
@@ -282,46 +337,7 @@ private:
// //
// return true if func added to queue. // return true if func added to queue.
bool bool
addRefCountedJob( addRefCountedJob(JobType type, std::string const& name, JobFunction func);
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);
// Runs the next appropriate waiting Job. // Runs the next appropriate waiting Job.
// //
@@ -334,13 +350,14 @@ private:
// Invariants: // Invariants:
// <none> // <none>
void void
processTask(int instance) override; processTask(unsigned int instance) override;
// Returns the limit of running jobs for the given job type. // Called when an uncaught exception occurs while processing a task
// For jobs with no limit, we return the largest int. Hopefully that //
// will be enough. // Invariants:
int // <none>
getJobLimit(JobType type); 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 coro_ member is initialized (a boost::coroutines::pull_type), execution
automatically passes to the coroutine, which we don't want at this point, 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 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 here that construction of a boost pull_type automatically passes execution
the coroutine. A pull_type object automatically generates a push_type that is to the coroutine. A pull_type object automatically generates a push_type
passed as a parameter (do_yield) in the signature of the function the that is passed as a parameter (do_yield) in the signature of the function
pull_type was created with. This function is immediately called during coro_ the pull_type was created with. This function is immediately called during
construction and within it, Coro::yield_ is assigned the push_type coro_ construction and within it, Coro::yield_ is assigned the push_type
parameter (do_yield) address and called (yield()) so we can return execution parameter (do_yield) address and called (yield()) so we can return execution
back to the caller's stack. back to the caller's stack.
@@ -385,19 +402,20 @@ private:
The race condition occurs as follows: The race condition occurs as follows:
1- The coroutine is running. 1. The coroutine is running.
2- The coroutine is about to suspend, but before it can do so, it must 2. The coroutine is about to suspend, but before it can do so, it must
arrange for some event to wake it up. arrange for some event to wake it up.
3- The coroutine arranges 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 4. Before the coroutine can suspend, that event occurs and the
resumption of the coroutine is scheduled on the job queue. 5- Again, before resumption of the coroutine is scheduled on the job queue.
the coroutine can suspend, the resumption of the coroutine is dispatched. 6- 5. Again, before the coroutine can suspend, the resumption of the coroutine
Again, before the coroutine can suspend, the resumption code runs the is dispatched.
coroutine. 6. Again, before the coroutine can suspend, the resumption code runs the
The coroutine is now running in two threads. coroutine.
The lock prevents this from happening as step 6 will block until the The coroutine is now running in two threads. The lock prevents this from
lock is released which only happens after the coroutine completes. happening as step 6 will block until the lock is released which only
happens after the coroutine completes.
*/ */
} // namespace ripple } // namespace ripple
@@ -410,12 +428,12 @@ template <class F>
std::shared_ptr<JobQueue::Coro> std::shared_ptr<JobQueue::Coro>
JobQueue::postCoro(JobType t, std::string const& name, F&& f) JobQueue::postCoro(JobType t, std::string const& name, F&& f)
{ {
/* First param is a detail type to make construction private. if (state_.load(std::memory_order_acquire) != state::running)
Last param is the function the coroutine runs. Signature of return nullptr;
void(std::shared_ptr<Coro>).
*/
auto coro = std::make_shared<Coro>( 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()) if (!coro->post())
{ {
// The Coro was not successfully posted. Disable it so it's destructor // 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->expectEarlyExit();
coro.reset(); coro.reset();
} }
return coro; 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 #define RIPPLE_CORE_JOBTYPES_H_INCLUDED
#include <xrpld/core/Job.h> #include <xrpld/core/Job.h>
#include <xrpld/core/JobTypeInfo.h> #include <array>
#include <map> #include <chrono>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map>
namespace ripple { 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: public:
using Map = std::map<JobType, JobTypeInfo>; JobType const type;
using const_iterator = Map::const_iterator;
private: /// The job's display name
JobTypes() std::string_view const name;
: m_unknown(
jtINVALID, /** The limit on the number of running jobs for this job type.
"invalid",
0, A limit of 0 marks this as a "special job" which is not
std::chrono::milliseconds{0}, dispatched via the job queue.
std::chrono::milliseconds{0}) */
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]( bool
JobType jt, special() const
std::string name, {
int limit, return limit == 0;
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");
[[maybe_unused]] auto const inserted = inline auto constexpr jobTypes = []() consteval {
m_map using namespace std::chrono_literals;
.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");
};
constexpr JobTypeInfo jobs[] = {
// clang-format off // clang-format off
// avg peak // avg peak
// JobType name limit latency latency // JobType name limit latency latency
add(jtPACK, "makeFetchPack", 1, 0ms, 0ms); { jtPACK, "makeFetchPack", 1, },
add(jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms); { jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms },
add(jtVALIDATION_ut, "untrustedValidation", maxLimit, 2000ms, 5000ms); { jtCLIENT, "clientCommand", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtMANIFEST, "manifest", maxLimit, 2000ms, 5000ms); { jtCLIENT_SUBSCRIBE, "clientSubscribe", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtTRANSACTION_l, "localTransaction", maxLimit, 100ms, 500ms); { jtCLIENT_FEE_CHANGE, "clientFeeChange", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms); { jtCLIENT_CONSENSUS, "clientConsensus", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtLEDGER_REQ, "ledgerRequest", 3, 0ms, 0ms); { jtCLIENT_ACCT_HIST, "clientAccountHistory", std::numeric_limits<int>::max(), 2000ms, 5000ms },
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);
// Web traffic from pwa ports: at most two workers, and no latency // Web traffic from pwa ports: at most two workers, and no latency
// targets, so that it can never move the node's load factor. // targets, so that it can never move the node's load factor.
add(jtCLIENT_PWA, "clientPWA", 2, 0ms, 0ms); { jtCLIENT_PWA, "clientPWA", 2, },
add(jtCLIENT_PWADNS, "clientPWADNS", 2, 0ms, 0ms); { jtCLIENT_PWADNS, "clientPWADNS", 2, },
add(jtCLIENT_WEBSOCKET, "clientWebsocket", maxLimit, 2000ms, 5000ms); { jtCLIENT_RPC, "clientRPC", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtRPC, "RPC", maxLimit, 0ms, 0ms); { jtCLIENT_WEBSOCKET, "clientWebsocket", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtUPDATE_PF, "updatePaths", 1, 0ms, 0ms); { jtRPC, "RPC", std::numeric_limits<int>::max(), },
add(jtTRANSACTION, "transaction", maxLimit, 250ms, 1000ms); { jtSWEEP, "sweep", 1, },
add(jtBATCH, "batch", maxLimit, 250ms, 1000ms); { jtVALIDATION_ut, "untrustedValidation", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtADVANCE, "advanceLedger", maxLimit, 0ms, 0ms); { jtMANIFEST, "manifest", std::numeric_limits<int>::max(), 2000ms, 5000ms },
add(jtPUBLEDGER, "publishNewLedger", maxLimit, 3000ms, 4500ms); { jtUPDATE_PF, "updatePaths", 1, },
add(jtTXN_DATA, "fetchTxnData", 5, 0ms, 0ms); { jtTRANSACTION_l, "localTransaction", std::numeric_limits<int>::max(), 100ms, 500ms },
add(jtWAL, "writeAhead", maxLimit, 1000ms, 2500ms); { jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms },
add(jtVALIDATION_t, "trustedValidation", maxLimit, 500ms, 1500ms); { jtLEDGER_REQ, "ledgerRequest", 3, },
add(jtWRITE, "writeObjects", maxLimit, 1750ms, 2500ms); { jtPROPOSAL_ut, "untrustedProposal", std::numeric_limits<int>::max(), 500ms, 1250ms },
add(jtACCEPT, "acceptLedger", maxLimit, 0ms, 0ms); { jtREPLAY_TASK, "ledgerReplayTask", std::numeric_limits<int>::max(), },
add(jtPROPOSAL_t, "trustedProposal", maxLimit, 100ms, 500ms); { jtTRANSACTION, "transaction", std::numeric_limits<int>::max(), 250ms, 1000ms },
add(jtSWEEP, "sweep", 1, 0ms, 0ms); { jtMISSING_TXN, "handleHaveTransactions", 1200, },
add(jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms); { jtREQUESTED_TXN, "doTransactions", 1200, },
add(jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms); { jtBATCH, "batch", std::numeric_limits<int>::max(), 250ms, 1000ms },
add(jtADMIN, "administration", maxLimit, 0ms, 0ms); { jtLEDGER_DATA, "ledgerData", 3, },
add(jtMISSING_TXN, "handleHaveTransactions", 1200, 0ms, 0ms); { jtADVANCE, "advanceLedger", std::numeric_limits<int>::max(), },
add(jtREQUESTED_TXN, "doTransactions", 1200, 0ms, 0ms); { jtPUBLEDGER, "publishNewLedger", std::numeric_limits<int>::max(), 3000ms, 4500ms },
{ jtTXN_DATA, "fetchTxnData", 5, },
add(jtPEER, "peerCommand", 0, 200ms, 2500ms); { jtWAL, "writeAhead", std::numeric_limits<int>::max(), 1000ms, 2500ms },
add(jtDISK, "diskAccess", 0, 500ms, 1000ms); { jtVALIDATION_t, "trustedValidation", std::numeric_limits<int>::max(), 500ms, 1500ms },
add(jtTXN_PROC, "processTransaction", 0, 0ms, 0ms); { jtWRITE, "writeObjects", std::numeric_limits<int>::max(), 1750ms, 2500ms },
add(jtOB_SETUP, "orderBookSetup", 0, 0ms, 0ms); { jtACCEPT, "acceptLedger", std::numeric_limits<int>::max(), },
add(jtPATH_FIND, "pathFind", 0, 0ms, 0ms); { jtPROPOSAL_t, "trustedProposal", std::numeric_limits<int>::max(), 100ms, 500ms },
add(jtHO_READ, "nodeRead", 0, 0ms, 0ms); { jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms },
add(jtHO_WRITE, "nodeWrite", 0, 0ms, 0ms); { jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms },
add(jtGENERIC, "generic", 0, 0ms, 0ms); { jtADMIN, "administration", std::numeric_limits<int>::max(), },
add(jtNS_SYNC_READ, "SyncReadNode", 0, 0ms, 0ms); { jtPEER, "peerCommand", 0, 200ms, 2500ms },
add(jtNS_ASYNC_READ, "AsyncReadNode", 0, 0ms, 0ms); { jtDISK, "diskAccess", 0, 500ms, 1000ms },
add(jtNS_WRITE, "WriteNode", 0, 0ms, 0ms); { 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 // clang-format on
} };
public: // Ensure that the order of entries in the table corresponds to the
static JobTypes const& // order of entries in the enum:
instance() std::underlying_type_t<JobType> idx = 0;
for (auto const& jt : jobs)
{ {
static JobTypes const types; if (static_cast<std::underlying_type_t<JobType>>(jt.type) != idx)
return types; throw "The JobTypes array is improperly ordered";
++idx;
} }
static std::string const& return std::to_array(jobs);
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;
};
} // namespace ripple } // namespace ripple
+23 -26
View File
@@ -20,24 +20,27 @@
#ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED #ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED
#define RIPPLE_CORE_LOADEVENT_H_INCLUDED #define RIPPLE_CORE_LOADEVENT_H_INCLUDED
#include <xrpld/core/LoadMonitor.h>
#include <chrono> #include <chrono>
#include <memory>
#include <string> #include <string>
namespace ripple { namespace ripple {
class LoadMonitor;
// VFALCO NOTE What is the difference between a LoadEvent and a LoadMonitor?
// VFALCO TODO Rename LoadEvent to ScopedLoadSample // VFALCO TODO Rename LoadEvent to ScopedLoadSample
//
// This looks like a scoped elapsed time measuring class
//
class LoadEvent class LoadEvent
{ {
public: public:
// VFALCO TODO remove the dependency on LoadMonitor. Is that possible? LoadEvent(
LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart); 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(LoadEvent const&) = delete;
~LoadEvent(); ~LoadEvent();
@@ -45,17 +48,6 @@ public:
std::string const& std::string const&
name() 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 // Start the measurement. If already started, then
// restart, assigning the elapsed time to the "waiting" // restart, assigning the elapsed time to the "waiting"
// state. // state.
@@ -69,20 +61,25 @@ public:
stop(); stop();
private: private:
LoadMonitor& monitor_; // The name for this event.
// Represents our current state
bool running_;
// The name associated with this event, if any.
std::string name_; 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 // Represents the time we last transitioned states
std::chrono::steady_clock::time_point mark_; std::chrono::steady_clock::time_point mark_;
// The time we spent waiting and running respectively // The time we spent waiting and running respectively
std::chrono::steady_clock::duration timeWaiting_; std::chrono::steady_clock::duration timeWaiting_;
std::chrono::steady_clock::duration timeRunning_; std::chrono::steady_clock::duration timeRunning_;
// Represents our current state
bool running_;
// Determines whether the callback should be invoked
bool neutered_;
}; };
} // namespace ripple } // namespace ripple
+47 -34
View File
@@ -20,69 +20,82 @@
#ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED #ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#define RIPPLE_CORE_LOADMONITOR_H_INCLUDED #define RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#include <xrpld/core/LoadEvent.h>
#include <xrpl/basics/UptimeClock.h> #include <xrpl/basics/UptimeClock.h>
#include <xrpl/beast/utility/Journal.h> #include <xrpl/beast/utility/Journal.h>
#include <chrono> #include <chrono>
#include <functional>
#include <mutex> #include <mutex>
#include <optional>
namespace ripple { 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 /** Monitors load levels and response times
// confusing.
// @todo Consider renaming this, since having both a LoadManager and a
LoadMonitor is confusing, especially considering that they do
different things.
*/
class LoadMonitor class LoadMonitor
{ {
public: public:
explicit LoadMonitor(beast::Journal j); struct Stats
{
std::uint64_t count = 0;
void std::chrono::milliseconds averageLatency{};
addLoadSample(LoadEvent const& sample); 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 void
addSamples(int count, std::chrono::milliseconds latency); 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 Stats
getStats(); getStats();
bool bool
isOver(); isOver();
/** A reference to a function object used to report performance events. */
std::reference_wrapper<LoadSampler const>
sample()
{
return std::cref(eventCallback_);
}
private: private:
void void
update(); update();
std::mutex mutex_; std::mutex mutex_;
std::uint64_t mCounts; std::uint64_t samples_ = 0;
int mLatencyEvents; std::chrono::milliseconds averageLatency_{};
std::chrono::milliseconds mLatencyMSAvg; std::chrono::milliseconds peakLatency_{};
std::chrono::milliseconds mLatencyMSPeak; std::optional<std::chrono::milliseconds> targetAverageLatency_{};
std::chrono::milliseconds mTargetLatencyAvg; std::optional<std::chrono::milliseconds> targetPeakLatency_{};
std::chrono::milliseconds mTargetLatencyPk; UptimeClock::time_point lastUpdate_;
UptimeClock::time_point mLastUpdate;
beast::Journal const j_; beast::Journal const j_;
LoadSampler eventCallback_;
}; };
} // namespace ripple } // namespace ripple
+4 -4
View File
@@ -367,15 +367,15 @@ Config::setup(
strXdgDataHome = strHome + "/.local/share"; strXdgDataHome = strHome + "/.local/share";
} }
CONFIG_DIR = strXdgConfigHome + "/" + systemName(); CONFIG_DIR = strXdgConfigHome + "/" + std::string(systemName);
CONFIG_FILE = CONFIG_DIR / strConfFile; CONFIG_FILE = CONFIG_DIR / strConfFile;
dataDir = strXdgDataHome + "/" + systemName(); dataDir = strXdgDataHome + "/" + std::string(systemName);
if (!boost::filesystem::exists(CONFIG_FILE)) if (!boost::filesystem::exists(CONFIG_FILE))
{ {
CONFIG_DIR = "/etc/opt/" + systemName(); CONFIG_DIR = "/etc/opt/" + std::string(systemName);
CONFIG_FILE = CONFIG_DIR / strConfFile; 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 { namespace ripple {
Job::Job() : mType(jtINVALID), mJobIndex(0)
{
}
Job::Job(JobType type, std::uint64_t index) : mType(type), mJobIndex(index)
{
}
Job::Job( Job::Job(
JobType type, JobType type,
std::string const& name, std::string name,
std::uint64_t index, std::uint64_t index,
LoadMonitor& lm, std::reference_wrapper<LoadSampler const> sample,
std::function<void()> const& job) std::function<void()> job)
: mType(type) : type_(type)
, mJobIndex(index) , index_(index)
, mJob(job) , queued_(clock_type::now())
, mName(name) , work_(std::move(job))
, m_queue_time(clock_type::now()) , 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 JobType
Job::getType() const Job::getType() const
{ {
return mType; return type_;
} }
Job::clock_type::time_point const& Job::clock_type::time_point const&
Job::queue_time() const Job::queue_time() const
{ {
return m_queue_time; return queued_;
}
std::string const&
Job::description() const
{
return loadEvent_.name();
} }
void void
Job::doJob() Job::execute()
{ {
beast::setCurrentThreadName("doJob: " + mName); beast::setCurrentThreadName("Job: " + loadEvent_.name());
m_loadEvent->start();
m_loadEvent->setName(mName);
mJob(); loadEvent_.start();
work_();
// Destroy the lambda, otherwise we won't include // Destroy the lambda, otherwise we won't include
// its duration in the time measurement // its duration in the time measurement
mJob = nullptr; work_ = 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;
} }
} // namespace ripple } // namespace ripple
+178 -268
View File
@@ -20,7 +20,10 @@
#include <xrpld/core/JobQueue.h> #include <xrpld/core/JobQueue.h>
#include <xrpld/perflog/PerfLog.h> #include <xrpld/perflog/PerfLog.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <mutex> #include <mutex>
#include <utility>
namespace ripple { namespace ripple {
@@ -31,36 +34,22 @@ JobQueue::JobQueue(
Logs& logs, Logs& logs,
perf::PerfLog& perfLog) perf::PerfLog& perfLog)
: m_journal(journal) : m_journal(journal)
, m_lastJob(0) , data_([&]<std::size_t... I>(std::index_sequence<I...>) {
, m_invalidJobData(JobTypes::instance().getInvalid(), collector, logs) return std::array<Data, sizeof...(I)>{
, m_processCount(0) Data{jobTypes[I], collector, logs}...};
, m_workers(*this, &perfLog, "JobQueue", threadCount) }(std::make_index_sequence<jobTypes.size()>()))
, m_workers(*this, "JobQueue", threadCount)
, perfLog_(perfLog) , perfLog_(perfLog)
, m_collector(collector) , 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"); 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() JobQueue::~JobQueue()
@@ -74,65 +63,42 @@ JobQueue::collect()
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
job_count = m_jobSet.size(); job_count = m_jobSet.size();
activeThreadsGauge_ = activeThreads_;
} }
bool bool
JobQueue::addRefCountedJob( JobQueue::addRefCountedJob(
JobType type, JobType type,
std::string const& name, std::string const& name,
JobFunction const& func) JobFunction func)
{ {
XRPL_ASSERT( JLOG(m_journal.debug()) << "Adding job '" << name << "' (" << type << ")";
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);
// FIXME: Workaround incorrect client shutdown ordering // FIXME: Workaround incorrect client shutdown ordering
// do not add jobs to a queue with no threads // do not add jobs to a queue with no threads
XRPL_ASSERT( XRPL_ASSERT(
(type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) || (type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) ||
m_workers.getNumberOfThreads() > 0, m_workers.count() > 0,
"ripple::JobQueue::addRefCountedJob : threads available or job " "ripple::JobQueue::addRefCountedJob : threads available or job "
"requires no threads"); "requires no threads");
{ std::lock_guard lock(m_mutex);
std::lock_guard lock(m_mutex);
auto result =
m_jobSet.emplace(type, name, ++m_lastJob, data.load(), func);
auto const& job = *result.first;
JobType const type(job.getType()); auto& d = data_[type];
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);
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; return true;
} }
@@ -140,125 +106,100 @@ int
JobQueue::getJobCount(JobType t) const JobQueue::getJobCount(JobType t) const
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
return data_[t].waiting;
JobDataMap::const_iterator c = m_jobData.find(t);
return (c == m_jobData.end()) ? 0 : c->second.waiting;
} }
int int
JobQueue::getJobCountTotal(JobType t) const JobQueue::getJobCountTotal(JobType t) const
{ {
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
auto const& d = data_[t];
JobDataMap::const_iterator c = m_jobData.find(t); return d.waiting + d.running;
return (c == m_jobData.end()) ? 0 : (c->second.waiting + c->second.running);
} }
int int
JobQueue::getJobCountGE(JobType t) const JobQueue::getJobCountGE(JobType t) const
{ {
// return the number of jobs at this priority level or greater
int ret = 0; int ret = 0;
std::lock_guard lock(m_mutex); 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) if (static_cast<JobType>(i) >= t)
ret += x.second.waiting; ret += data_[i].waiting;
} }
return ret; return ret;
} }
std::unique_ptr<LoadEvent> LoadEvent
JobQueue::makeLoadEvent(JobType t, std::string const& name) JobQueue::makeLoadEvent(JobType t, std::string name)
{ {
JobDataMap::iterator iter(m_jobData.find(t)); return {data_[t].load.sample(), std::move(name), true};
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);
} }
void void
JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed) JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
{ {
if (isStopped()) if (!isStopped()) [[likely]]
LogicError("JobQueue::addLoadEvents() called after JobQueue stopped"); data_[t].load.addSamples(count, elapsed);
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);
} }
bool bool
JobQueue::isOverloaded() JobQueue::isOverloaded()
{ {
return std::any_of(m_jobData.begin(), m_jobData.end(), [](auto& entry) { return std::any_of(
return entry.second.load().isOver(); data_.begin(), data_.end(), [](auto& d) { return d.load.isOver(); });
});
} }
Json::Value Json::Value
JobQueue::getJson(int c) JobQueue::getJson(int)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
Json::Value ret(Json::objectValue); 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; Json::Value priorities = Json::arrayValue;
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
for (auto& x : m_jobData) for (std::size_t i = 0; i < data_.size(); ++i)
{ {
XRPL_ASSERT( auto const type = static_cast<JobType>(i);
x.first != jtINVALID, "ripple::JobQueue::getJson : valid job type");
if (x.first == jtGENERIC) if (type == jtGENERIC)
continue; continue;
JobTypeData& data(x.second); auto& d = data_[i];
auto const s = d.load.getStats();
LoadMonitor::Stats stats(data.stats()); if (s.count || d.waiting || d.running || (s.peakLatency != 0ms))
int waiting(data.waiting);
int running(data.running);
if ((stats.count != 0) || (waiting != 0) ||
(stats.latencyPeak != 0ms) || (running != 0))
{ {
Json::Value& pri = priorities.append(Json::objectValue); 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; pri["over_target"] = true;
if (waiting != 0) if (d.waiting != 0)
pri["waiting"] = waiting; pri["waiting"] = d.waiting;
if (stats.count != 0) if (s.count != 0)
pri["per_second"] = static_cast<int>(stats.count); pri["per_second"] = static_cast<int>(s.count);
if (stats.latencyPeak != 0ms) if (s.peakLatency != 0ms)
pri["peak_time"] = static_cast<int>(stats.latencyPeak.count()); pri["peak_time"] = static_cast<int>(s.peakLatency.count());
if (stats.latencyAvg != 0ms) if (s.averageLatency != 0ms)
pri["avg_time"] = static_cast<int>(stats.latencyAvg.count()); pri["avg_time"] = static_cast<int>(s.averageLatency.count());
if (running != 0) if (d.running != 0)
pri["in_progress"] = running; pri["in_progress"] = d.running;
} }
} }
@@ -271,179 +212,148 @@ void
JobQueue::rendezvous() JobQueue::rendezvous()
{ {
std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> lock(m_mutex);
cv_.wait(lock, [this] { return m_processCount == 0 && m_jobSet.empty(); }); cv_.wait(lock, [this] { return activeThreads_ == 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;
} }
void void
JobQueue::stop() JobQueue::stop()
{ {
stopping_ = true; if (state expected = state::running; !state_.compare_exchange_strong(
using namespace std::chrono_literals; expected, state::stopping, std::memory_order_acq_rel))
jobCounter_.join("JobQueue", 1s, m_journal);
{ {
// After the JobCounter is joined, all jobs have finished executing while (state_.load(std::memory_order_acquire) != state::stopped)
// (i.e. returned from `Job::doJob`) and no more are being accepted, std::this_thread::yield();
// but there may still be some threads between the return of return;
// `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;
} }
}
bool jobCounter_.join("JobQueue", std::chrono::seconds(1), m_journal);
JobQueue::isStopped() const
{ // All jobs have finished executing (i.e. returned from `Job::doJob`) and
return stopped_; // 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 void
JobQueue::getNextJob(Job& job) JobQueue::uncaughtException(unsigned int instance, std::exception_ptr eptr)
{ {
XRPL_ASSERT( try
!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)
{ {
JobType const type = iter->getType(); if (eptr)
XRPL_ASSERT( std::rethrow_exception(eptr);
type != jtINVALID, "ripple::JobQueue::getNextJob : valid job type");
JobTypeData& data(getJobTypeData(type)); LogicError(
XRPL_ASSERT( beast::getCurrentThreadName() +
data.running <= getJobLimit(type), ": Uncaught exception handler invoked with no exception_ptr");
"ripple::JobQueue::getNextJob : maximum jobs running"); }
catch (std::exception const& e)
// Run this job if we're running below the limit. {
if (data.running < getJobLimit(data.type())) LogicError(
{ beast::getCurrentThreadName() +
XRPL_ASSERT( ": Exception caught during task processing: " + e.what());
data.waiting > 0, }
"ripple::JobQueue::getNextJob : positive data waiting"); catch (...)
--data.waiting; {
++data.running; LogicError(
break; 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 void
JobQueue::finishJob(JobType type) JobQueue::processTask(unsigned int instance)
{
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)
{ {
JobType type; 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; 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; data_[type].dequeue.notify(q_time);
{ data_[type].execute.notify(x_time);
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);
} }
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); assert(d.running + d.waiting >= jobTypes[type].limit);
// Job should be destroyed before stopping --d.deferred;
// otherwise destructors with side effects can access m_workers.addTask();
// parent objects that are already destroyed.
finishJob(type);
if (--m_processCount == 0 && m_jobSet.empty())
cv_.notify_all();
} }
// Note that when Job::~Job is called, the last reference --d.running;
// to the associated LoadEvent object (in the Job) may be destroyed.
}
int if (--activeThreads_ == 0 && m_jobSet.empty())
JobQueue::getJobLimit(JobType type) cv_.notify_all();
{
JobTypeInfo const& j(JobTypes::instance().get(type));
XRPL_ASSERT(
j.type() != jtINVALID,
"ripple::JobQueue::getJobLimit : valid job type");
return j.limit();
} }
} // namespace ripple } // namespace ripple
+50 -27
View File
@@ -18,25 +18,63 @@
//============================================================================== //==============================================================================
#include <xrpld/core/LoadEvent.h> #include <xrpld/core/LoadEvent.h>
#include <xrpld/core/LoadMonitor.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <iomanip>
namespace ripple { namespace ripple {
LoadEvent::LoadEvent( LoadEvent::LoadEvent(
LoadMonitor& monitor, std::reference_wrapper<LoadSampler const> callback,
std::string const& name, std::string name,
bool shouldStart) bool shouldStart)
: monitor_(monitor) : name_(std::move(name))
, running_(shouldStart) , callback_(callback)
, name_(name) , mark_(std::chrono::steady_clock::now())
, mark_{std::chrono::steady_clock::now()}
, timeWaiting_{} , timeWaiting_{}
, timeRunning_{} , 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() LoadEvent::~LoadEvent()
{ {
if (running_) if (running_)
@@ -49,27 +87,11 @@ LoadEvent::name() const
return name_; 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 void
LoadEvent::start() LoadEvent::start()
{ {
XRPL_ASSERT(!neutered_, "ripple::LoadEvent::start : is neutered");
auto const now = std::chrono::steady_clock::now(); auto const now = std::chrono::steady_clock::now();
// If we had already called start, this call will // If we had already called start, this call will
@@ -91,7 +113,8 @@ LoadEvent::stop()
mark_ = now; mark_ = now;
running_ = false; running_ = false;
monitor_.addLoadSample(*this); if (!neutered_)
callback_(name_.c_str(), timeRunning_, timeWaiting_);
} }
} // namespace ripple } // namespace ripple
+90 -131
View File
@@ -23,104 +23,95 @@
namespace ripple { namespace ripple {
/* LoadMonitor::LoadMonitor(
std::optional<std::chrono::milliseconds> targetAverageLatency,
TODO std::optional<std::chrono::milliseconds> targetPeakLatency,
---- beast::Journal j)
: targetAverageLatency_([&targetAverageLatency]() {
- Use Journal for logging using namespace std::chrono_literals;
if (targetAverageLatency.has_value() && targetAverageLatency == 0ms)
*/ targetAverageLatency.reset();
return targetAverageLatency;
//------------------------------------------------------------------------------ }())
, targetPeakLatency_([&targetPeakLatency]() {
LoadMonitor::Stats::Stats() using namespace std::chrono_literals;
: count(0), latencyAvg(0), latencyPeak(0), isOverloaded(false) if (targetPeakLatency.has_value() && targetPeakLatency == 0ms)
{ targetPeakLatency.reset();
} return targetPeakLatency;
}())
//------------------------------------------------------------------------------ , lastUpdate_(UptimeClock::now())
LoadMonitor::LoadMonitor(beast::Journal j)
: mCounts(0)
, mLatencyEvents(0)
, mLatencyMSAvg(0)
, mLatencyMSPeak(0)
, mTargetLatencyAvg(0)
, mTargetLatencyPk(0)
, mLastUpdate(UptimeClock::now())
, j_(j) , 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 void
LoadMonitor::update() LoadMonitor::update()
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
auto now = UptimeClock::now();
if (now == mLastUpdate) // current
return;
// VFALCO TODO Why 8? if (auto const now = UptimeClock::now(); now != lastUpdate_)
if ((now < mLastUpdate) || (now > (mLastUpdate + 8s)))
{ {
// way out of date // VFALCO TODO Why 8?
mCounts = 0; if ((now < lastUpdate_) || (now > (lastUpdate_ + 8s)))
mLatencyEvents = 0; {
mLatencyMSAvg = 0ms; // way out of date
mLatencyMSPeak = 0ms; samples_ = 0;
mLastUpdate = now; averageLatency_ = 0ms;
return; 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 /* Add multiple samples
@param count The number of samples to add @param count The number of samples to add
@param latencyMS The total number of milliseconds @param latency The total number of milliseconds
*/ */
void void
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency) 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_); std::lock_guard sl(mutex_);
update(); update();
mCounts += count; samples_ += count;
mLatencyEvents += count; averageLatency_ += latency;
mLatencyMSAvg += latency; peakLatency_ += latency;
mLatencyMSPeak += latency;
auto const latencyPeak = mLatencyEvents * latency * 4 / count; auto const latencyPeak = samples_ * latency * 4 / count;
if (mLatencyMSPeak < latencyPeak) if (peakLatency_ < latencyPeak)
mLatencyMSPeak = latencyPeak; peakLatency_ = 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));
} }
bool bool
LoadMonitor::isOver() LoadMonitor::isOver()
{ {
std::lock_guard sl(mutex_); return getStats().isOverloaded;
update();
if (mLatencyEvents == 0)
return 0;
return isOverTarget(
mLatencyMSAvg / (mLatencyEvents * 4),
mLatencyMSPeak / (mLatencyEvents * 4));
} }
LoadMonitor::Stats LoadMonitor::Stats
LoadMonitor::getStats() LoadMonitor::getStats()
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
Stats stats;
std::lock_guard sl(mutex_); std::lock_guard sl(mutex_);
update(); update();
stats.count = mCounts / 4; Stats s;
s.count = samples_ / 4;
if (mLatencyEvents == 0) if (samples_ != 0)
{ {
stats.latencyAvg = 0ms; s.averageLatency = averageLatency_ / (samples_ * 4);
stats.latencyPeak = 0ms; s.peakLatency = peakLatency_ / (samples_ * 4);
}
else
{
stats.latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4);
stats.latencyPeak = mLatencyMSPeak / (mLatencyEvents * 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 } // namespace ripple
+88 -229
View File
@@ -18,265 +18,124 @@
//============================================================================== //==============================================================================
#include <xrpld/core/detail/Workers.h> #include <xrpld/core/detail/Workers.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/beast/core/CurrentThreadName.h> #include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <chrono>
#include <thread>
namespace ripple { namespace ripple {
Workers::Workers( Workers::Workers(
Callback& callback, Callback& callback,
perf::PerfLog* perfLog, std::string const& name,
std::string const& threadNames, unsigned int count)
int numberOfThreads) : callback_(callback)
: 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)
{ {
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() Workers::~Workers()
{ {
stop(); stop();
deleteWorkers(m_everyone); assert(head_.load() == 0 && threads_.load() == 0);
}
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;
} }
void void
Workers::stop() Workers::stop()
{ {
setNumberOfThreads(0); head_.store(0);
head_.notify_all();
std::unique_lock<std::mutex> lk{m_mut}; while (threads_.load() != 0)
m_cv.wait(lk, [this] { return m_allPaused; }); std::this_thread::yield();
lk.unlock();
XRPL_ASSERT(
numberOfCurrentlyRunningTasks() == 0,
"ripple::Workers::stop : zero running tasks");
} }
void void
Workers::addTask() Workers::addTask()
{ {
m_semaphore.notify(); auto h = head_.load();
}
int while (h != 0)
Workers::numberOfCurrentlyRunningTasks() const noexcept
{
return m_runningTaskCount.load();
}
void
Workers::deleteWorkers(beast::LockFreeStack<Worker>& stack)
{
for (;;)
{ {
Worker* const worker = stack.pop_front(); if (head_.compare_exchange_strong(h, h + 1))
if (worker != nullptr)
{ {
// This call blocks until the thread orderly exits head_.notify_one();
delete worker; h = 0;
}
else
{
break;
} }
} }
} }
//------------------------------------------------------------------------------
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 } // namespace ripple
+73 -142
View File
@@ -20,61 +20,33 @@
#ifndef RIPPLE_CORE_WORKERS_H_INCLUDED #ifndef RIPPLE_CORE_WORKERS_H_INCLUDED
#define 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 <atomic>
#include <condition_variable> #include <condition_variable>
#include <exception>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread>
namespace ripple { namespace ripple {
namespace perf { /** A simple thread pool.
class PerfLog;
}
/** This class is a simple, fixed-sized thread pool. The pool only tracks
* `Workers` is effectively a thread pool. The constructor takes a "callback" the number of outstanding tasks, and dispatches work. When the pool detects
* that has a `void processTask(int instance)` method, and a number of that there is no work to be done, it puts threads to sleep.
* workers. It creates that many `Worker`s and then waits for calls to
* `Workers::addTask()`. It holds a semaphore that counts the number of The pool does not decide which task to run; that is handled by a callback
* pending "tasks", and a condition variable for the event when the last that the pool invokes. This makes it possible to implement the dispatch
* worker pauses itself. strategy (e.g. FIFO or priority queues) that makes sense without requiring
* changes in the thread pool 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 Threads will continue to run (or sleep) until the thread pool is stopped
* the future. which can happen via an explicit call to the stop method, or automatically
* This is implemented with a semaphore. by the pool's destructor.
* If there are any workers waiting when a task is added, then one will be
* woken to claim the task. Note that servicing existing tasks take priority over stopping and stopping
* If not, then the next worker to wait on the semaphore will claim the task. 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)
* Creating a `Worker` creates a thread that calls `Worker::run()`. When that and then exit, potentially leaving work unfinished.
* 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.
*/ */
class Workers class Workers
{ {
@@ -90,50 +62,54 @@ public:
/** Perform a task. /** Perform a task.
The call is made on a thread owned by Workers. It is important The function is invoked precisely once for every call to the
that you only process one task from inside your callback. Each thread pool's addTask method. It executes on one of the thread
call to addTask will result in exactly one call to processTask. pool's threads.
This function should process precisely one task.
@param instance The worker thread instance. @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 @see Workers::addTask
*/ */
virtual void 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. /** Create the object.
A number of initial threads may be optionally specified. The @param callback The task selection & execution algorithm.
default is to create one thread per CPU. @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
@param threadNames The name given to each created worker thread.
*/ */
explicit Workers( explicit Workers(
Callback& callback, Callback& callback,
perf::PerfLog* perfLog, std::string const& name,
std::string const& threadNames = "Worker", unsigned int count);
int numberOfThreads =
static_cast<int>(std::thread::hardware_concurrency()));
~Workers(); ~Workers();
/** Retrieve the desired number of threads. /** Retrieve the number of threads in the thread pool. */
unsigned int
This just returns the number of active threads that were requested. If count() const noexcept
there was a recent call to setNumberOfThreads, the actual number of {
active threads may be temporarily different from what was last requested. return threads_.load();
}
@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);
/** Pause all threads and wait until they are paused. /** Pause all threads and wait until they are paused.
@@ -157,79 +133,34 @@ public:
void void
addTask(); 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: private:
struct PausedTag Callback& callback_;
{
explicit PausedTag() = default;
};
/* 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. To minimize overhead, we track two things: total number of tasks
Idle: Active, but blocked on waiting for a task. added, and total number of tasks dispatched. The key insight is
Paused: Blocked waiting to exit or become active. that both of these numbers are only ever incremented and never
*/ decremented. Along with atomic operations, this allows us to
class Worker : public beast::LockFreeStack<Worker>::Node, write lock free code.
public beast::LockFreeStack<Worker, PausedTag>::Node
{
public:
Worker(
Workers& workers,
std::string const& threadName,
int const instance);
~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 /// The total number of tasks that have been dispatched for processing.
notify(); std::atomic<std::uint64_t> tail_ = 1;
/** @} */
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
}; };
} // namespace ripple } // 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? // BuildInfo::getFullVersionString () as well?
s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n" 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" << "Host: " << strHost << "\r\n"
<< "Content-Type: application/json\r\n" << "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n" << "Content-Length: " << strMsg.size() << "\r\n"
+14 -3
View File
@@ -31,12 +31,23 @@ class DummyScheduler : public Scheduler
public: public:
DummyScheduler() = default; DummyScheduler() = default;
~DummyScheduler() = default; ~DummyScheduler() = default;
void void
scheduleTask(Task& task) override; scheduleTask(Task& task) override
{
// Invoke the task synchronously.
task.performScheduledTask();
}
void void
onFetch(FetchReport const& report) override; onFetch(FetchReport const& report) override
{
}
void void
onBatchWrite(BatchWriteReport const& report) override; onBatchWrite(BatchWriteReport const& report) override
{
}
}; };
} // namespace NodeStore } // 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_; std::queue<std::shared_ptr<Message>> send_queue_;
bool gracefulClose_ = false; bool gracefulClose_ = false;
int large_sendq_ = 0; int large_sendq_ = 0;
std::unique_ptr<LoadEvent> load_event_; std::optional<LoadEvent> load_event_;
// The highest sequence of each PublisherList that has // The highest sequence of each PublisherList that has
// been sent to or received from this peer. // been sent to or received from this peer.
hash_map<PublicKey, std::size_t> publisherListSequences_; hash_map<PublicKey, std::size_t> publisherListSequences_;
+10 -12
View File
@@ -41,9 +41,7 @@
namespace ripple { namespace ripple {
namespace perf { namespace perf {
PerfLogImp::Counters::Counters( PerfLogImp::Counters::Counters(std::set<char const*> const& labels)
std::set<char const*> const& labels,
JobTypes const& jobTypes)
{ {
{ {
// populateRpc // populateRpc
@@ -63,9 +61,9 @@ PerfLogImp::Counters::Counters(
{ {
// populateJq // populateJq
jq_.reserve(jobTypes.size()); 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) if (!inserted)
{ {
// Ensure that no other function populates this entry. // Ensure that no other function populates this entry.
@@ -148,7 +146,7 @@ PerfLogImp::Counters::countersJson() const
j[jss::running_duration_us] = j[jss::running_duration_us] =
std::to_string(value.runningDuration.count()); std::to_string(value.runningDuration.count());
totalJq.runningDuration += value.runningDuration; totalJq.runningDuration += value.runningDuration;
jqobj[JobTypes::name(proc.first)] = j; jqobj[std::string(jobTypes[proc.first].name)] = j;
} }
if (totalJq.queued) if (totalJq.queued)
@@ -185,12 +183,12 @@ PerfLogImp::Counters::currentJson() const
for (auto const& j : jobs) for (auto const& j : jobs)
{ {
if (j.first == jtINVALID) if (!j)
continue; continue;
Json::Value jobj(Json::objectValue); 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( jobj[jss::duration_us] = std::to_string(
std::chrono::duration_cast<microseconds>(present - j.second) std::chrono::duration_cast<microseconds>(present - j->second)
.count()); .count());
jobsArray.append(jobj); jobsArray.append(jobj);
} }
@@ -414,7 +412,7 @@ PerfLogImp::jobStart(
} }
std::lock_guard lock(counters_.jobsMutex_); std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size()) if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance] = {type, startTime}; counters_.jobs_[instance].emplace(type, startTime);
} }
void void
@@ -434,7 +432,7 @@ PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
} }
std::lock_guard lock(counters_.jobsMutex_); std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size()) if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance] = {jtINVALID, steady_time_point()}; counters_.jobs_[instance].reset();
} }
void void
@@ -442,7 +440,7 @@ PerfLogImp::resizeJobs(int const resize)
{ {
std::lock_guard lock(counters_.jobsMutex_); std::lock_guard lock(counters_.jobsMutex_);
if (resize > counters_.jobs_.size()) if (resize > counters_.jobs_.size())
counters_.jobs_.resize(resize, {jtINVALID, steady_time_point()}); counters_.jobs_.resize(resize, std::nullopt);
} }
void void
+6 -3
View File
@@ -25,12 +25,15 @@
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h> #include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <boost/asio/ip/host_name.hpp> #include <boost/asio/ip/host_name.hpp>
#include <condition_variable> #include <condition_variable>
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include <string> #include <string>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
@@ -108,12 +111,12 @@ class PerfLogImp : public PerfLog
// keys and values are created before more threads are started. // keys and values are created before more threads are started.
std::unordered_map<std::string, Locked<Rpc>> rpc_; std::unordered_map<std::string, Locked<Rpc>> rpc_;
std::unordered_map<JobType, Locked<Jq>> jq_; 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_; mutable std::mutex jobsMutex_;
std::unordered_map<std::uint64_t, MethodStart> methods_; std::unordered_map<std::uint64_t, MethodStart> methods_;
mutable std::mutex methodsMutex_; mutable std::mutex methodsMutex_;
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes); Counters(std::set<char const*> const& labels);
Json::Value Json::Value
countersJson() const; countersJson() const;
Json::Value Json::Value
@@ -124,7 +127,7 @@ class PerfLogImp : public PerfLog
Application& app_; Application& app_;
beast::Journal const j_; beast::Journal const j_;
std::function<void()> const signalStop_; std::function<void()> const signalStop_;
Counters counters_{ripple::RPC::getHandlerNames(), JobTypes::instance()}; Counters counters_{ripple::RPC::getHandlerNames()};
std::ofstream logFile_; std::ofstream logFile_;
std::thread thread_; std::thread thread_;
std::mutex mutex_; std::mutex mutex_;
+5 -3
View File
@@ -538,7 +538,7 @@ pwaReply(
{ {
std::string out = std::string out =
"HTTP/1.1 " + std::to_string(status) + " " + pwaReason(status) + "\r\n"; "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" "\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n"
"Referrer-Policy: no-referrer\r\n"; "Referrer-Policy: no-referrer\r\n";
for (auto const& h : headers) for (auto const& h : headers)
@@ -1985,8 +1985,10 @@ ServerHandler::statusResponse(http_request_type const& request) const
if (app_.serverOkay(reason)) if (app_.serverOkay(reason))
{ {
msg.result(boost::beast::http::status::ok); msg.result(boost::beast::http::status::ok);
msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() + msg.body() = "<!DOCTYPE html><html><head><title>" +
" Test page for rippled</title></head><body><h1>" + systemName() + std::string(systemName) +
" Test page for rippled</title></head><body><h1>" +
std::string(systemName) +
" Test</h1><p>This page shows rippled http(s) " " Test</h1><p>This page shows rippled http(s) "
"connectivity is working.</p></body></html>"; "connectivity is working.</p></body></html>";
} }
+1 -1
View File
@@ -436,7 +436,7 @@ private:
static_cast<uint32_t>(entry.first); 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 // Hook API: enough to regenerate hook/extern.h, hookapi.h keylet
// constants and error.h without the xahaud source tree. // 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.isMember(jss::counters) &&
context.params[jss::counters].asBool()); context.params[jss::counters].asBool());
ret[jss::native_currency_code] = systemCurrencyCode(); ret[jss::native_currency_code] = std::string(systemCurrencyCode);
return ret; return ret;
} }
+1 -1
View File
@@ -38,7 +38,7 @@ doServerState(RPC::JsonContext& context)
context.params.isMember(jss::counters) && context.params.isMember(jss::counters) &&
context.params[jss::counters].asBool()); context.params[jss::counters].asBool());
ret[jss::native_currency_code] = systemCurrencyCode(); ret[jss::native_currency_code] = std::string(systemCurrencyCode);
return ret; return ret;
} }
+1 -1
View File
@@ -33,7 +33,7 @@ Json::Value
doStop(RPC::JsonContext& context) doStop(RPC::JsonContext& context)
{ {
context.app.signalStop(); context.app.signalStop();
return RPC::makeObjectValue(systemName() + " server stopping"); return RPC::makeObjectValue(std::string(systemName) + " server stopping");
} }
} // namespace ripple } // namespace ripple
+32 -23
View File
@@ -114,15 +114,15 @@ namespace detail {
// The slab cutoffs and the number of megabytes per allocation are customized // 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 // 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. // in time and with an eye to minimize the number of slack bytes in a block.
inline SlabAllocatorSet<SHAMapItem> slabber({ inline constinit slab::allocator_t<SHAMapItem,
{ 128, megabytes(std::size_t(60)) }, slab::config<1000000, 128>,
{ 192, megabytes(std::size_t(46)) }, slab::config<1000000, 296>,
{ 272, megabytes(std::size_t(60)) }, slab::config<125000, 392>,
{ 384, megabytes(std::size_t(56)) }, slab::config<125000, 520>,
{ 564, megabytes(std::size_t(40)) }, slab::config<62500, 760>,
{ 772, megabytes(std::size_t(46)) }, slab::config<62500, 856>,
{ 1052, megabytes(std::size_t(60)) }, slab::config<31250, 1048>
}); > slabber;
// clang-format on // clang-format on
} // namespace detail } // namespace detail
@@ -130,7 +130,11 @@ inline SlabAllocatorSet<SHAMapItem> slabber({
inline void inline void
intrusive_ptr_add_ref(SHAMapItem const* x) 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. // item while we were trying to increment the refcount.
if (x->refcount_++ == 0) if (x->refcount_++ == 0)
LogicError("SHAMapItem: the reference count is 0!"); LogicError("SHAMapItem: the reference count is 0!");
@@ -139,20 +143,23 @@ intrusive_ptr_add_ref(SHAMapItem const* x)
inline void inline void
intrusive_ptr_release(SHAMapItem const* x) intrusive_ptr_release(SHAMapItem const* x)
{ {
assert(x);
if (--x->refcount_ == 0) if (--x->refcount_ == 0)
{ {
auto p = reinterpret_cast<std::uint8_t const*>(x); auto p = reinterpret_cast<std::uint8_t const*>(x);
// The SHAMapItem constuctor isn't trivial (because the destructor // We need to invoke the destructor for this object before we
// for CountedObject isn't) so we can't avoid calling it here, but // release the memory.
// plan for a future where we might not need to. std::destroy_at(x);
if constexpr (!std::is_trivially_destructible_v<SHAMapItem>)
std::destroy_at(x);
// If the slabber doens't claim this pointer, it was allocated // If the slabber doesn't claim this pointer, it was allocated
// manually, so we free it manually. // 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))) 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 // 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: // the standard library and try to grab a precisely-sized memory block:
if (raw == nullptr) 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 // We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1. We use the fact // constructor of SHAMapItem explicitly sets it to 1. We use the fact
// that the refcount can never be zero before incrementing as an // that the refcount can never be zero before incrementing as an
// invariant. // 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> inline boost::intrusive_ptr<SHAMapItem>
make_shamapitem(SHAMapItem const& other) make_shamapitem(SHAMapItem const& other)
{ {