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