mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-24 04:37:10 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b2d2995c | ||
|
|
86ac135c63 | ||
|
|
5c05e107c0 | ||
|
|
3dc41b2a59 |
No files matched your search
@@ -41,7 +41,7 @@ Loop: xrpld.core xrpld.net
|
|||||||
xrpld.net > xrpld.core
|
xrpld.net > xrpld.core
|
||||||
|
|
||||||
Loop: xrpld.core xrpld.perflog
|
Loop: xrpld.core xrpld.perflog
|
||||||
xrpld.perflog == xrpld.core
|
xrpld.perflog ~= xrpld.core
|
||||||
|
|
||||||
Loop: xrpld.net xrpld.rpc
|
Loop: xrpld.net xrpld.rpc
|
||||||
xrpld.rpc > xrpld.net
|
xrpld.rpc > xrpld.net
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ test.core > test.toplevel
|
|||||||
test.core > test.unit_test
|
test.core > test.unit_test
|
||||||
test.core > xrpl.basics
|
test.core > xrpl.basics
|
||||||
test.core > xrpld.core
|
test.core > xrpld.core
|
||||||
test.core > xrpld.perflog
|
|
||||||
test.core > xrpl.json
|
|
||||||
test.core > xrpl.server
|
test.core > xrpl.server
|
||||||
test.csf > xrpl.basics
|
test.csf > xrpl.basics
|
||||||
test.csf > xrpld.consensus
|
test.csf > xrpld.consensus
|
||||||
|
|||||||
+1
-1
@@ -88,7 +88,7 @@ git remote -v >> release.info &&
|
|||||||
echo "Git status:" >> release.info &&
|
echo "Git status:" >> release.info &&
|
||||||
git status -v >> release.info &&
|
git status -v >> release.info &&
|
||||||
echo "Git log [last 20]:" >> release.info &&
|
echo "Git log [last 20]:" >> release.info &&
|
||||||
git log -n 20 >> release.info;
|
git log -n 20 >> release.info || exit 1
|
||||||
|
|
||||||
if [[ "$4" == "" ]]; then
|
if [[ "$4" == "" ]]; then
|
||||||
# Non GH, local building
|
# Non GH, local building
|
||||||
|
|||||||
@@ -1,75 +1,33 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
/*
|
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
||||||
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpl/beast/type_name.h>
|
#include <xrpl/beast/type_name.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
/** Manages all counted object types. */
|
|
||||||
class CountedObjects
|
class CountedObjects
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static CountedObjects&
|
|
||||||
getInstance() noexcept;
|
|
||||||
|
|
||||||
using Entry = std::pair<std::string, int>;
|
|
||||||
using List = std::vector<Entry>;
|
|
||||||
|
|
||||||
List
|
|
||||||
getCounts(int minimumThreshold) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/** Implementation for @ref CountedObject.
|
|
||||||
|
|
||||||
@internal
|
|
||||||
*/
|
|
||||||
class Counter
|
class Counter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
|
Counter(std::string name) noexcept;
|
||||||
{
|
|
||||||
// Insert ourselves at the front of the lock-free linked list
|
|
||||||
CountedObjects& instance = CountedObjects::getInstance();
|
|
||||||
Counter* head;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
head = instance.m_head.load();
|
|
||||||
next_ = head;
|
|
||||||
} while (instance.m_head.exchange(this) != head);
|
|
||||||
|
|
||||||
++instance.m_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Counter() noexcept = default;
|
|
||||||
|
|
||||||
int
|
int
|
||||||
increment() noexcept
|
increment() noexcept
|
||||||
{
|
{
|
||||||
return ++count_;
|
auto const newCount = ++count_;
|
||||||
|
|
||||||
|
if (auto maxCount = maxCount_.load(); newCount > maxCount)
|
||||||
|
maxCount_.compare_exchange_strong(maxCount, newCount);
|
||||||
|
|
||||||
|
return newCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -79,78 +37,161 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
getCount() const noexcept
|
count() const noexcept
|
||||||
{
|
{
|
||||||
return count_.load();
|
return count_.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
Counter*
|
int
|
||||||
getNext() const noexcept
|
max() const noexcept
|
||||||
{
|
{
|
||||||
return next_;
|
return std::max(count_.load(), maxCount_.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string const&
|
std::string const&
|
||||||
getName() const noexcept
|
name() const noexcept
|
||||||
{
|
{
|
||||||
return name_;
|
return name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string const name_;
|
friend class CountedObjects;
|
||||||
std::atomic<int> count_;
|
|
||||||
Counter* next_;
|
Counter* next_;
|
||||||
|
std::atomic<std::uint32_t> count_ = 0;
|
||||||
|
std::atomic<std::uint32_t> maxCount_ = 0;
|
||||||
|
std::string const name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
class Iterator
|
||||||
CountedObjects() noexcept;
|
{
|
||||||
~CountedObjects() noexcept = default;
|
public:
|
||||||
|
using value_type = Counter const;
|
||||||
|
using reference = value_type&;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
reference
|
||||||
|
operator*() const noexcept
|
||||||
|
{
|
||||||
|
return *current_;
|
||||||
|
}
|
||||||
|
pointer
|
||||||
|
operator->() const noexcept
|
||||||
|
{
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator&
|
||||||
|
operator++() noexcept
|
||||||
|
{
|
||||||
|
current_ = current_->next_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator
|
||||||
|
operator++(int) noexcept
|
||||||
|
{
|
||||||
|
auto tmp = *this;
|
||||||
|
++*this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
operator==(Iterator const&) const noexcept = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Counter* current_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using List = std::vector<std::pair<std::string, int>>;
|
||||||
|
|
||||||
|
constexpr CountedObjects() noexcept = default;
|
||||||
|
|
||||||
|
static CountedObjects&
|
||||||
|
getInstance() noexcept;
|
||||||
|
|
||||||
|
/** Returns the name and count of every counter with at least
|
||||||
|
`minimumThreshold` live instances. */
|
||||||
|
List
|
||||||
|
getCounts(int minimumThreshold) const
|
||||||
|
{
|
||||||
|
List ret;
|
||||||
|
|
||||||
|
for (auto c = head_.load(); c != nullptr; c = c->next_)
|
||||||
|
if (c->count() >= minimumThreshold)
|
||||||
|
ret.emplace_back(c->name(), c->count());
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
begin() const noexcept
|
||||||
|
{
|
||||||
|
return Iterator{head_.load()};
|
||||||
|
}
|
||||||
|
auto
|
||||||
|
end() const noexcept
|
||||||
|
{
|
||||||
|
return Iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<int> m_count;
|
friend class Counter;
|
||||||
std::atomic<Counter*> m_head;
|
|
||||||
|
std::atomic<Counter*> head_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline constinit CountedObjects countedObjects;
|
||||||
|
|
||||||
|
inline CountedObjects&
|
||||||
|
CountedObjects::getInstance() noexcept
|
||||||
|
{
|
||||||
|
return countedObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline CountedObjects::Counter::Counter(std::string name) noexcept
|
||||||
|
: name_(std::move(name))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
next_ = countedObjects.head_.load();
|
||||||
|
while (!countedObjects.head_.compare_exchange_weak(next_, this));
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Tracks the number of instances of an object.
|
|
||||||
|
|
||||||
Derived classes have their instances counted automatically. This is used
|
|
||||||
for reporting purposes.
|
|
||||||
|
|
||||||
@ingroup ripple_basics
|
|
||||||
*/
|
|
||||||
template <class Object>
|
template <class Object>
|
||||||
class CountedObject
|
class CountedObject
|
||||||
{
|
{
|
||||||
private:
|
static CountedObjects::Counter counter_;
|
||||||
static auto&
|
|
||||||
getCounter() noexcept
|
|
||||||
{
|
|
||||||
static CountedObjects::Counter c{beast::type_name<Object>()};
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CountedObject() noexcept
|
CountedObject() noexcept
|
||||||
{
|
{
|
||||||
getCounter().increment();
|
counter_.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
CountedObject(CountedObject const&) noexcept
|
CountedObject(CountedObject const&) noexcept
|
||||||
{
|
{
|
||||||
getCounter().increment();
|
counter_.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
CountedObject&
|
CountedObject&
|
||||||
operator=(CountedObject const&) noexcept = default;
|
operator=(CountedObject const&) noexcept = default;
|
||||||
|
|
||||||
~CountedObject() noexcept
|
~CountedObject() noexcept
|
||||||
{
|
{
|
||||||
getCounter().decrement();
|
counter_.decrement();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Instantiation of the static CountedObject<T>::counter_
|
||||||
|
template <class Object>
|
||||||
|
CountedObjects::Counter CountedObject<Object>::counter_{
|
||||||
|
beast::type_name<Object>()};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -113,7 +113,7 @@ template <>
|
|||||||
inline std::size_t
|
inline std::size_t
|
||||||
extract(SHAMapHash const& key)
|
extract(SHAMapHash const& key)
|
||||||
{
|
{
|
||||||
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data());
|
return extract(key.as_uint256());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
+528
-305
@@ -21,195 +21,443 @@
|
|||||||
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpl/basics/ByteUtilities.h>
|
#include <xrpl/basics/ByteUtilities.h>
|
||||||
#include <xrpl/beast/type_name.h>
|
#include <xrpl/basics/spinlock.h>
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
|
||||||
#include <boost/align.hpp>
|
#include <boost/align.hpp>
|
||||||
#include <boost/container/static_vector.hpp>
|
#include <boost/predef/os.h>
|
||||||
#include <boost/predef.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <bit>
|
||||||
#include <cstring>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#if BOOST_OS_LINUX
|
#include <limits>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#ifdef BOOST_OS_LINUX
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
namespace slab {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
template <typename Type>
|
/// Alignment for hugepage support.
|
||||||
class SlabAllocator
|
inline constexpr std::size_t pageSize = megabytes(std::size_t(2));
|
||||||
|
|
||||||
|
/** Allocate a new 2MB-aligned data buffer.
|
||||||
|
|
||||||
|
@param size Size of the buffer to allocate in bytes.
|
||||||
|
@return Pointer to allocated memory, or nullptr on failure.
|
||||||
|
|
||||||
|
@note On Linux, advises the kernel to back the allocation with
|
||||||
|
transparent huge pages if available.
|
||||||
|
*/
|
||||||
|
[[nodiscard, gnu::malloc]] inline std::uint8_t*
|
||||||
|
allocateBuffer(std::size_t size) noexcept
|
||||||
{
|
{
|
||||||
static_assert(
|
auto ptr = reinterpret_cast<std::uint8_t*>(
|
||||||
sizeof(Type) >= sizeof(std::uint8_t*),
|
boost::alignment::aligned_alloc(pageSize, size));
|
||||||
"SlabAllocator: the requested object must be larger than a pointer.");
|
|
||||||
|
|
||||||
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
|
#if BOOST_OS_LINUX
|
||||||
|
if (ptr != nullptr) [[likely]]
|
||||||
|
madvise(ptr, size, MADV_HUGEPAGE);
|
||||||
|
#endif
|
||||||
|
|
||||||
/** A block of memory that is owned by a slab allocator */
|
return ptr;
|
||||||
struct SlabBlock
|
}
|
||||||
|
|
||||||
|
/** Deallocate a buffer previously allocated by allocateBuffer.
|
||||||
|
|
||||||
|
@param ptr Pointer to buffer, or nullptr (no-op).
|
||||||
|
*/
|
||||||
|
inline void
|
||||||
|
deallocateBuffer(std::uint8_t* ptr) noexcept
|
||||||
|
{
|
||||||
|
boost::alignment::aligned_free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** A block of memory metadata for slab allocators.
|
||||||
|
|
||||||
|
Each block manages a single slab buffer and its associated free list.
|
||||||
|
Blocks are linked together to form a list of slabs per allocator.
|
||||||
|
|
||||||
|
@note slab_t instances are intentionally never destroyed or reclaimed
|
||||||
|
once allocated, to support lock-free iteration of lists of slabs
|
||||||
|
without introducing ABA hazards. The data buffer the class holds
|
||||||
|
can be released independently.
|
||||||
|
*/
|
||||||
|
struct slab_t
|
||||||
|
{
|
||||||
|
/** A node in the intrusive free list.
|
||||||
|
|
||||||
|
Constructed in-place within free memory blocks. The next pointer
|
||||||
|
is const because nodes are never modified after construction;
|
||||||
|
they are simply constructed anew when reused.
|
||||||
|
*/
|
||||||
|
struct item_t
|
||||||
{
|
{
|
||||||
// A mutex to protect the freelist for this block:
|
item_t* const next;
|
||||||
std::mutex m_;
|
|
||||||
|
|
||||||
// A linked list of appropriately sized free buffers:
|
constexpr explicit item_t(item_t* n) noexcept : next(n)
|
||||||
std::uint8_t* l_ = nullptr;
|
|
||||||
|
|
||||||
// The next memory block
|
|
||||||
SlabBlock* next_;
|
|
||||||
|
|
||||||
// The underlying memory block:
|
|
||||||
std::uint8_t const* const p_ = nullptr;
|
|
||||||
|
|
||||||
// The extent of the underlying memory block:
|
|
||||||
std::size_t const size_;
|
|
||||||
|
|
||||||
SlabBlock(
|
|
||||||
SlabBlock* next,
|
|
||||||
std::uint8_t* data,
|
|
||||||
std::size_t size,
|
|
||||||
std::size_t item)
|
|
||||||
: next_(next), p_(data), size_(size)
|
|
||||||
{
|
{
|
||||||
// We don't need to grab the mutex here, since we're the only
|
|
||||||
// ones with access at this moment.
|
|
||||||
|
|
||||||
while (data + item <= p_ + size_)
|
|
||||||
{
|
|
||||||
// Use memcpy to avoid unaligned UB
|
|
||||||
// (will optimize to equivalent code)
|
|
||||||
std::memcpy(data, &l_, sizeof(std::uint8_t*));
|
|
||||||
l_ = data;
|
|
||||||
data += item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~SlabBlock()
|
|
||||||
{
|
|
||||||
// Calling this destructor will release the allocated memory but
|
|
||||||
// will not properly destroy any objects that are constructed in
|
|
||||||
// the block itself.
|
|
||||||
}
|
|
||||||
|
|
||||||
SlabBlock(SlabBlock const& other) = delete;
|
|
||||||
SlabBlock&
|
|
||||||
operator=(SlabBlock const& other) = delete;
|
|
||||||
|
|
||||||
SlabBlock(SlabBlock&& other) = delete;
|
|
||||||
SlabBlock&
|
|
||||||
operator=(SlabBlock&& other) = delete;
|
|
||||||
|
|
||||||
/** Determines whether the given pointer belongs to this allocator */
|
|
||||||
bool
|
|
||||||
own(std::uint8_t const* p) const noexcept
|
|
||||||
{
|
|
||||||
return (p >= p_) && (p < p_ + size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint8_t*
|
|
||||||
allocate() noexcept
|
|
||||||
{
|
|
||||||
std::uint8_t* ret;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard l(m_);
|
|
||||||
|
|
||||||
ret = l_;
|
|
||||||
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
// Use memcpy to avoid unaligned UB
|
|
||||||
// (will optimize to equivalent code)
|
|
||||||
std::memcpy(&l_, ret, sizeof(std::uint8_t*));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return an item to this allocator's freelist.
|
|
||||||
|
|
||||||
@param ptr The pointer to the chunk of memory being deallocated.
|
|
||||||
|
|
||||||
@note This is a dangerous, private interface; the item being
|
|
||||||
returned should belong to this allocator. Debug builds
|
|
||||||
will check and assert if this is not the case. Release
|
|
||||||
builds will not.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
deallocate(std::uint8_t* ptr) noexcept
|
|
||||||
{
|
|
||||||
XRPL_ASSERT(
|
|
||||||
own(ptr),
|
|
||||||
"ripple::SlabAllocator::SlabBlock::deallocate : own input");
|
|
||||||
|
|
||||||
std::lock_guard l(m_);
|
|
||||||
|
|
||||||
// Use memcpy to avoid unaligned UB
|
|
||||||
// (will optimize to equivalent code)
|
|
||||||
std::memcpy(ptr, &l_, sizeof(std::uint8_t*));
|
|
||||||
l_ = ptr;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
// We need this to be trivially destructible so that we do not
|
||||||
// A linked list of slabs
|
// have to explicitly invoke the destructor when popping items
|
||||||
std::atomic<SlabBlock*> slabs_ = nullptr;
|
// from the free list or when deallocating the slab buffer.
|
||||||
|
static_assert(std::is_trivially_destructible_v<item_t>);
|
||||||
|
|
||||||
// The alignment requirements of the item we're allocating:
|
item_t* head_ = nullptr;
|
||||||
std::size_t const itemAlignment_;
|
slab_t* const next_ = nullptr;
|
||||||
|
std::atomic<std::uint8_t*> data_{nullptr};
|
||||||
|
std::atomic<std::uint8_t> lock_{0};
|
||||||
|
std::uint16_t cycles_ = 0;
|
||||||
|
std::uint32_t outstanding_ = 0;
|
||||||
|
|
||||||
// The size of an item, including the extra bytes requested and
|
/** Construct a block that is unlinked. */
|
||||||
// any padding needed for alignment purposes:
|
constexpr slab_t() noexcept = default;
|
||||||
std::size_t const itemSize_;
|
|
||||||
|
|
||||||
// The size of each individual slab:
|
/** Construct a block with a link to the next block in the chain.
|
||||||
std::size_t const slabSize_;
|
|
||||||
|
@param next Pointer to the next block, or nullptr for the tail.
|
||||||
|
*/
|
||||||
|
constexpr explicit slab_t(slab_t* next) noexcept : next_(next)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~slab_t() = default;
|
||||||
|
|
||||||
|
slab_t(slab_t const&) = delete;
|
||||||
|
slab_t&
|
||||||
|
operator=(slab_t const&) = delete;
|
||||||
|
slab_t(slab_t&&) = delete;
|
||||||
|
slab_t&
|
||||||
|
operator=(slab_t&&) = delete;
|
||||||
|
|
||||||
|
/** Attempt to acquire the block's spinlock.
|
||||||
|
|
||||||
|
@return true if lock was acquired, false if already held.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool
|
||||||
|
try_lock() noexcept
|
||||||
|
{
|
||||||
|
return spin_try_lock(lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Acquire the block's spinlock, blocking until available. */
|
||||||
|
void
|
||||||
|
lock() noexcept
|
||||||
|
{
|
||||||
|
spin_lock(lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Release the block's spinlock. */
|
||||||
|
void
|
||||||
|
unlock() noexcept
|
||||||
|
{
|
||||||
|
spin_unlock(lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempt to assign a buffer to this block.
|
||||||
|
|
||||||
|
If the block has no buffer, takes ownership and initializes
|
||||||
|
the free list.
|
||||||
|
|
||||||
|
@param buffer Pointer to the buffer to assign.
|
||||||
|
@param slabSize Size of the buffer in bytes.
|
||||||
|
@param itemSize Size of each item in bytes.
|
||||||
|
@return Pointer to first item if assigned, nullptr if block already
|
||||||
|
has a buffer.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::uint8_t*
|
||||||
|
try_assign(
|
||||||
|
std::uint8_t* buffer,
|
||||||
|
std::size_t slabSize,
|
||||||
|
std::size_t itemSize) noexcept
|
||||||
|
{
|
||||||
|
std::uint8_t* expected = nullptr;
|
||||||
|
|
||||||
|
if (!data_.compare_exchange_strong(
|
||||||
|
expected, buffer, std::memory_order::acquire))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
spin_lock(lock_);
|
||||||
|
|
||||||
|
head_ = nullptr;
|
||||||
|
outstanding_ = 1;
|
||||||
|
|
||||||
|
auto p = buffer;
|
||||||
|
|
||||||
|
for (std::size_t n = (slabSize / itemSize) - 1; n--; p += itemSize)
|
||||||
|
head_ = std::construct_at(reinterpret_cast<item_t*>(p), head_);
|
||||||
|
|
||||||
|
spin_unlock(lock_);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempt to allocate from this block.
|
||||||
|
|
||||||
|
@return Pointer to allocated memory, or nullptr if block is empty.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::uint8_t*
|
||||||
|
try_allocate() noexcept
|
||||||
|
{
|
||||||
|
spin_lock(lock_);
|
||||||
|
|
||||||
|
auto ret = head_;
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
head_ = ret->next;
|
||||||
|
++outstanding_;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock(lock_);
|
||||||
|
|
||||||
|
return reinterpret_cast<std::uint8_t*>(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempt to return a pointer to this block.
|
||||||
|
|
||||||
|
@param ptr Pointer to memory block.
|
||||||
|
@param slabSize Size of the slab's data buffer.
|
||||||
|
@param releaseBuffer Callback invoked with a pointer to the block's
|
||||||
|
buffer so that it can be released.
|
||||||
|
@return true if ptr belonged to this block and was freed,
|
||||||
|
false otherwise.
|
||||||
|
*/
|
||||||
|
template <typename ReleaseFunc>
|
||||||
|
[[nodiscard]] bool
|
||||||
|
try_deallocate(
|
||||||
|
std::uint8_t* ptr,
|
||||||
|
std::size_t slabSize,
|
||||||
|
ReleaseFunc&& releaseBuffer) noexcept
|
||||||
|
{
|
||||||
|
auto data = data_.load(std::memory_order::acquire);
|
||||||
|
|
||||||
|
if (!data || ptr < data || ptr >= data + slabSize)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::uint8_t* buf = nullptr;
|
||||||
|
|
||||||
|
spin_lock(lock_);
|
||||||
|
|
||||||
|
assert(ptr != nullptr);
|
||||||
|
assert(outstanding_ > 0);
|
||||||
|
|
||||||
|
head_ = std::construct_at(reinterpret_cast<item_t*>(ptr), head_);
|
||||||
|
|
||||||
|
// If this block became empty and it is not the first block, we
|
||||||
|
// release its buffer. The first block (at the tail of the list
|
||||||
|
// with next_ == nullptr) is exempt to keep one block ready.
|
||||||
|
if (--outstanding_ == 0 && next_ != nullptr)
|
||||||
|
{
|
||||||
|
head_ = nullptr;
|
||||||
|
buf = data_.exchange(buf, std::memory_order::relaxed);
|
||||||
|
cycles_ += (cycles_ < std::numeric_limits<std::uint16_t>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock(lock_);
|
||||||
|
|
||||||
|
if (buf != nullptr)
|
||||||
|
releaseBuffer(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Global pool of slab_t objects shared by all slab allocators.
|
||||||
|
|
||||||
|
Provides a fixed-size array of pre-allocated slabs with fallback
|
||||||
|
to heap allocation if exhausted.
|
||||||
|
*/
|
||||||
|
class slab_pool_t
|
||||||
|
{
|
||||||
|
std::array<slab_t, 32768> blocks_{};
|
||||||
|
std::atomic<std::size_t> count_{0};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr slab_pool_t() = default;
|
||||||
|
|
||||||
|
slab_pool_t(slab_pool_t const&) = delete;
|
||||||
|
slab_pool_t&
|
||||||
|
operator=(slab_pool_t const&) = delete;
|
||||||
|
slab_pool_t(slab_pool_t&&) = delete;
|
||||||
|
slab_pool_t&
|
||||||
|
operator=(slab_pool_t&&) = delete;
|
||||||
|
|
||||||
|
/** Acquire a slab from the pool.
|
||||||
|
|
||||||
|
@param next Pointer to link as the slab's next pointer.
|
||||||
|
@return Pointer to acquired block, or nullptr if allocation failed.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] slab_t*
|
||||||
|
acquire(slab_t* next) noexcept
|
||||||
|
{
|
||||||
|
auto idx = count_.load(std::memory_order::relaxed);
|
||||||
|
|
||||||
|
while (idx < blocks_.size())
|
||||||
|
{
|
||||||
|
if (count_.compare_exchange_weak(
|
||||||
|
idx, idx + 1, std::memory_order::relaxed))
|
||||||
|
{
|
||||||
|
return std::construct_at(&blocks_[idx], next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool exhausted (unlikely!) so fall back to heap
|
||||||
|
return new (std::nothrow) slab_t(next);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Single global block pool for all slab allocators.
|
||||||
|
inline constinit slab_pool_t globalSlabPool;
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** A slab allocator for fixed-size memory blocks.
|
||||||
|
|
||||||
|
Allocates memory in large slabs (multiples of 2MB) and sub-allocates
|
||||||
|
fixed-size blocks from them. Provides fast allocation with minimal
|
||||||
|
fragmentation for objects of uniform size.
|
||||||
|
|
||||||
|
@tparam Type The type used to determine minimum block size and alignment.
|
||||||
|
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
|
||||||
|
*/
|
||||||
|
template <typename Type, std::size_t Align = alignof(Type)>
|
||||||
|
requires(
|
||||||
|
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
|
||||||
|
std::has_single_bit(Align))
|
||||||
|
class sized_allocator_t
|
||||||
|
{
|
||||||
|
/// Linked list of slabs for this allocator.
|
||||||
|
std::atomic<detail::slab_t*> slabs_{nullptr};
|
||||||
|
|
||||||
|
/// Cached buffer to avoid thrashing when oscillating around block
|
||||||
|
/// boundaries.
|
||||||
|
std::atomic<std::uint8_t*> cachedBuffer_{nullptr};
|
||||||
|
|
||||||
|
/// Spinlock to serialize the slow allocation path.
|
||||||
|
std::atomic<std::uint8_t> blockLock_{0};
|
||||||
|
|
||||||
|
/// Item size (sizeof(Type) + extra, rounded up for alignment).
|
||||||
|
std::size_t itemSize_;
|
||||||
|
|
||||||
|
/// Size of each slab's data buffer (multiple of 2 MB).
|
||||||
|
std::size_t slabSize_;
|
||||||
|
|
||||||
|
/** Allocate a memory block from the existing slabs.
|
||||||
|
|
||||||
|
Fast path allocation that doesn't acquire the global lock.
|
||||||
|
|
||||||
|
@return Pointer to allocated memory, or nullptr if all slabs are full.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::uint8_t*
|
||||||
|
fast_allocate() noexcept
|
||||||
|
{
|
||||||
|
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||||
|
slab = slab->next_)
|
||||||
|
{
|
||||||
|
if (auto ret = slab->try_allocate())
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Allocate a memory block, potentially creating new capacity.
|
||||||
|
|
||||||
|
Slow path allocation that may allocate a new buffer or slab.
|
||||||
|
Must be called with blockLock_ held.
|
||||||
|
|
||||||
|
@return Pointer to allocated memory, or nullptr on failure.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::uint8_t*
|
||||||
|
slow_allocate() noexcept
|
||||||
|
{
|
||||||
|
if (auto ret = fast_allocate())
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
// Use a cached buffer first, if we have one, or allocate memory
|
||||||
|
// from the operating system.
|
||||||
|
auto buf = cachedBuffer_.exchange(nullptr, std::memory_order::acquire);
|
||||||
|
|
||||||
|
if (!buf)
|
||||||
|
buf = detail::allocateBuffer(slabSize_);
|
||||||
|
|
||||||
|
if (!buf) [[unlikely]]
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Try to give buffer to an existing empty slab
|
||||||
|
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||||
|
slab = slab->next_)
|
||||||
|
{
|
||||||
|
if (auto ret = slab->try_assign(buf, slabSize_, itemSize_))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new slab from global pool
|
||||||
|
auto* slab = detail::globalSlabPool.acquire(
|
||||||
|
slabs_.load(std::memory_order::relaxed));
|
||||||
|
|
||||||
|
if (!slab) [[unlikely]]
|
||||||
|
{
|
||||||
|
detail::deallocateBuffer(buf);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ret = slab->try_assign(buf, slabSize_, itemSize_);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
// Link the new slab. Since we hold the lock, we can just do a
|
||||||
|
// direct assignment here.
|
||||||
|
slabs_.store(slab, std::memory_order::release);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Constructs a slab allocator able to allocate objects of a fixed size
|
/** Constructs a slab allocator able to allocate objects of a fixed size
|
||||||
|
|
||||||
@param count the number of items the slab allocator can allocate; note
|
@param extra Extra bytes per item beyond sizeof(Type).
|
||||||
that a count of 0 is valid and means that the allocator
|
@param minItems Minimum number of items per slab (rounded up to 2MB).
|
||||||
is, effectively, disabled. This can be very useful in some
|
|
||||||
contexts (e.g. when mimimal memory usage is needed) and
|
|
||||||
allows for graceful failure.
|
|
||||||
*/
|
*/
|
||||||
constexpr explicit SlabAllocator(
|
constexpr explicit sized_allocator_t(
|
||||||
std::size_t extra,
|
std::size_t extra,
|
||||||
std::size_t alloc = 0,
|
std::size_t minItems)
|
||||||
std::size_t align = 0)
|
: itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
|
||||||
: itemAlignment_(align ? align : alignof(Type))
|
, slabSize_(boost::alignment::align_up(
|
||||||
, itemSize_(
|
itemSize_ * minItems,
|
||||||
boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
|
detail::pageSize))
|
||||||
, slabSize_(alloc)
|
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
|
||||||
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
|
|
||||||
"ripple::SlabAllocator::SlabAllocator : valid alignment");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SlabAllocator(SlabAllocator const& other) = delete;
|
// Data buffers are intentionally not released. C++ destruction order
|
||||||
SlabAllocator&
|
// does not guarantee that all items allocated from this allocator
|
||||||
operator=(SlabAllocator const& other) = delete;
|
// have been freed by the time this destructor runs.
|
||||||
|
~sized_allocator_t() = default;
|
||||||
|
|
||||||
SlabAllocator(SlabAllocator&& other) = delete;
|
sized_allocator_t(sized_allocator_t const&) = delete;
|
||||||
SlabAllocator&
|
sized_allocator_t&
|
||||||
operator=(SlabAllocator&& other) = delete;
|
operator=(sized_allocator_t const&) = delete;
|
||||||
|
sized_allocator_t(sized_allocator_t&&) = delete;
|
||||||
|
sized_allocator_t&
|
||||||
|
operator=(sized_allocator_t&&) = delete;
|
||||||
|
|
||||||
~SlabAllocator()
|
/** Returns the size of memory blocks returned by this allocator.
|
||||||
{
|
|
||||||
// FIXME: We can't destroy the memory blocks we've allocated, because
|
|
||||||
// we can't be sure that they are not being used. Cleaning the
|
|
||||||
// shutdown process up could make this possible.
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the size of the memory block this allocator returns. */
|
@return Size of each allocated block in bytes.
|
||||||
constexpr std::size_t
|
*/
|
||||||
|
[[nodiscard]] constexpr std::size_t
|
||||||
size() const noexcept
|
size() const noexcept
|
||||||
{
|
{
|
||||||
return itemSize_;
|
return itemSize_;
|
||||||
@@ -217,87 +465,36 @@ public:
|
|||||||
|
|
||||||
/** Returns a suitably aligned pointer, if one is available.
|
/** Returns a suitably aligned pointer, if one is available.
|
||||||
|
|
||||||
@return a pointer to a block of memory from the allocator, or
|
@return Pointer to allocated memory, or nullptr on failure.
|
||||||
nullptr if the allocator can't satisfy this request.
|
|
||||||
|
@note The gnu::malloc attribute is an optimization hint that can
|
||||||
|
be leveraged by GCC and Clang.
|
||||||
*/
|
*/
|
||||||
std::uint8_t*
|
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||||
allocate() noexcept
|
allocate() noexcept
|
||||||
{
|
{
|
||||||
auto slab = slabs_.load();
|
// Fast path: try existing slabs
|
||||||
|
auto ret = fast_allocate();
|
||||||
|
|
||||||
while (slab != nullptr)
|
if (ret == nullptr)
|
||||||
{
|
{
|
||||||
if (auto ret = slab->allocate())
|
// Slow path: need new capacity. Grab the lock to serialize access
|
||||||
return ret;
|
// to this code path because it is expensive. Then re-check in case
|
||||||
|
// another thread added capacity while we waited.
|
||||||
slab = slab->next_;
|
spin_lock(blockLock_);
|
||||||
|
ret = slow_allocate();
|
||||||
|
spin_unlock(blockLock_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No slab can satisfy our request, so we attempt to allocate a new
|
return ret;
|
||||||
// one here:
|
|
||||||
std::size_t size = slabSize_;
|
|
||||||
|
|
||||||
// We want to allocate the memory at a 2 MiB boundary, to make it
|
|
||||||
// possible to use hugepage mappings on Linux:
|
|
||||||
auto buf =
|
|
||||||
boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
if (!buf) [[unlikely]]
|
|
||||||
return nullptr;
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
#if BOOST_OS_LINUX
|
|
||||||
// When allocating large blocks, attempt to leverage Linux's
|
|
||||||
// transparent hugepage support. It is unclear and difficult
|
|
||||||
// to accurately determine if doing this impacts performance
|
|
||||||
// enough to justify using platform-specific tricks.
|
|
||||||
if (size >= megabytes(std::size_t(4)))
|
|
||||||
madvise(buf, size, MADV_HUGEPAGE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// We need to carve out a bit of memory for the slab header
|
|
||||||
// and then align the rest appropriately:
|
|
||||||
auto slabData = reinterpret_cast<void*>(
|
|
||||||
reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
|
|
||||||
auto slabSize = size - sizeof(SlabBlock);
|
|
||||||
|
|
||||||
// This operation is essentially guaranteed not to fail but
|
|
||||||
// let's be careful anyways.
|
|
||||||
if (!boost::alignment::align(
|
|
||||||
itemAlignment_, itemSize_, slabData, slabSize))
|
|
||||||
{
|
|
||||||
boost::alignment::aligned_free(buf);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
slab = new (buf) SlabBlock(
|
|
||||||
slabs_.load(),
|
|
||||||
reinterpret_cast<std::uint8_t*>(slabData),
|
|
||||||
slabSize,
|
|
||||||
itemSize_);
|
|
||||||
|
|
||||||
// Link the new slab
|
|
||||||
while (!slabs_.compare_exchange_weak(
|
|
||||||
slab->next_,
|
|
||||||
slab,
|
|
||||||
std::memory_order_release,
|
|
||||||
std::memory_order_relaxed))
|
|
||||||
{
|
|
||||||
; // Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
return slab->allocate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the memory block to the allocator.
|
/** Returns the memory block to the allocator.
|
||||||
|
|
||||||
@param ptr A pointer to a memory block.
|
@param ptr Pointer to memory block.
|
||||||
@param size If non-zero, a hint as to the size of the block.
|
@return true if the block belonged to this allocator and was freed.
|
||||||
@return true if this memory block belonged to the allocator and has
|
|
||||||
been released; false otherwise.
|
|
||||||
*/
|
*/
|
||||||
bool
|
[[nodiscard]] bool
|
||||||
deallocate(std::uint8_t* ptr) noexcept
|
deallocate(std::uint8_t* ptr) noexcept
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
@@ -305,112 +502,135 @@ public:
|
|||||||
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
|
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
|
||||||
"input");
|
"input");
|
||||||
|
|
||||||
for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next_)
|
auto release = [this](std::uint8_t* buf) {
|
||||||
|
std::uint8_t* expected = nullptr;
|
||||||
|
|
||||||
|
if (buf != nullptr &&
|
||||||
|
!cachedBuffer_.compare_exchange_strong(
|
||||||
|
expected, buf, std::memory_order::release))
|
||||||
|
detail::deallocateBuffer(buf);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||||
|
slab = slab->next_)
|
||||||
{
|
{
|
||||||
if (slab->own(ptr))
|
if (slab->try_deallocate(ptr, slabSize_, release))
|
||||||
{
|
|
||||||
slab->deallocate(ptr);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A collection of slab allocators of various sizes for a given type. */
|
//------------------------------------------------------------------------------
|
||||||
template <typename Type>
|
|
||||||
class SlabAllocatorSet
|
/** Configuration for a single slab allocator.
|
||||||
|
|
||||||
|
@tparam MinItems Minimum number of items per slab (must be > 0).
|
||||||
|
@tparam Extra Extra bytes per item beyond sizeof(Type).
|
||||||
|
*/
|
||||||
|
template <std::size_t MinItems, std::size_t Extra = 0>
|
||||||
|
requires(MinItems > 0)
|
||||||
|
struct config
|
||||||
{
|
{
|
||||||
private:
|
static constexpr std::size_t minItems = MinItems;
|
||||||
// The list of allocators that belong to this set
|
static constexpr std::size_t extra = Extra;
|
||||||
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_;
|
};
|
||||||
|
|
||||||
std::size_t maxSize_ = 0;
|
/** Concept for valid slab configuration types. */
|
||||||
|
template <typename T>
|
||||||
|
concept SlabConfig = requires {
|
||||||
|
{ T::minItems } -> std::convertible_to<std::size_t>;
|
||||||
|
{ T::extra } -> std::convertible_to<std::size_t>;
|
||||||
|
requires T::minItems > 0;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
/** Validate slab configurations at compile time.
|
||||||
class SlabConfig
|
|
||||||
{
|
|
||||||
friend class SlabAllocatorSet;
|
|
||||||
|
|
||||||
private:
|
Checks that configurations produce strictly increasing sizes after
|
||||||
std::size_t extra;
|
alignment. This catches both unsorted configs and configs that
|
||||||
std::size_t alloc;
|
collapse to the same size after alignment.
|
||||||
std::size_t align;
|
|
||||||
|
|
||||||
public:
|
@tparam Type The type used to determine base size.
|
||||||
constexpr SlabConfig(
|
@tparam Align Alignment for allocated blocks.
|
||||||
std::size_t extra_,
|
@tparam Configs Configuration types to validate.
|
||||||
std::size_t alloc_ = 0,
|
@return true if configurations are valid, false otherwise.
|
||||||
std::size_t align_ = alignof(Type))
|
*/
|
||||||
: extra(extra_), alloc(alloc_), align(align_)
|
template <typename Type, std::size_t Align, SlabConfig... Configs>
|
||||||
{
|
consteval bool
|
||||||
}
|
validate_slab_config()
|
||||||
|
{
|
||||||
|
constexpr auto align_up = [](std::size_t n, std::size_t a) {
|
||||||
|
return (n + a - 1) & ~(a - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr SlabAllocatorSet(std::vector<SlabConfig> cfg)
|
constexpr std::array<std::size_t, sizeof...(Configs)> sizes{
|
||||||
{
|
align_up(sizeof(Type) + Configs::extra, Align)...};
|
||||||
// Ensure that the specified allocators are sorted from smallest to
|
|
||||||
// largest by size:
|
|
||||||
std::sort(
|
|
||||||
std::begin(cfg),
|
|
||||||
std::end(cfg),
|
|
||||||
[](SlabConfig const& a, SlabConfig const& b) {
|
|
||||||
return a.extra < b.extra;
|
|
||||||
});
|
|
||||||
|
|
||||||
// We should never have two slabs of the same size
|
for (std::size_t i = 1; i < sizes.size(); ++i)
|
||||||
if (std::adjacent_find(
|
if (sizes[i - 1] >= sizes[i])
|
||||||
std::begin(cfg),
|
return false;
|
||||||
std::end(cfg),
|
|
||||||
[](SlabConfig const& a, SlabConfig const& b) {
|
|
||||||
return a.extra == b.extra;
|
|
||||||
}) != cfg.end())
|
|
||||||
{
|
|
||||||
throw std::runtime_error(
|
|
||||||
"SlabAllocatorSet<" + beast::type_name<Type>() +
|
|
||||||
">: duplicate slab size");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const& c : cfg)
|
return true;
|
||||||
{
|
}
|
||||||
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
|
|
||||||
|
|
||||||
if (a.size() > maxSize_)
|
//------------------------------------------------------------------------------
|
||||||
maxSize_ = a.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
|
/** A collection of slab allocators for different sizes.
|
||||||
SlabAllocatorSet&
|
|
||||||
operator=(SlabAllocatorSet const& other) = delete;
|
|
||||||
|
|
||||||
SlabAllocatorSet(SlabAllocatorSet&& other) = delete;
|
Manages multiple sized_allocator_t instances, each configured for a
|
||||||
SlabAllocatorSet&
|
different block size. Allocations are routed to the smallest allocator
|
||||||
operator=(SlabAllocatorSet&& other) = delete;
|
that can satisfy the request.
|
||||||
|
|
||||||
~SlabAllocatorSet()
|
@tparam Type The type used to determine minimum block size and alignment.
|
||||||
|
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
|
||||||
|
@tparam Configs Configuration types specifying each size class.
|
||||||
|
*/
|
||||||
|
template <typename Type, std::size_t Align, SlabConfig... Configs>
|
||||||
|
requires(
|
||||||
|
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
|
||||||
|
std::has_single_bit(Align) && sizeof...(Configs) > 0 &&
|
||||||
|
validate_slab_config<Type, Align, Configs...>())
|
||||||
|
class aligned_allocator_t
|
||||||
|
{
|
||||||
|
std::array<sized_allocator_t<Type, Align>, sizeof...(Configs)> allocators_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Construct an allocator set. */
|
||||||
|
constexpr aligned_allocator_t()
|
||||||
|
: allocators_{sized_allocator_t<Type, Align>(
|
||||||
|
Configs::extra,
|
||||||
|
Configs::minItems)...}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a suitably aligned pointer, if one is available.
|
~aligned_allocator_t() = default;
|
||||||
|
|
||||||
@param extra The number of extra bytes, above and beyond the size of
|
aligned_allocator_t(aligned_allocator_t const&) = delete;
|
||||||
the object, that should be returned by the allocator.
|
aligned_allocator_t&
|
||||||
|
operator=(aligned_allocator_t const&) = delete;
|
||||||
|
aligned_allocator_t(aligned_allocator_t&&) = delete;
|
||||||
|
aligned_allocator_t&
|
||||||
|
operator=(aligned_allocator_t&&) = delete;
|
||||||
|
|
||||||
@return a pointer to a block of memory, or nullptr if the allocator
|
/** Allocate memory for an object with extra bytes.
|
||||||
can't satisfy this request.
|
|
||||||
|
@param extra Extra bytes needed beyond sizeof(Type).
|
||||||
|
@return Pointer to memory, or nullptr if no suitable allocator
|
||||||
|
or allocation failed.
|
||||||
|
|
||||||
|
@note The gnu::malloc attribute is an optimization hint that can
|
||||||
|
be leveraged by GCC and Clang.
|
||||||
*/
|
*/
|
||||||
std::uint8_t*
|
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||||
allocate(std::size_t extra) noexcept
|
allocate(std::size_t extra) noexcept
|
||||||
{
|
{
|
||||||
if (auto const size = sizeof(Type) + extra; size <= maxSize_)
|
auto const size = sizeof(Type) + extra;
|
||||||
|
|
||||||
|
for (auto& a : allocators_)
|
||||||
{
|
{
|
||||||
for (auto& a : allocators_)
|
if (a.size() >= size)
|
||||||
{
|
return a.allocate();
|
||||||
if (a.size() >= size)
|
|
||||||
return a.allocate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -418,12 +638,10 @@ public:
|
|||||||
|
|
||||||
/** Returns the memory block to the allocator.
|
/** Returns the memory block to the allocator.
|
||||||
|
|
||||||
@param ptr A pointer to a memory block.
|
@param ptr Pointer to memory block.
|
||||||
|
@return true if memory belonged to this set and was freed.
|
||||||
@return true if this memory block belonged to one of the allocators
|
|
||||||
in this set and has been released; false otherwise.
|
|
||||||
*/
|
*/
|
||||||
bool
|
[[nodiscard]] bool
|
||||||
deallocate(std::uint8_t* ptr) noexcept
|
deallocate(std::uint8_t* ptr) noexcept
|
||||||
{
|
{
|
||||||
for (auto& a : allocators_)
|
for (auto& a : allocators_)
|
||||||
@@ -431,11 +649,16 @@ public:
|
|||||||
if (a.deallocate(ptr))
|
if (a.deallocate(ptr))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Alias for aligned_allocator_t with default alignment. */
|
||||||
|
template <typename Type, SlabConfig... Configs>
|
||||||
|
using allocator_t = aligned_allocator_t<Type, alignof(Type), Configs...>;
|
||||||
|
|
||||||
|
} // namespace slab
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
||||||
+420
-345
File diff suppressed because it is too large.
Load diff
@@ -23,6 +23,7 @@
|
|||||||
#include <xrpl/beast/type_name.h>
|
#include <xrpl/beast/type_name.h>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ Rethrow()
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class E, class... Args>
|
template <class E, class... Args>
|
||||||
[[noreturn]] inline void
|
[[noreturn]] constexpr void
|
||||||
Throw(Args&&... args)
|
Throw(Args&&... args)
|
||||||
{
|
{
|
||||||
static_assert(
|
static_assert(
|
||||||
@@ -60,10 +61,15 @@ Throw(Args&&... args)
|
|||||||
"Exception must derive from std::exception.");
|
"Exception must derive from std::exception.");
|
||||||
|
|
||||||
E e(std::forward<Args>(args)...);
|
E e(std::forward<Args>(args)...);
|
||||||
LogThrow(
|
|
||||||
std::string(
|
// Skip logging when evaluated at compile time, so that `Throw` can be
|
||||||
"Throwing exception of type " + beast::type_name<E>() + ": ") +
|
// called from constexpr/consteval functions.
|
||||||
e.what());
|
if (!std::is_constant_evaluated())
|
||||||
|
LogThrow(
|
||||||
|
std::string(
|
||||||
|
"Throwing exception of type " + beast::type_name<E>() + ": ") +
|
||||||
|
e.what());
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+139
-96
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <concepts>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
/** Inform the processor that we are in a tight spin-wait loop.
|
/** Inform the processor that we are in a tight spin-wait loop.
|
||||||
|
|
||||||
Spinlocks caught in tight loops can result in the processor's pipeline
|
Spinlocks caught in tight loops can result in the processor's pipeline
|
||||||
@@ -52,11 +54,130 @@ spin_pause() noexcept
|
|||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
/** @{ */
|
/** Attempt to acquire a spinlock without blocking.
|
||||||
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
|
|
||||||
|
|
||||||
Packed spinlocks allow for tremendously space-efficient lock-sharding
|
@tparam T An unsigned integral type.
|
||||||
but they come at a cost.
|
@param lock The atomic variable used as the lock.
|
||||||
|
@return true if the lock was acquired, false if it was already held.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||||
|
[[nodiscard]] bool
|
||||||
|
spin_try_lock(std::atomic<T>& lock) noexcept
|
||||||
|
{
|
||||||
|
T expected = 0;
|
||||||
|
|
||||||
|
return lock.compare_exchange_strong(
|
||||||
|
expected,
|
||||||
|
std::numeric_limits<T>::max(),
|
||||||
|
std::memory_order::acquire,
|
||||||
|
std::memory_order::relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Acquire a spinlock, blocking until available.
|
||||||
|
|
||||||
|
Uses a TTAS (test-and-test-and-set) pattern to reduce cache coherency
|
||||||
|
traffic during contention.
|
||||||
|
|
||||||
|
@tparam T An unsigned integral type.
|
||||||
|
@param lock The atomic variable used as the lock.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||||
|
void
|
||||||
|
spin_lock(std::atomic<T>& lock) noexcept
|
||||||
|
{
|
||||||
|
T expected = 0;
|
||||||
|
|
||||||
|
while (!lock.compare_exchange_weak(
|
||||||
|
expected,
|
||||||
|
std::numeric_limits<T>::max(),
|
||||||
|
std::memory_order::acquire,
|
||||||
|
std::memory_order::relaxed))
|
||||||
|
{
|
||||||
|
expected = 0;
|
||||||
|
|
||||||
|
while (lock.load(std::memory_order::relaxed) != 0)
|
||||||
|
detail::spin_pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Release a spinlock.
|
||||||
|
|
||||||
|
@tparam T An unsigned integral type.
|
||||||
|
@param lock The atomic variable used as the lock.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||||
|
void
|
||||||
|
spin_unlock(std::atomic<T>& lock) noexcept
|
||||||
|
{
|
||||||
|
lock.store(0, std::memory_order::release);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** A Lockable interface to a spinlock implemented on top of an atomic.
|
||||||
|
|
||||||
|
@tparam T An unsigned integral type.
|
||||||
|
|
||||||
|
@note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||||
|
atomic integer can result in `spinlock` not being able to actually
|
||||||
|
acquire the lock during periods of high contention, because of how
|
||||||
|
the two locks operate: `spinlock` will spin trying to grab all the
|
||||||
|
bits at once, whereas any given `packed_spinlock` will only try to
|
||||||
|
grab one bit at a time. Caveat emptor.
|
||||||
|
|
||||||
|
This class meets the requirements of Lockable:
|
||||||
|
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||||
|
class spinlock
|
||||||
|
{
|
||||||
|
std::atomic<T>& lock_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
spinlock(spinlock const&) = delete;
|
||||||
|
spinlock&
|
||||||
|
operator=(spinlock const&) = delete;
|
||||||
|
|
||||||
|
/** Construct a spinlock handle.
|
||||||
|
|
||||||
|
@param lock The atomic integer to spin against.
|
||||||
|
|
||||||
|
@note For performance reasons, you should strive to have `lock` be
|
||||||
|
on a cacheline by itself.
|
||||||
|
*/
|
||||||
|
explicit spinlock(std::atomic<T>& lock) noexcept : lock_(lock)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool
|
||||||
|
try_lock() noexcept
|
||||||
|
{
|
||||||
|
return spin_try_lock(lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
lock() noexcept
|
||||||
|
{
|
||||||
|
spin_lock(lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
unlock() noexcept
|
||||||
|
{
|
||||||
|
spin_unlock(lock_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** A Lockable interface to a packed spinlock implemented on top of an atomic.
|
||||||
|
|
||||||
|
Packed spinlocks offer tremendous space-efficient lock-sharding but
|
||||||
|
they come at a cost.
|
||||||
|
|
||||||
First, the implementation is necessarily low-level and uses advanced
|
First, the implementation is necessarily low-level and uses advanced
|
||||||
features like memory ordering and highly platform-specific tricks to
|
features like memory ordering and highly platform-specific tricks to
|
||||||
@@ -78,26 +199,19 @@ spin_pause() noexcept
|
|||||||
that it can, usually, outperform spinlocks.
|
that it can, usually, outperform spinlocks.
|
||||||
|
|
||||||
@tparam T An unsigned integral type (e.g. std::uint16_t)
|
@tparam T An unsigned integral type (e.g. std::uint16_t)
|
||||||
*/
|
|
||||||
|
|
||||||
/** A class that grabs a single packed spinlock from an atomic integer.
|
|
||||||
|
|
||||||
This class meets the requirements of Lockable:
|
This class meets the requirements of Lockable:
|
||||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <typename T>
|
||||||
|
requires(
|
||||||
|
std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free &&
|
||||||
|
requires(std::atomic<T>& a, T v) {
|
||||||
|
{ a.fetch_or(v) } -> std::same_as<T>;
|
||||||
|
{ a.fetch_and(v) } -> std::same_as<T>;
|
||||||
|
})
|
||||||
class packed_spinlock
|
class packed_spinlock
|
||||||
{
|
{
|
||||||
// clang-format off
|
|
||||||
static_assert(std::is_unsigned_v<T>);
|
|
||||||
static_assert(std::atomic<T>::is_always_lock_free);
|
|
||||||
static_assert(
|
|
||||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
|
|
||||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
|
|
||||||
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic<T>& bits_;
|
std::atomic<T>& bits_;
|
||||||
T const mask_;
|
T const mask_;
|
||||||
|
|
||||||
@@ -114,7 +228,7 @@ public:
|
|||||||
@note For performance reasons, you should strive to have `lock` be
|
@note For performance reasons, you should strive to have `lock` be
|
||||||
on a cacheline by itself.
|
on a cacheline by itself.
|
||||||
*/
|
*/
|
||||||
packed_spinlock(std::atomic<T>& lock, int index)
|
packed_spinlock(std::atomic<T>& lock, int index) noexcept
|
||||||
: bits_(lock), mask_(static_cast<T>(1) << index)
|
: bits_(lock), mask_(static_cast<T>(1) << index)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
@@ -123,13 +237,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
try_lock()
|
try_lock() noexcept
|
||||||
{
|
{
|
||||||
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
|
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
lock()
|
lock() noexcept
|
||||||
{
|
{
|
||||||
while (!try_lock())
|
while (!try_lock())
|
||||||
{
|
{
|
||||||
@@ -137,89 +251,18 @@ public:
|
|||||||
// serves to help reduce cache coherency traffic during times
|
// serves to help reduce cache coherency traffic during times
|
||||||
// of contention by avoiding writes that would definitely not
|
// of contention by avoiding writes that would definitely not
|
||||||
// result in the lock being acquired.
|
// result in the lock being acquired.
|
||||||
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
|
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
|
||||||
detail::spin_pause();
|
detail::spin_pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
unlock()
|
unlock() noexcept
|
||||||
{
|
{
|
||||||
bits_.fetch_and(~mask_, std::memory_order_release);
|
bits_.fetch_and(~mask_, std::memory_order::release);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A spinlock implemented on top of an atomic integer.
|
|
||||||
|
|
||||||
@note Using `packed_spinlock` and `spinlock` against the same underlying
|
|
||||||
atomic integer can result in `spinlock` not being able to actually
|
|
||||||
acquire the lock during periods of high contention, because of how
|
|
||||||
the two locks operate: `spinlock` will spin trying to grab all the
|
|
||||||
bits at once, whereas any given `packed_spinlock` will only try to
|
|
||||||
grab one bit at a time. Caveat emptor.
|
|
||||||
|
|
||||||
This class meets the requirements of Lockable:
|
|
||||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
|
||||||
*/
|
|
||||||
template <class T>
|
|
||||||
class spinlock
|
|
||||||
{
|
|
||||||
static_assert(std::is_unsigned_v<T>);
|
|
||||||
static_assert(std::atomic<T>::is_always_lock_free);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic<T>& lock_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
spinlock(spinlock const&) = delete;
|
|
||||||
spinlock&
|
|
||||||
operator=(spinlock const&) = delete;
|
|
||||||
|
|
||||||
/** Grabs the
|
|
||||||
|
|
||||||
@param lock The atomic integer to spin against.
|
|
||||||
|
|
||||||
@note For performance reasons, you should strive to have `lock` be
|
|
||||||
on a cacheline by itself.
|
|
||||||
*/
|
|
||||||
spinlock(std::atomic<T>& lock) : lock_(lock)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool
|
|
||||||
try_lock()
|
|
||||||
{
|
|
||||||
T expected = 0;
|
|
||||||
|
|
||||||
return lock_.compare_exchange_weak(
|
|
||||||
expected,
|
|
||||||
std::numeric_limits<T>::max(),
|
|
||||||
std::memory_order_acquire,
|
|
||||||
std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
lock()
|
|
||||||
{
|
|
||||||
while (!try_lock())
|
|
||||||
{
|
|
||||||
// The use of relaxed memory ordering here is intentional and
|
|
||||||
// serves to help reduce cache coherency traffic during times
|
|
||||||
// of contention by avoiding writes that would definitely not
|
|
||||||
// result in the lock being acquired.
|
|
||||||
while (lock_.load(std::memory_order_relaxed) != 0)
|
|
||||||
detail::spin_pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
unlock()
|
|
||||||
{
|
|
||||||
lock_.store(0, std::memory_order_release);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @} */
|
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -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
|
#ifndef HOOKMISC_INCLUDED
|
||||||
#define HOOKMISC_INCLUDED 1
|
#define HOOKMISC_INCLUDED 1
|
||||||
#include <xrpl/basics/base_uint.h>
|
|
||||||
namespace ripple {
|
|
||||||
// RH TODO: there's definitely a mucher nicer way to do this, but it involves
|
|
||||||
// modifying the base_uint class and we don't want to do that yet.
|
|
||||||
|
|
||||||
static const std::array<ripple::uint256, 256> UINT256_BIT = {
|
#include <xrpl/basics/base_uint.h>
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001"),
|
#include <array>
|
||||||
ripple::uint256(
|
#include <cstdint>
|
||||||
"0000000000000000000000000000000000000000000000000000000000000002"),
|
|
||||||
ripple::uint256(
|
namespace ripple {
|
||||||
"0000000000000000000000000000000000000000000000000000000000000004"),
|
|
||||||
ripple::uint256(
|
/** UINT256_BIT[i] is the 256-bit value with only bit i set (LSB is bit 0).
|
||||||
"0000000000000000000000000000000000000000000000000000000000000008"),
|
|
||||||
ripple::uint256(
|
Used to encode and test the per-transaction-type bits of the HookOn and
|
||||||
"0000000000000000000000000000000000000000000000000000000000000010"),
|
HookCanEmit fields. Built at compile time.
|
||||||
ripple::uint256(
|
*/
|
||||||
"0000000000000000000000000000000000000000000000000000000000000020"),
|
inline constexpr auto UINT256_BIT = []() consteval {
|
||||||
ripple::uint256(
|
std::array<ripple::uint256, 256> table{};
|
||||||
"0000000000000000000000000000000000000000000000000000000000000040"),
|
|
||||||
ripple::uint256(
|
for (int i = 0; i < 256; ++i)
|
||||||
"0000000000000000000000000000000000000000000000000000000000000080"),
|
{
|
||||||
ripple::uint256(
|
std::array<std::uint8_t, 32> bytes{};
|
||||||
"0000000000000000000000000000000000000000000000000000000000000100"),
|
bytes[31 - (i / 8)] = 1 << (i % 8);
|
||||||
ripple::uint256(
|
table[i] = ripple::uint256(bytes);
|
||||||
"0000000000000000000000000000000000000000000000000000000000000200"),
|
}
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000400"),
|
return table;
|
||||||
ripple::uint256(
|
}();
|
||||||
"0000000000000000000000000000000000000000000000000000000000000800"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000001000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000002000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000004000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000008000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000010000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000020000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000040000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000080000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000100000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000200000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000400000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000800000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000001000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000002000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000004000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000008000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000010000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000020000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000040000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000080000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000100000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000200000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000400000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000000800000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000001000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000002000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000004000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000008000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000010000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000020000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000040000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000080000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000100000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000200000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000400000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000000800000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000001000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000002000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000004000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000008000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000010000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000020000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000040000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000080000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000100000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000200000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000400000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000000800000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000001000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000002000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000004000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000008000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000010000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000020000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000040000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000080000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000100000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000200000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000400000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000000800000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000001000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000002000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000004000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000008000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000010000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000020000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000040000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000080000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000100000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000200000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000400000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000000800000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000001000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000002000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000004000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000008000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000010000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000020000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000040000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000080000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000100000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000200000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000400000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000000800000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000001000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000002000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000004000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000008000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000010000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000020000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000040000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000080000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000100000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000200000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000400000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000000800000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000001000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000002000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000004000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000008000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000010000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000020000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000040000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000080000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000100000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000200000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000400000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000000800000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000001000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000002000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000004000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000008000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000010000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000020000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000040000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000080000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000100000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000200000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000400000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000000800000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000001000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000002000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000004000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000008000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000010000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000020000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000040000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000080000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000100000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000200000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000400000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000000800000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000001000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000002000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000004000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000008000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000010000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000020000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000040000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000080000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000100000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000200000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000400000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000000800000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000001000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000002000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000004000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000008000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000010000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000020000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000040000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000080000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000100000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000200000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000400000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000000800000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000001000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000002000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000004000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000008000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000010000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000020000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000040000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000080000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000100000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000200000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000400000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000000800000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000001000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000002000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000004000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000008000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000010000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000020000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000040000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000080000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000100000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000200000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000400000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000000800000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000001000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000002000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000004000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000008000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000010000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000020000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000040000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000080000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000100000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000200000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000400000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000000800000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000001000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000002000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000004000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000008000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000010000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000020000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000040000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000080000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000100000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000200000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000400000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000000800000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000001000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000002000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000004000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000008000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000010000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000020000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000040000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000080000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000100000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000200000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000400000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000000800000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000001000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000002000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000004000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000008000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000010000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000020000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000040000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000080000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000100000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000200000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000400000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0000800000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0001000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0002000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0004000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0008000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0010000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0020000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0040000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0080000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0200000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0400000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"0800000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"1000000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"2000000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"4000000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
ripple::uint256(
|
|
||||||
"8000000000000000000000000000000000000000000000000000000000000000")};
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <xrpl/protocol/KeyType.h>
|
#include <xrpl/protocol/KeyType.h>
|
||||||
#include <xrpl/protocol/STExchange.h>
|
#include <xrpl/protocol/STExchange.h>
|
||||||
#include <xrpl/protocol/UintTypes.h>
|
#include <xrpl/protocol/UintTypes.h>
|
||||||
|
#include <xrpl/protocol/detail/KeyBase.h>
|
||||||
#include <xrpl/protocol/json_get_or_throw.h>
|
#include <xrpl/protocol/json_get_or_throw.h>
|
||||||
#include <xrpl/protocol/tokens.h>
|
#include <xrpl/protocol/tokens.h>
|
||||||
|
|
||||||
@@ -36,6 +37,26 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
/** Returns the type of public key.
|
||||||
|
|
||||||
|
@return std::nullopt If the public key does not
|
||||||
|
represent a known type.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] inline std::optional<KeyType>
|
||||||
|
publicKeyType(Slice const& slice)
|
||||||
|
{
|
||||||
|
if (slice.size() == 33)
|
||||||
|
{
|
||||||
|
if (slice[0] == 0xED)
|
||||||
|
return KeyType::ed25519;
|
||||||
|
|
||||||
|
if (slice[0] == 0x02 || slice[0] == 0x03)
|
||||||
|
return KeyType::secp256k1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
/** A public key.
|
/** A public key.
|
||||||
|
|
||||||
Public keys are used in the public-key cryptography
|
Public keys are used in the public-key cryptography
|
||||||
@@ -58,77 +79,47 @@ namespace ripple {
|
|||||||
prefix constant 0xED, followed by 32 bytes of
|
prefix constant 0xED, followed by 32 bytes of
|
||||||
public key data.
|
public key data.
|
||||||
*/
|
*/
|
||||||
class PublicKey
|
class PublicKey : public detail::KeyBase<PublicKey, 33>
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
// All the constructed public keys are valid, non-empty and contain 33
|
|
||||||
// bytes of data.
|
|
||||||
static constexpr std::size_t size_ = 33;
|
|
||||||
std::uint8_t buf_[size_]; // should be large enough
|
|
||||||
|
|
||||||
public:
|
|
||||||
using const_iterator = std::uint8_t const*;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PublicKey() = delete;
|
PublicKey() = delete;
|
||||||
|
|
||||||
PublicKey(PublicKey const& other);
|
PublicKey(PublicKey const&) = default;
|
||||||
PublicKey&
|
PublicKey&
|
||||||
operator=(PublicKey const& other);
|
operator=(PublicKey const&) = default;
|
||||||
|
|
||||||
/** Create a public key.
|
explicit PublicKey(Slice const& slice) noexcept
|
||||||
|
|
||||||
Preconditions:
|
|
||||||
publicKeyType(slice) != std::nullopt
|
|
||||||
*/
|
|
||||||
explicit PublicKey(Slice const& slice);
|
|
||||||
|
|
||||||
std::uint8_t const*
|
|
||||||
data() const noexcept
|
|
||||||
{
|
{
|
||||||
return buf_;
|
if (slice.size() < buf_.size())
|
||||||
|
LogicError("PublicKey::PublicKey: undersized buffer");
|
||||||
|
|
||||||
|
if (!publicKeyType(slice))
|
||||||
|
LogicError("PublicKey::PublicKey: invalid type");
|
||||||
|
|
||||||
|
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
explicit PublicKey(value_t const& data) noexcept
|
||||||
size() const noexcept
|
: PublicKey(makeSlice(data))
|
||||||
{
|
{
|
||||||
return size_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const_iterator
|
[[nodiscard]] Slice
|
||||||
begin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cbegin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
end() const noexcept
|
|
||||||
{
|
|
||||||
return buf_ + size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cend() const noexcept
|
|
||||||
{
|
|
||||||
return buf_ + size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Slice
|
|
||||||
slice() const noexcept
|
slice() const noexcept
|
||||||
{
|
{
|
||||||
return {buf_, size_};
|
return {buf_.data(), buf_.size()};
|
||||||
}
|
}
|
||||||
|
|
||||||
operator Slice() const noexcept
|
operator Slice() const noexcept
|
||||||
{
|
{
|
||||||
return slice();
|
return slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto
|
||||||
|
operator<=>(PublicKey const& rhs) const
|
||||||
|
{
|
||||||
|
return buf_ <=> rhs.buf_;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Print the public key to a stream.
|
/** Print the public key to a stream.
|
||||||
@@ -136,22 +127,6 @@ public:
|
|||||||
std::ostream&
|
std::ostream&
|
||||||
operator<<(std::ostream& os, PublicKey const& pk);
|
operator<<(std::ostream& os, PublicKey const& pk);
|
||||||
|
|
||||||
inline bool
|
|
||||||
operator==(PublicKey const& lhs, PublicKey const& rhs)
|
|
||||||
{
|
|
||||||
return std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool
|
|
||||||
operator<(PublicKey const& lhs, PublicKey const& rhs)
|
|
||||||
{
|
|
||||||
return std::lexicographical_compare(
|
|
||||||
lhs.data(),
|
|
||||||
lhs.data() + lhs.size(),
|
|
||||||
rhs.data(),
|
|
||||||
rhs.data() + rhs.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Hasher>
|
template <class Hasher>
|
||||||
void
|
void
|
||||||
hash_append(Hasher& h, PublicKey const& pk)
|
hash_append(Hasher& h, PublicKey const& pk)
|
||||||
@@ -221,22 +196,6 @@ enum class ECDSACanonicality { canonical, fullyCanonical };
|
|||||||
std::optional<ECDSACanonicality>
|
std::optional<ECDSACanonicality>
|
||||||
ecdsaCanonicality(Slice const& sig);
|
ecdsaCanonicality(Slice const& sig);
|
||||||
|
|
||||||
/** Returns the type of public key.
|
|
||||||
|
|
||||||
@return std::nullopt If the public key does not
|
|
||||||
represent a known type.
|
|
||||||
*/
|
|
||||||
/** @{ */
|
|
||||||
[[nodiscard]] std::optional<KeyType>
|
|
||||||
publicKeyType(Slice const& slice);
|
|
||||||
|
|
||||||
[[nodiscard]] inline std::optional<KeyType>
|
|
||||||
publicKeyType(PublicKey const& publicKey)
|
|
||||||
{
|
|
||||||
return publicKeyType(publicKey.slice());
|
|
||||||
}
|
|
||||||
/** @} */
|
|
||||||
|
|
||||||
/** Verify a secp256k1 signature on the digest of a message. */
|
/** Verify a secp256k1 signature on the digest of a message. */
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
verifyDigest(
|
verifyDigest(
|
||||||
|
|||||||
@@ -25,90 +25,39 @@
|
|||||||
#include <xrpl/protocol/KeyType.h>
|
#include <xrpl/protocol/KeyType.h>
|
||||||
#include <xrpl/protocol/PublicKey.h>
|
#include <xrpl/protocol/PublicKey.h>
|
||||||
#include <xrpl/protocol/Seed.h>
|
#include <xrpl/protocol/Seed.h>
|
||||||
|
#include <xrpl/protocol/detail/KeyBase.h>
|
||||||
#include <xrpl/protocol/tokens.h>
|
#include <xrpl/protocol/tokens.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
/** A secret key. */
|
/** A secret key. */
|
||||||
class SecretKey
|
class SecretKey : public detail::KeyBase<SecretKey, 32>
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
std::uint8_t buf_[32];
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using const_iterator = std::uint8_t const*;
|
|
||||||
|
|
||||||
SecretKey() = delete;
|
SecretKey() = delete;
|
||||||
|
|
||||||
SecretKey(SecretKey const&) = default;
|
SecretKey(SecretKey const&) = default;
|
||||||
SecretKey&
|
SecretKey&
|
||||||
operator=(SecretKey const&) = default;
|
operator=(SecretKey const&) = default;
|
||||||
|
|
||||||
|
/** Destroy the secret key. This will try to securely erase the buffer. */
|
||||||
~SecretKey();
|
~SecretKey();
|
||||||
|
|
||||||
SecretKey(std::array<std::uint8_t, 32> const& data);
|
SecretKey(Slice const& slice)
|
||||||
SecretKey(Slice const& slice);
|
|
||||||
|
|
||||||
std::uint8_t const*
|
|
||||||
data() const
|
|
||||||
{
|
{
|
||||||
return buf_;
|
if (slice.size() != buf_.size())
|
||||||
|
LogicError("SecretKey::SecretKey: invalid size");
|
||||||
|
|
||||||
|
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
|
||||||
size() const
|
|
||||||
{
|
{
|
||||||
return sizeof(buf_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert the secret key to a hexadecimal string.
|
|
||||||
|
|
||||||
@note The operator<< function is deliberately omitted
|
|
||||||
to avoid accidental exposure of secret key material.
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
to_string() const;
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
begin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cbegin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
end() const noexcept
|
|
||||||
{
|
|
||||||
return buf_ + sizeof(buf_);
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cend() const noexcept
|
|
||||||
{
|
|
||||||
return buf_ + sizeof(buf_);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool
|
|
||||||
operator==(SecretKey const& lhs, SecretKey const& rhs)
|
|
||||||
{
|
|
||||||
return lhs.size() == rhs.size() &&
|
|
||||||
std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool
|
|
||||||
operator!=(SecretKey const& lhs, SecretKey const& rhs)
|
|
||||||
{
|
|
||||||
return !(lhs == rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Parse a secret key */
|
/** Parse a secret key */
|
||||||
|
|||||||
@@ -22,72 +22,37 @@
|
|||||||
|
|
||||||
#include <xrpl/basics/Slice.h>
|
#include <xrpl/basics/Slice.h>
|
||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
|
#include <xrpl/protocol/detail/KeyBase.h>
|
||||||
#include <xrpl/protocol/tokens.h>
|
#include <xrpl/protocol/tokens.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
/** Seeds are used to generate deterministic secret keys. */
|
/** Seeds are used to generate deterministic secret keys. */
|
||||||
class Seed
|
class Seed : public detail::KeyBase<Seed, 16>
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
std::array<uint8_t, 16> buf_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using const_iterator = std::array<uint8_t, 16>::const_iterator;
|
|
||||||
|
|
||||||
Seed() = delete;
|
Seed() = delete;
|
||||||
|
|
||||||
Seed(Seed const&) = default;
|
Seed(Seed const&) = default;
|
||||||
Seed&
|
Seed&
|
||||||
operator=(Seed const&) = default;
|
operator=(Seed const&) = default;
|
||||||
|
|
||||||
/** Destroy the seed.
|
/** Destroy the seed. This will attempt to securely erase the buffer. */
|
||||||
The buffer will first be securely erased.
|
|
||||||
*/
|
|
||||||
~Seed();
|
~Seed();
|
||||||
|
|
||||||
/** Construct a seed */
|
explicit Seed(Slice const& slice)
|
||||||
/** @{ */
|
|
||||||
explicit Seed(Slice const& slice);
|
|
||||||
explicit Seed(uint128 const& seed);
|
|
||||||
/** @} */
|
|
||||||
|
|
||||||
std::uint8_t const*
|
|
||||||
data() const
|
|
||||||
{
|
{
|
||||||
return buf_.data();
|
if (slice.size() != buf_.size())
|
||||||
|
LogicError("Seed::Seed: invalid size");
|
||||||
|
|
||||||
|
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
explicit Seed(span_t seed) noexcept : KeyBase(seed)
|
||||||
size() const
|
|
||||||
{
|
{
|
||||||
return buf_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
begin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cbegin() const noexcept
|
|
||||||
{
|
|
||||||
return buf_.cbegin();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
end() const noexcept
|
|
||||||
{
|
|
||||||
return buf_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cend() const noexcept
|
|
||||||
{
|
|
||||||
return buf_.cend();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,18 +24,14 @@
|
|||||||
#include <xrpl/protocol/XRPAmount.h>
|
#include <xrpl/protocol/XRPAmount.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
// Various protocol and system specific constant globals.
|
// Various protocol and system specific constant globals.
|
||||||
|
|
||||||
/* The name of the system. */
|
/* The name of the system. */
|
||||||
static inline std::string const&
|
inline constexpr std::string_view systemName = "xahau";
|
||||||
systemName()
|
|
||||||
{
|
|
||||||
static std::string const name = "xahau";
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Configure the native currency. */
|
/** Configure the native currency. */
|
||||||
|
|
||||||
@@ -58,12 +54,7 @@ isLegalAmountSigned(XRPAmount const& amount)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The currency code for the native currency. */
|
/* The currency code for the native currency. */
|
||||||
static inline std::string const&
|
inline constexpr std::string_view systemCurrencyCode = "XAH";
|
||||||
systemCurrencyCode()
|
|
||||||
{
|
|
||||||
static std::string const code = "XAH";
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The XRP ledger network's earliest allowed sequence */
|
/** The XRP ledger network's earliest allowed sequence */
|
||||||
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
|
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
|
||||||
|
|||||||
@@ -24,84 +24,166 @@
|
|||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/beast/utility/Zero.h>
|
#include <xrpl/beast/utility/Zero.h>
|
||||||
#include <xrpl/protocol/AccountID.h>
|
#include <xrpl/protocol/AccountID.h>
|
||||||
|
#include <xrpl/protocol/SystemParameters.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
class CurrencyTag
|
using LedgerHash = uint256;
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit CurrencyTag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DirectoryTag
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit DirectoryTag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NodeIDTag
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit NodeIDTag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
/** Directory is an index into the directory of offer books.
|
/** Directory is an index into the directory of offer books.
|
||||||
The last 64 bits of this are the quality. */
|
The last 64 bits of this are the quality. */
|
||||||
using Directory = base_uint<256, detail::DirectoryTag>;
|
using Directory = base_uint<256, struct DirectoryTag>;
|
||||||
|
|
||||||
/** Currency is a hash representing a specific currency. */
|
/** Currency is a hash representing a specific currency. */
|
||||||
using Currency = base_uint<160, detail::CurrencyTag>;
|
using Currency = base_uint<160, struct CurrencyTag>;
|
||||||
|
|
||||||
/** NodeID is a 160-bit hash representing one node. */
|
/** NodeID is a 160-bit hash representing one node. */
|
||||||
using NodeID = base_uint<160, detail::NodeIDTag>;
|
using NodeID = base_uint<160, struct NodeIDTag>;
|
||||||
|
|
||||||
/** MPTID is a 192-bit value representing MPT Issuance ID,
|
/** A Multi-Purpose Token Issuance ID,
|
||||||
* which is a concatenation of a 32-bit sequence (big endian)
|
|
||||||
* and a 160-bit account */
|
The ID is the a concatenation of a 32-bit sequence number,
|
||||||
|
in big endian, and a 160-bit account.
|
||||||
|
|
||||||
|
@note This type is, unfortunately, untagged because the authors
|
||||||
|
of the original code used a deserialization APIs that did
|
||||||
|
not support tags. It should be fixed.
|
||||||
|
*/
|
||||||
using MPTID = base_uint<192>;
|
using MPTID = base_uint<192>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// A table mapping which characters we are willing to allow in the ASCII
|
||||||
|
// representation of a three-letter currency code.
|
||||||
|
inline constexpr auto validIsoChars = []() consteval {
|
||||||
|
std::string_view isoCharSet =
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"0123456789"
|
||||||
|
"<>(){}[]|?!@#$%^&*";
|
||||||
|
|
||||||
|
std::array<bool, 256> table{};
|
||||||
|
|
||||||
|
for (unsigned char c : isoCharSet)
|
||||||
|
table[c] = true;
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Determines if the given code is composed entirely of letters valid in
|
||||||
|
// ISO 4217 codes.
|
||||||
|
[[nodiscard]] inline constexpr bool
|
||||||
|
isIsoCode(std::string_view code) noexcept
|
||||||
|
{
|
||||||
|
return std::all_of(code.begin(), code.end(), [](char c) {
|
||||||
|
return validIsoChars[static_cast<unsigned char>(c)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The location (in bytes) of the 3 digit currency inside a 160-bit value
|
||||||
|
inline constexpr std::size_t isoCodeOffset = 12;
|
||||||
|
|
||||||
|
// The length of an ISO-4217 like code
|
||||||
|
inline constexpr std::size_t isoCodeLength = 3;
|
||||||
|
|
||||||
|
inline constexpr Currency isoMaskBits = ~Currency(0xFFFFFF0000000000);
|
||||||
|
|
||||||
|
// The special currency identifier for the system currency: all-zero
|
||||||
|
inline constexpr Currency xrpCurrency{0x0000000000000000};
|
||||||
|
|
||||||
|
// The special currency identifier meaning "no currency"
|
||||||
|
inline constexpr Currency noCurrency{0x0000000000000001};
|
||||||
|
|
||||||
|
// The system currency identifier in "ISO4217 format" which is reserved.
|
||||||
|
// We derive this value dynamically from the system currency code.
|
||||||
|
inline constexpr Currency badCurrency = []() consteval {
|
||||||
|
if (systemCurrencyCode.size() != isoCodeLength)
|
||||||
|
throw "Incorrect systemCurrencyCode size (must be 3 digits)";
|
||||||
|
|
||||||
|
std::array<std::uint8_t, Currency::bytes> bytes{};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < isoCodeLength; ++i)
|
||||||
|
bytes[isoCodeOffset + i] =
|
||||||
|
static_cast<std::uint8_t>(systemCurrencyCode[i]);
|
||||||
|
|
||||||
|
return Currency(bytes);
|
||||||
|
}();
|
||||||
|
|
||||||
|
// We take advantage of the fact that an ASCII value in the [a-z] range
|
||||||
|
// transforms into the equivalent character in the [A-Z] range when you
|
||||||
|
// AND it with 0xDF, so we can compare against all possible variants of
|
||||||
|
// "XAH" in one go. Every byte outside the ISO code position is kept as-is
|
||||||
|
// by the mask, so a currency is only "bad" when those bytes are all zero
|
||||||
|
// (i.e. the value is exactly one of the case variants of the code).
|
||||||
|
inline constexpr Currency badCurrencyCodeMask =
|
||||||
|
isoMaskBits | Currency{0xDFDFDF0000000000};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
/** XRP currency. */
|
/** XRP currency. */
|
||||||
Currency const&
|
[[nodiscard]] constexpr Currency const&
|
||||||
xrpCurrency();
|
xrpCurrency() noexcept
|
||||||
|
{
|
||||||
|
return detail::xrpCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool
|
||||||
|
isXRP(Currency const& c) noexcept
|
||||||
|
{
|
||||||
|
return c == detail::xrpCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
/** A placeholder for empty currencies. */
|
/** A placeholder for empty currencies. */
|
||||||
Currency const&
|
[[nodiscard]] constexpr Currency const&
|
||||||
noCurrency();
|
noCurrency() noexcept
|
||||||
|
{
|
||||||
|
return detail::noCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
/** We deliberately disallow the currency that looks like "XAH" because too
|
/** We deliberately disallow the currency that looks like "XAH" because too
|
||||||
many people were using it instead of the correct XAH currency. */
|
many people were using it instead of the correct XAH currency. */
|
||||||
Currency const&
|
[[nodiscard]] constexpr Currency const&
|
||||||
badCurrency();
|
badCurrency() noexcept
|
||||||
|
|
||||||
inline bool
|
|
||||||
isXRP(Currency const& c)
|
|
||||||
{
|
{
|
||||||
return c == beast::zero;
|
return detail::badCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
[[nodiscard]] constexpr bool
|
||||||
isBadCurrency(Currency const& c)
|
isBadCurrency(Currency const& c) noexcept
|
||||||
{
|
{
|
||||||
static const std::set<Currency> badCurrencies{
|
// We take advantage of the fact that an ASCII value in the [a-z] range
|
||||||
Currency(0x7861680000000000), // xah
|
// transforms into the equivalent character in the [A-Z] range when you
|
||||||
Currency(0x7861480000000000), // xaH
|
// AND it with 0xDF, so we can compare against all possible variants of
|
||||||
Currency(0x7841680000000000), // xAh
|
// the bad currency code in one go:
|
||||||
Currency(0x7841480000000000), // xAH
|
return (c & detail::badCurrencyCodeMask) == badCurrency();
|
||||||
Currency(0x5861680000000000), // Xah
|
|
||||||
Currency(0x5861480000000000), // XaH
|
|
||||||
Currency(0x5841680000000000), // XAh
|
|
||||||
Currency(0x5841480000000000) // XAH
|
|
||||||
};
|
|
||||||
|
|
||||||
return badCurrencies.find(c) != badCurrencies.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns "", "XAH", or three letter ISO code. */
|
/** Returns "", "XAH", three letter ISO code or the hex representation. */
|
||||||
std::string
|
[[nodiscard]] inline std::string
|
||||||
to_string(Currency const& c);
|
to_string(Currency const& currency)
|
||||||
|
{
|
||||||
|
if (currency == xrpCurrency())
|
||||||
|
return std::string{systemCurrencyCode};
|
||||||
|
|
||||||
|
if (currency == noCurrency())
|
||||||
|
return "1";
|
||||||
|
|
||||||
|
if ((currency & detail::isoMaskBits) == beast::zero)
|
||||||
|
{
|
||||||
|
std::string_view const iso(
|
||||||
|
reinterpret_cast<char const*>(currency.data()) +
|
||||||
|
detail::isoCodeOffset,
|
||||||
|
detail::isoCodeLength);
|
||||||
|
|
||||||
|
// Specifying the system currency code using ISO-style representation
|
||||||
|
// is not allowed. Note that the check is case-sensitive; yet another
|
||||||
|
// instance of legacy code smell.
|
||||||
|
if (detail::isIsoCode(iso) && iso != systemCurrencyCode)
|
||||||
|
return std::string{iso};
|
||||||
|
}
|
||||||
|
|
||||||
|
return strHex(currency);
|
||||||
|
}
|
||||||
|
|
||||||
/** Tries to convert a string to a Currency, returns true on success.
|
/** Tries to convert a string to a Currency, returns true on success.
|
||||||
|
|
||||||
@@ -110,8 +192,29 @@ to_string(Currency const& c);
|
|||||||
will require very careful checking everywhere and may mean having
|
will require very careful checking everywhere and may mean having
|
||||||
to rewrite some unit test code.
|
to rewrite some unit test code.
|
||||||
*/
|
*/
|
||||||
bool
|
[[nodiscard]] constexpr bool
|
||||||
to_currency(Currency&, std::string const&);
|
to_currency(Currency& currency, std::string_view code) noexcept
|
||||||
|
{
|
||||||
|
if (code.empty() || code == systemCurrencyCode)
|
||||||
|
{
|
||||||
|
currency = xrpCurrency();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ISO-4217-like 3-digit character codes.
|
||||||
|
if (code.size() != detail::isoCodeLength)
|
||||||
|
return currency.parseHex(code);
|
||||||
|
|
||||||
|
if (!detail::isIsoCode(code))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
currency = beast::zero;
|
||||||
|
|
||||||
|
std::copy_n(
|
||||||
|
code.data(), code.size(), currency.begin() + detail::isoCodeOffset);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
|
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
|
||||||
|
|
||||||
@@ -119,8 +222,14 @@ to_currency(Currency&, std::string const&);
|
|||||||
unfortunate; changing this will require very careful checking
|
unfortunate; changing this will require very careful checking
|
||||||
everywhere and may mean having to rewrite some unit test code.
|
everywhere and may mean having to rewrite some unit test code.
|
||||||
*/
|
*/
|
||||||
Currency
|
[[nodiscard]] constexpr Currency
|
||||||
to_currency(std::string const&);
|
to_currency(std::string_view code) noexcept
|
||||||
|
{
|
||||||
|
if (Currency currency; to_currency(currency, code))
|
||||||
|
return currency;
|
||||||
|
|
||||||
|
return noCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
inline std::ostream&
|
inline std::ostream&
|
||||||
operator<<(std::ostream& os, Currency const& x)
|
operator<<(std::ostream& os, Currency const& x)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -24,11 +24,21 @@
|
|||||||
/**
|
/**
|
||||||
* TRANSACTION(tag, value, name, fields)
|
* TRANSACTION(tag, value, name, fields)
|
||||||
*
|
*
|
||||||
|
* To ease maintenance, you may replace any unneeded values with "..."
|
||||||
|
* e.g. #define TRANSACTION(tag, value, name, ...)
|
||||||
|
*
|
||||||
* You must define a transactor class in the `ripple` namespace named `name`,
|
* You must define a transactor class in the `ripple` namespace named `name`,
|
||||||
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
|
* and include its header alongside the TRANSACTOR definition using this
|
||||||
|
* format:
|
||||||
|
* #if TRANSACTION_INCLUDE
|
||||||
|
* # include <xrpld/app/tx/detail/HEADER.h>
|
||||||
|
* #endif
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** This transaction type executes a payment. */
|
/** This transaction type executes a payment. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Payment.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttPAYMENT, 0, Payment, ({
|
TRANSACTION(ttPAYMENT, 0, Payment, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||||
@@ -41,6 +51,9 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates an escrow object. */
|
/** This transaction type creates an escrow object. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Escrow.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED},
|
{sfAmount, soeREQUIRED},
|
||||||
@@ -62,6 +75,9 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
|
|||||||
|
|
||||||
|
|
||||||
/** This transaction type adjusts various account settings. */
|
/** This transaction type adjusts various account settings. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetAccount.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||||
{sfEmailHash, soeOPTIONAL},
|
{sfEmailHash, soeOPTIONAL},
|
||||||
{sfWalletLocator, soeOPTIONAL},
|
{sfWalletLocator, soeOPTIONAL},
|
||||||
@@ -85,6 +101,9 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type sets or clears an account's "regular key". */
|
/** This transaction type sets or clears an account's "regular key". */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
||||||
{sfRegularKey, soeOPTIONAL},
|
{sfRegularKey, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
@@ -92,6 +111,9 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
|||||||
// 6 deprecated
|
// 6 deprecated
|
||||||
|
|
||||||
/** This transaction type creates an offer to trade one asset for another. */
|
/** This transaction type creates an offer to trade one asset for another. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CreateOffer.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||||
{sfTakerPays, soeREQUIRED},
|
{sfTakerPays, soeREQUIRED},
|
||||||
{sfTakerGets, soeREQUIRED},
|
{sfTakerGets, soeREQUIRED},
|
||||||
@@ -101,6 +123,9 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CancelOffer.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
||||||
{sfOfferSequence, soeOPTIONAL},
|
{sfOfferSequence, soeOPTIONAL},
|
||||||
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
|
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||||
@@ -109,6 +134,9 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
|||||||
// 9 deprecated
|
// 9 deprecated
|
||||||
|
|
||||||
/** This transaction type creates a new set of tickets. */
|
/** This transaction type creates a new set of tickets. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CreateTicket.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
||||||
{sfTicketCount, soeREQUIRED},
|
{sfTicketCount, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
@@ -118,12 +146,18 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
|||||||
/** This transaction type modifies the signer list associated with an account. */
|
/** This transaction type modifies the signer list associated with an account. */
|
||||||
// The SignerEntries are optional because a SignerList is deleted by
|
// The SignerEntries are optional because a SignerList is deleted by
|
||||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetSignerList.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
|
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
|
||||||
{sfSignerQuorum, soeREQUIRED},
|
{sfSignerQuorum, soeREQUIRED},
|
||||||
{sfSignerEntries, soeOPTIONAL},
|
{sfSignerEntries, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/PayChan.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
|
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED},
|
{sfAmount, soeREQUIRED},
|
||||||
@@ -151,6 +185,9 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates a new check. */
|
/** This transaction type creates a new check. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CreateCheck.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfSendMax, soeREQUIRED},
|
{sfSendMax, soeREQUIRED},
|
||||||
@@ -160,6 +197,9 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type cashes an existing check. */
|
/** This transaction type cashes an existing check. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CashCheck.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
||||||
{sfCheckID, soeREQUIRED},
|
{sfCheckID, soeREQUIRED},
|
||||||
{sfAmount, soeOPTIONAL},
|
{sfAmount, soeOPTIONAL},
|
||||||
@@ -167,11 +207,17 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type cancels an existing check. */
|
/** This transaction type cancels an existing check. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CancelCheck.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
|
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
|
||||||
{sfCheckID, soeREQUIRED},
|
{sfCheckID, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type grants or revokes authorization to transfer funds. */
|
/** This transaction type grants or revokes authorization to transfer funds. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
||||||
{sfAuthorize, soeOPTIONAL},
|
{sfAuthorize, soeOPTIONAL},
|
||||||
{sfUnauthorize, soeOPTIONAL},
|
{sfUnauthorize, soeOPTIONAL},
|
||||||
@@ -180,6 +226,9 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type modifies a trustline between two accounts. */
|
/** This transaction type modifies a trustline between two accounts. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetTrust.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
||||||
{sfLimitAmount, soeOPTIONAL},
|
{sfLimitAmount, soeOPTIONAL},
|
||||||
{sfQualityIn, soeOPTIONAL},
|
{sfQualityIn, soeOPTIONAL},
|
||||||
@@ -187,6 +236,9 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type deletes an existing account. */
|
/** This transaction type deletes an existing account. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfDestinationTag, soeOPTIONAL},
|
{sfDestinationTag, soeOPTIONAL},
|
||||||
@@ -194,11 +246,17 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type installs a hook. */
|
/** This transaction type installs a hook. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetHook.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
|
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
|
||||||
{sfHooks, soeREQUIRED},
|
{sfHooks, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction mints a new NFT. */
|
/** This transaction mints a new NFT. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenMint.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||||
{sfNFTokenTaxon, soeREQUIRED},
|
{sfNFTokenTaxon, soeREQUIRED},
|
||||||
{sfTransferFee, soeOPTIONAL},
|
{sfTransferFee, soeOPTIONAL},
|
||||||
@@ -210,12 +268,18 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction burns (i.e. destroys) an existing NFT. */
|
/** This transaction burns (i.e. destroys) an existing NFT. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenBurn.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
|
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
|
||||||
{sfNFTokenID, soeREQUIRED},
|
{sfNFTokenID, soeREQUIRED},
|
||||||
{sfOwner, soeOPTIONAL},
|
{sfOwner, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction creates a new offer to buy or sell an NFT. */
|
/** This transaction creates a new offer to buy or sell an NFT. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
||||||
{sfNFTokenID, soeREQUIRED},
|
{sfNFTokenID, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED},
|
{sfAmount, soeREQUIRED},
|
||||||
@@ -225,11 +289,17 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
|
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
|
||||||
{sfNFTokenOffers, soeREQUIRED},
|
{sfNFTokenOffers, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
||||||
{sfNFTokenBuyOffer, soeOPTIONAL},
|
{sfNFTokenBuyOffer, soeOPTIONAL},
|
||||||
{sfNFTokenSellOffer, soeOPTIONAL},
|
{sfNFTokenSellOffer, soeOPTIONAL},
|
||||||
@@ -237,12 +307,18 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction claws back issued tokens. */
|
/** This transaction claws back issued tokens. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Clawback.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCLAWBACK, 30, Clawback, ({
|
TRANSACTION(ttCLAWBACK, 30, Clawback, ({
|
||||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||||
{sfHolder, soeOPTIONAL},
|
{sfHolder, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction claws back tokens from an AMM pool. */
|
/** This transaction claws back tokens from an AMM pool. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMClawback.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
||||||
{sfHolder, soeREQUIRED},
|
{sfHolder, soeREQUIRED},
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
@@ -251,6 +327,9 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates an AMM instance */
|
/** This transaction type creates an AMM instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMCreate.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
||||||
{sfAmount, soeREQUIRED},
|
{sfAmount, soeREQUIRED},
|
||||||
{sfAmount2, soeREQUIRED},
|
{sfAmount2, soeREQUIRED},
|
||||||
@@ -258,6 +337,9 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type deposits into an AMM instance */
|
/** This transaction type deposits into an AMM instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMDeposit.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAsset2, soeREQUIRED},
|
{sfAsset2, soeREQUIRED},
|
||||||
@@ -269,6 +351,9 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type withdraws from an AMM instance */
|
/** This transaction type withdraws from an AMM instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMWithdraw.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAsset2, soeREQUIRED},
|
{sfAsset2, soeREQUIRED},
|
||||||
@@ -279,6 +364,9 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type votes for the trading fee */
|
/** This transaction type votes for the trading fee */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMVote.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAsset2, soeREQUIRED},
|
{sfAsset2, soeREQUIRED},
|
||||||
@@ -286,6 +374,9 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type bids for the auction slot */
|
/** This transaction type bids for the auction slot */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMBid.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAsset2, soeREQUIRED},
|
{sfAsset2, soeREQUIRED},
|
||||||
@@ -295,12 +386,18 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type deletes AMM in the empty state */
|
/** This transaction type deletes AMM in the empty state */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/AMMDelete.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
|
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
|
||||||
{sfAsset, soeREQUIRED},
|
{sfAsset, soeREQUIRED},
|
||||||
{sfAsset2, soeREQUIRED},
|
{sfAsset2, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction mints/burns/buys/sells a URI TOKEN */
|
/** This transaction mints/burns/buys/sells a URI TOKEN */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/URIToken.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({
|
TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({
|
||||||
{sfURI, soeREQUIRED},
|
{sfURI, soeREQUIRED},
|
||||||
{sfDigest, soeOPTIONAL},
|
{sfDigest, soeOPTIONAL},
|
||||||
@@ -328,6 +425,9 @@ TRANSACTION(ttURITOKEN_CANCEL_SELL_OFFER, 49, URITokenCancelSellOffer, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transactions creates a crosschain sequence number */
|
/** This transactions creates a crosschain sequence number */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/XChainBridge.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({
|
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({
|
||||||
{sfXChainBridge, soeREQUIRED},
|
{sfXChainBridge, soeREQUIRED},
|
||||||
{sfSignatureReward, soeREQUIRED},
|
{sfSignatureReward, soeREQUIRED},
|
||||||
@@ -407,6 +507,9 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 57, XChainCreateBridge, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates or updates a DID */
|
/** This transaction type creates or updates a DID */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/DID.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttDID_SET, 58, DIDSet, ({
|
TRANSACTION(ttDID_SET, 58, DIDSet, ({
|
||||||
{sfDIDDocument, soeOPTIONAL},
|
{sfDIDDocument, soeOPTIONAL},
|
||||||
{sfURI, soeOPTIONAL},
|
{sfURI, soeOPTIONAL},
|
||||||
@@ -417,6 +520,9 @@ TRANSACTION(ttDID_SET, 58, DIDSet, ({
|
|||||||
TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({}))
|
TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({}))
|
||||||
|
|
||||||
/** This transaction type creates an Oracle instance */
|
/** This transaction type creates an Oracle instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetOracle.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
|
TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
|
||||||
{sfOracleDocumentID, soeREQUIRED},
|
{sfOracleDocumentID, soeREQUIRED},
|
||||||
{sfProvider, soeOPTIONAL},
|
{sfProvider, soeOPTIONAL},
|
||||||
@@ -427,17 +533,26 @@ TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type deletes an Oracle instance */
|
/** This transaction type deletes an Oracle instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({
|
TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({
|
||||||
{sfOracleDocumentID, soeREQUIRED},
|
{sfOracleDocumentID, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type fixes a problem in the ledger state */
|
/** This transaction type fixes a problem in the ledger state */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/LedgerStateFix.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({
|
TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({
|
||||||
{sfLedgerFixType, soeREQUIRED},
|
{sfLedgerFixType, soeREQUIRED},
|
||||||
{sfOwner, soeOPTIONAL},
|
{sfOwner, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates a MPTokensIssuance instance */
|
/** This transaction type creates a MPTokensIssuance instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
|
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
|
||||||
{sfAssetScale, soeOPTIONAL},
|
{sfAssetScale, soeOPTIONAL},
|
||||||
{sfTransferFee, soeOPTIONAL},
|
{sfTransferFee, soeOPTIONAL},
|
||||||
@@ -446,23 +561,35 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type destroys a MPTokensIssuance instance */
|
/** This transaction type destroys a MPTokensIssuance instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({
|
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({
|
||||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({
|
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({
|
||||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||||
{sfHolder, soeOPTIONAL},
|
{sfHolder, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type authorizes a MPToken instance */
|
/** This transaction type authorizes a MPToken instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({
|
TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({
|
||||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||||
{sfHolder, soeOPTIONAL},
|
{sfHolder, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type create an Credential instance */
|
/** This transaction type create an Credential instance */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Credentials.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({
|
TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({
|
||||||
{sfSubject, soeREQUIRED},
|
{sfSubject, soeREQUIRED},
|
||||||
{sfCredentialType, soeREQUIRED},
|
{sfCredentialType, soeREQUIRED},
|
||||||
@@ -484,6 +611,9 @@ TRANSACTION(ttCREDENTIAL_DELETE, 69, CredentialDelete, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type modify a NFToken */
|
/** This transaction type modify a NFToken */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/NFTokenModify.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
|
TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
|
||||||
{sfNFTokenID, soeREQUIRED},
|
{sfNFTokenID, soeREQUIRED},
|
||||||
{sfOwner, soeOPTIONAL},
|
{sfOwner, soeOPTIONAL},
|
||||||
@@ -491,12 +621,18 @@ TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type creates or modifies a Permissioned Domain */
|
/** This transaction type creates or modifies a Permissioned Domain */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({
|
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({
|
||||||
{sfDomainID, soeOPTIONAL},
|
{sfDomainID, soeOPTIONAL},
|
||||||
{sfAcceptedCredentials, soeREQUIRED},
|
{sfAcceptedCredentials, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type deletes a Permissioned Domain */
|
/** This transaction type deletes a Permissioned Domain */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||||
{sfDomainID, soeREQUIRED},
|
{sfDomainID, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
@@ -510,6 +646,9 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
|||||||
open ledger, so a reveal only becomes public after the revealing
|
open ledger, so a reveal only becomes public after the revealing
|
||||||
validator's transaction set is locked.
|
validator's transaction set is locked.
|
||||||
*/
|
*/
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Change.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttENTROPY, 89, Entropy, ({
|
TRANSACTION(ttENTROPY, 89, Entropy, ({
|
||||||
{sfLedgerSequence, soeREQUIRED},
|
{sfLedgerSequence, soeREQUIRED},
|
||||||
{sfPublicKey, soeREQUIRED},
|
{sfPublicKey, soeREQUIRED},
|
||||||
@@ -519,17 +658,26 @@ TRANSACTION(ttENTROPY, 89, Entropy, ({
|
|||||||
|
|
||||||
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
||||||
makes the SOTemplate reject the format at construction. */
|
makes the SOTemplate reject the format at construction. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetManifest.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
||||||
{sfManifest, soeREQUIRED},
|
{sfManifest, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Cron.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCRON, 92, Cron, ({
|
TRANSACTION(ttCRON, 92, Cron, ({
|
||||||
{sfOwner, soeREQUIRED},
|
{sfOwner, soeREQUIRED},
|
||||||
{sfLedgerSequence, soeREQUIRED},
|
{sfLedgerSequence, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/* Sechedule an alarm for later */
|
/* Sechedule an alarm for later */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/CronSet.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCRON_SET, 93, CronSet, ({
|
TRANSACTION(ttCRON_SET, 93, CronSet, ({
|
||||||
{sfDelaySeconds, soeOPTIONAL},
|
{sfDelaySeconds, soeOPTIONAL},
|
||||||
{sfRepeatCount, soeOPTIONAL},
|
{sfRepeatCount, soeOPTIONAL},
|
||||||
@@ -537,6 +685,9 @@ TRANSACTION(ttCRON_SET, 93, CronSet, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
|
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/SetRemarks.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
|
TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
|
||||||
{sfObjectID, soeREQUIRED},
|
{sfObjectID, soeREQUIRED},
|
||||||
{sfRemarks, soeREQUIRED},
|
{sfRemarks, soeREQUIRED},
|
||||||
@@ -544,6 +695,9 @@ TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
|
|||||||
|
|
||||||
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
|
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
|
||||||
* that the sender pays for. */
|
* that the sender pays for. */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Remit.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttREMIT, 95, Remit, ({
|
TRANSACTION(ttREMIT, 95, Remit, ({
|
||||||
{sfDestination, soeREQUIRED},
|
{sfDestination, soeREQUIRED},
|
||||||
{sfAmounts, soeOPTIONAL},
|
{sfAmounts, soeOPTIONAL},
|
||||||
@@ -558,12 +712,18 @@ TRANSACTION(ttREMIT, 95, Remit, ({
|
|||||||
/** This transaction can only be used by the genesis account, which is controlled exclusively by
|
/** This transaction can only be used by the genesis account, which is controlled exclusively by
|
||||||
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
|
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
|
||||||
* according to reward schedule */
|
* according to reward schedule */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/GenesisMint.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({
|
TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({
|
||||||
{sfGenesisMints, soeREQUIRED},
|
{sfGenesisMints, soeREQUIRED},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction accepts a proof of burn from an external network as a basis
|
/** This transaction accepts a proof of burn from an external network as a basis
|
||||||
* for minting according to featureImport */
|
* for minting according to featureImport */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Import.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttIMPORT, 97, Import, ({
|
TRANSACTION(ttIMPORT, 97, Import, ({
|
||||||
{sfBlob, soeREQUIRED},
|
{sfBlob, soeREQUIRED},
|
||||||
{sfIssuer, soeOPTIONAL},
|
{sfIssuer, soeOPTIONAL},
|
||||||
@@ -571,12 +731,18 @@ TRANSACTION(ttIMPORT, 97, Import, ({
|
|||||||
|
|
||||||
/** This transaction resets accumulator/counters and claims a reward for holding an average balance
|
/** This transaction resets accumulator/counters and claims a reward for holding an average balance
|
||||||
* from a specified hook */
|
* from a specified hook */
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/ClaimReward.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
||||||
{sfIssuer, soeOPTIONAL},
|
{sfIssuer, soeOPTIONAL},
|
||||||
{sfClaimCurrency, soeOPTIONAL},
|
{sfClaimCurrency, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
# include <xrpld/app/tx/detail/Invoke.h>
|
||||||
|
#endif
|
||||||
TRANSACTION(ttINVOKE, 99, Invoke, ({
|
TRANSACTION(ttINVOKE, 99, Invoke, ({
|
||||||
{sfBlob, soeOPTIONAL},
|
{sfBlob, soeOPTIONAL},
|
||||||
{sfDestination, soeOPTIONAL},
|
{sfDestination, soeOPTIONAL},
|
||||||
|
|||||||
@@ -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 ripple {
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
[[noreturn]] void
|
|
||||||
accessViolation() noexcept
|
|
||||||
{
|
|
||||||
// dereference memory location zero
|
|
||||||
int volatile* j = 0;
|
|
||||||
(void)*j;
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
void
|
void
|
||||||
LogThrow(std::string const& title)
|
LogThrow(std::string const& title)
|
||||||
{
|
{
|
||||||
@@ -55,7 +42,7 @@ LogicError(std::string const& s) noexcept
|
|||||||
// here.
|
// here.
|
||||||
// For the above reasons, we want this contract to stand out.
|
// For the above reasons, we want this contract to stand out.
|
||||||
UNREACHABLE("LogicError", {{"message", s}});
|
UNREACHABLE("LogicError", {{"message", s}});
|
||||||
detail::accessViolation();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
namespace beast::detail {
|
namespace beast::detail {
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
setCurrentThreadNameImpl(std::string_view name)
|
setCurrentThreadNameImpl(char const* name)
|
||||||
{
|
{
|
||||||
#if DEBUG && BOOST_COMP_MSVC
|
#if DEBUG && BOOST_COMP_MSVC
|
||||||
// This technique is documented by Microsoft and works for all versions
|
// This technique is documented by Microsoft and works for all versions
|
||||||
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(std::string_view name)
|
|||||||
THREADNAME_INFO ni;
|
THREADNAME_INFO ni;
|
||||||
|
|
||||||
ni.dwType = 0x1000;
|
ni.dwType = 0x1000;
|
||||||
ni.szName = name.data();
|
ni.szName = name;
|
||||||
ni.dwThreadID = GetCurrentThreadId();
|
ni.dwThreadID = GetCurrentThreadId();
|
||||||
ni.dwFlags = 0;
|
ni.dwFlags = 0;
|
||||||
|
|
||||||
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(std::string_view name)
|
|||||||
namespace beast::detail {
|
namespace beast::detail {
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
setCurrentThreadNameImpl(std::string_view name)
|
setCurrentThreadNameImpl(char const* name)
|
||||||
{
|
{
|
||||||
pthread_setname_np(name.data());
|
pthread_setname_np(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace beast::detail
|
} // namespace beast::detail
|
||||||
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(std::string_view name)
|
|||||||
namespace beast::detail {
|
namespace beast::detail {
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
setCurrentThreadNameImpl(std::string_view name)
|
setCurrentThreadNameImpl(char const* name)
|
||||||
{
|
{
|
||||||
pthread_setname_np(pthread_self(), name.data());
|
pthread_setname_np(pthread_self(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace beast::detail
|
} // namespace beast::detail
|
||||||
@@ -119,7 +119,7 @@ void
|
|||||||
setCurrentThreadName(std::string_view name)
|
setCurrentThreadName(std::string_view name)
|
||||||
{
|
{
|
||||||
detail::threadName = name;
|
detail::threadName = name;
|
||||||
detail::setCurrentThreadNameImpl(name);
|
detail::setCurrentThreadNameImpl(detail::threadName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace beast
|
} // namespace beast
|
||||||
@@ -29,7 +29,9 @@ bool
|
|||||||
is_private(AddressV6 const& addr)
|
is_private(AddressV6 const& addr)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
|
addr.is_link_local() || // fe80::/10
|
||||||
|
addr.is_loopback() || // ::1
|
||||||
|
((addr.to_bytes()[0] & 0xfe) == 0xfc) || // fc00::/7 (all ULA)
|
||||||
(addr.is_v4_mapped() &&
|
(addr.is_v4_mapped() &&
|
||||||
is_private(boost::asio::ip::make_address_v4(
|
is_private(boost::asio::ip::make_address_v4(
|
||||||
boost::asio::ip::v4_mapped, addr))));
|
boost::asio::ip::v4_mapped, addr))));
|
||||||
@@ -38,8 +40,7 @@ is_private(AddressV6 const& addr)
|
|||||||
bool
|
bool
|
||||||
is_public(AddressV6 const& addr)
|
is_public(AddressV6 const& addr)
|
||||||
{
|
{
|
||||||
// TODO is this correct?
|
return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
|
||||||
return !is_private(addr) && !addr.is_multicast();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IP
|
} // namespace IP
|
||||||
|
|||||||
@@ -171,53 +171,6 @@ ed25519Canonical(Slice const& sig)
|
|||||||
return std::lexicographical_compare(S, S + 32, Order, Order + 32);
|
return std::lexicographical_compare(S, S + 32, Order, Order + 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
PublicKey::PublicKey(Slice const& slice)
|
|
||||||
{
|
|
||||||
if (slice.size() < size_)
|
|
||||||
LogicError(
|
|
||||||
"PublicKey::PublicKey - Input slice cannot be an undersized "
|
|
||||||
"buffer");
|
|
||||||
|
|
||||||
if (!publicKeyType(slice))
|
|
||||||
LogicError("PublicKey::PublicKey invalid type");
|
|
||||||
std::memcpy(buf_, slice.data(), size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
PublicKey::PublicKey(PublicKey const& other)
|
|
||||||
{
|
|
||||||
std::memcpy(buf_, other.buf_, size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
PublicKey&
|
|
||||||
PublicKey::operator=(PublicKey const& other)
|
|
||||||
{
|
|
||||||
if (this != &other)
|
|
||||||
{
|
|
||||||
std::memcpy(buf_, other.buf_, size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
std::optional<KeyType>
|
|
||||||
publicKeyType(Slice const& slice)
|
|
||||||
{
|
|
||||||
if (slice.size() == 33)
|
|
||||||
{
|
|
||||||
if (slice[0] == 0xED)
|
|
||||||
return KeyType::ed25519;
|
|
||||||
|
|
||||||
if (slice[0] == 0x02 || slice[0] == 0x03)
|
|
||||||
return KeyType::secp256k1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
verifyDigest(
|
verifyDigest(
|
||||||
PublicKey const& publicKey,
|
PublicKey const& publicKey,
|
||||||
|
|||||||
@@ -961,7 +961,7 @@ amountFromJson(SField const& name, Json::Value const& v)
|
|||||||
|
|
||||||
bool const native = !currencyOrMPTID.isString() ||
|
bool const native = !currencyOrMPTID.isString() ||
|
||||||
currencyOrMPTID.asString().empty() ||
|
currencyOrMPTID.asString().empty() ||
|
||||||
(currencyOrMPTID.asString() == systemCurrencyCode());
|
(currencyOrMPTID.asString() == systemCurrencyCode);
|
||||||
|
|
||||||
if (native)
|
if (native)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <xrpl/protocol/SecretKey.h>
|
#include <xrpl/protocol/SecretKey.h>
|
||||||
#include <xrpl/protocol/detail/secp256k1.h>
|
#include <xrpl/protocol/detail/secp256k1.h>
|
||||||
#include <xrpl/protocol/digest.h>
|
#include <xrpl/protocol/digest.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ed25519.h>
|
#include <ed25519.h>
|
||||||
|
|
||||||
@@ -32,25 +33,7 @@ namespace ripple {
|
|||||||
|
|
||||||
SecretKey::~SecretKey()
|
SecretKey::~SecretKey()
|
||||||
{
|
{
|
||||||
secure_erase(buf_, sizeof(buf_));
|
secure_erase(buf_.data(), buf_.size());
|
||||||
}
|
|
||||||
|
|
||||||
SecretKey::SecretKey(std::array<std::uint8_t, 32> const& key)
|
|
||||||
{
|
|
||||||
std::memcpy(buf_, key.data(), key.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
SecretKey::SecretKey(Slice const& slice)
|
|
||||||
{
|
|
||||||
if (slice.size() != sizeof(buf_))
|
|
||||||
LogicError("SecretKey::SecretKey: invalid size");
|
|
||||||
std::memcpy(buf_, slice.data(), sizeof(buf_));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
SecretKey::to_string() const
|
|
||||||
{
|
|
||||||
return strHex(*this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@@ -214,7 +197,6 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
|
|||||||
if (publicKeyType(pk.slice()) != KeyType::secp256k1)
|
if (publicKeyType(pk.slice()) != KeyType::secp256k1)
|
||||||
LogicError("sign: secp256k1 required for digest signing");
|
LogicError("sign: secp256k1 required for digest signing");
|
||||||
|
|
||||||
BOOST_ASSERT(sk.size() == 32);
|
|
||||||
secp256k1_ecdsa_signature sig_imp;
|
secp256k1_ecdsa_signature sig_imp;
|
||||||
if (secp256k1_ecdsa_sign(
|
if (secp256k1_ecdsa_sign(
|
||||||
secp256k1Context(),
|
secp256k1Context(),
|
||||||
@@ -226,7 +208,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
|
|||||||
LogicError("sign: secp256k1_ecdsa_sign failed");
|
LogicError("sign: secp256k1_ecdsa_sign failed");
|
||||||
|
|
||||||
unsigned char sig[72];
|
unsigned char sig[72];
|
||||||
size_t len = sizeof(sig);
|
auto len = sizeof(sig);
|
||||||
if (secp256k1_ecdsa_signature_serialize_der(
|
if (secp256k1_ecdsa_signature_serialize_der(
|
||||||
secp256k1Context(), sig, &len, &sig_imp) != 1)
|
secp256k1Context(), sig, &len, &sig_imp) != 1)
|
||||||
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
|
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
|
||||||
|
|||||||
@@ -39,28 +39,12 @@ Seed::~Seed()
|
|||||||
secure_erase(buf_.data(), buf_.size());
|
secure_erase(buf_.data(), buf_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Seed::Seed(Slice const& slice)
|
|
||||||
{
|
|
||||||
if (slice.size() != buf_.size())
|
|
||||||
LogicError("Seed::Seed: invalid size");
|
|
||||||
std::memcpy(buf_.data(), slice.data(), buf_.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
Seed::Seed(uint128 const& seed)
|
|
||||||
{
|
|
||||||
if (seed.size() != buf_.size())
|
|
||||||
LogicError("Seed::Seed: invalid size");
|
|
||||||
std::memcpy(buf_.data(), seed.data(), buf_.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Seed
|
Seed
|
||||||
randomSeed()
|
randomSeed()
|
||||||
{
|
{
|
||||||
std::array<std::uint8_t, 16> buffer;
|
std::array<std::uint8_t, 16> buffer;
|
||||||
beast::rngfill(buffer.data(), buffer.size(), crypto_prng());
|
beast::rngfill(buffer, crypto_prng());
|
||||||
Seed seed(makeSlice(buffer));
|
Seed seed(buffer);
|
||||||
secure_erase(buffer.data(), buffer.size());
|
secure_erase(buffer.data(), buffer.size());
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
@@ -117,7 +101,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
|
|||||||
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
||||||
{
|
{
|
||||||
Blob const blob(key.rbegin(), key.rend());
|
Blob const blob(key.rbegin(), key.rend());
|
||||||
return Seed{uint128{blob}};
|
return Seed{makeSlice(blob)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// CHECKME this returns a different version than the replies below. Is
|
||||||
// this by design or an accident or should it be using
|
// this by design or an accident or should it be using
|
||||||
// BuildInfo::getFullVersionString () as well?
|
// BuildInfo::getFullVersionString () as well?
|
||||||
output("Server: " + systemName() + "-json-rpc/v1");
|
output("Server: " + std::string(systemName) + "-json-rpc/v1");
|
||||||
output("\r\n");
|
output("\r\n");
|
||||||
|
|
||||||
// Be careful in modifying this! If you change the contents you MUST
|
// Be careful in modifying this! If you change the contents you MUST
|
||||||
@@ -147,7 +147,7 @@ HTTPReply(
|
|||||||
"\r\n"
|
"\r\n"
|
||||||
"Content-Type: application/json; charset=UTF-8\r\n");
|
"Content-Type: application/json; charset=UTF-8\r\n");
|
||||||
|
|
||||||
output("Server: " + systemName() + "-json-rpc/");
|
output("Server: " + std::string(systemName) + "-json-rpc/");
|
||||||
output(BuildInfo::getFullVersionString());
|
output(BuildInfo::getFullVersionString());
|
||||||
output(
|
output(
|
||||||
"\r\n"
|
"\r\n"
|
||||||
|
|||||||
@@ -1271,6 +1271,79 @@ public:
|
|||||||
BEAST_EXPECT(table->needValidatedLedger(257));
|
BEAST_EXPECT(table->needValidatedLedger(257));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testIgnoredUnsupported()
|
||||||
|
{
|
||||||
|
testcase("ignore_amendments");
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
weeks constexpr w(1);
|
||||||
|
test::jtx::Env env{*this, makeConfig()};
|
||||||
|
|
||||||
|
static std::vector<AmendmentTable::FeatureInfo> const supported =
|
||||||
|
makeDefaultYes(yes_);
|
||||||
|
|
||||||
|
// Ignore every unsupported_ amendment except the first one.
|
||||||
|
BEAST_EXPECT(unsupported_.size() > 1);
|
||||||
|
std::vector<std::string> const ignoredNames(
|
||||||
|
unsupported_.begin() + 1, unsupported_.end());
|
||||||
|
|
||||||
|
auto table = make_AmendmentTable(
|
||||||
|
env.app(),
|
||||||
|
w,
|
||||||
|
supported,
|
||||||
|
emptySection_,
|
||||||
|
emptySection_,
|
||||||
|
journal_,
|
||||||
|
makeSection(ignoredNames));
|
||||||
|
|
||||||
|
for (auto const& a : ignoredNames)
|
||||||
|
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||||
|
BEAST_EXPECT(!table->isSupported(amendmentId(unsupported_[0])));
|
||||||
|
|
||||||
|
majorityAmendments_t const majority;
|
||||||
|
|
||||||
|
// Enabling only ignored amendments must not block.
|
||||||
|
std::set<uint256> enabled;
|
||||||
|
for (auto const& a : ignoredNames)
|
||||||
|
enabled.insert(amendmentId(a));
|
||||||
|
table->doValidatedLedger(1, enabled, majority);
|
||||||
|
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||||
|
BEAST_EXPECT(table->unsupportedAmendments().empty());
|
||||||
|
|
||||||
|
// An ignored amendment must never be voted for.
|
||||||
|
auto const desired = table->getDesired();
|
||||||
|
for (auto const& a : ignoredNames)
|
||||||
|
BEAST_EXPECT(
|
||||||
|
std::find(desired.begin(), desired.end(), amendmentId(a)) ==
|
||||||
|
desired.end());
|
||||||
|
|
||||||
|
// A non-ignored unsupported amendment still blocks.
|
||||||
|
enabled.insert(amendmentId(unsupported_[0]));
|
||||||
|
table->doValidatedLedger(2, enabled, majority);
|
||||||
|
BEAST_EXPECT(table->hasUnsupportedEnabled());
|
||||||
|
|
||||||
|
// Bad entries are rejected.
|
||||||
|
Section bad("ignore_amendments");
|
||||||
|
bad.append("NotARealFeatureName");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
make_AmendmentTable(
|
||||||
|
env.app(),
|
||||||
|
w,
|
||||||
|
supported,
|
||||||
|
emptySection_,
|
||||||
|
emptySection_,
|
||||||
|
journal_,
|
||||||
|
bad);
|
||||||
|
fail("expected bad [ignore_amendments] entry to throw");
|
||||||
|
}
|
||||||
|
catch (std::runtime_error const&)
|
||||||
|
{
|
||||||
|
pass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testFeature(FeatureBitset const& feat)
|
testFeature(FeatureBitset const& feat)
|
||||||
{
|
{
|
||||||
@@ -1294,6 +1367,7 @@ public:
|
|||||||
testBadConfig();
|
testBadConfig();
|
||||||
testEnableVeto();
|
testEnableVeto();
|
||||||
testHasUnsupported();
|
testHasUnsupported();
|
||||||
|
testIgnoredUnsupported();
|
||||||
testFeature(all - fixMajorityCalc);
|
testFeature(all - fixMajorityCalc);
|
||||||
testFeature(all);
|
testFeature(all);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -529,11 +529,10 @@ public:
|
|||||||
|
|
||||||
std::vector<JobName> jobs;
|
std::vector<JobName> jobs;
|
||||||
{
|
{
|
||||||
auto const& jobTypes = JobTypes::instance();
|
|
||||||
jobs.reserve(jobTypes.size());
|
jobs.reserve(jobTypes.size());
|
||||||
for (auto const& job : jobTypes)
|
for (auto const& job : jobTypes)
|
||||||
{
|
{
|
||||||
jobs.emplace_back(job.first, job.second.name());
|
jobs.emplace_back(job.type, std::string(job.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::shuffle(jobs.begin(), jobs.end(), default_prng());
|
std::shuffle(jobs.begin(), jobs.end(), default_prng());
|
||||||
@@ -865,14 +864,10 @@ public:
|
|||||||
JobType jobType;
|
JobType jobType;
|
||||||
std::string jobTypeName;
|
std::string jobTypeName;
|
||||||
{
|
{
|
||||||
auto const& jobTypes = JobTypes::instance();
|
auto index = rand_int<std::size_t>(0, jobTypes.size() - 1);
|
||||||
|
|
||||||
std::uniform_int_distribution<> dis(0, jobTypes.size() - 1);
|
jobType = jobTypes[index].type;
|
||||||
auto iter{jobTypes.begin()};
|
jobTypeName = jobTypes[index].name;
|
||||||
std::advance(iter, dis(default_prng()));
|
|
||||||
|
|
||||||
jobType = iter->second.type();
|
|
||||||
jobTypeName = iter->second.name();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Say there's one worker thread.
|
// Say there's one worker thread.
|
||||||
|
|||||||
@@ -307,11 +307,9 @@ struct base_uint_test : beast::unit_test::suite
|
|||||||
std::string_view sView(str.data(), str.size());
|
std::string_view sView(str.data(), str.size());
|
||||||
[[maybe_unused]] test96 t96(sView);
|
[[maybe_unused]] test96 t96(sView);
|
||||||
}
|
}
|
||||||
catch (std::invalid_argument const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||||
e.what() ==
|
|
||||||
std::string("invalid length for hex string"));
|
|
||||||
caught = true;
|
caught = true;
|
||||||
}
|
}
|
||||||
BEAST_EXPECT(caught);
|
BEAST_EXPECT(caught);
|
||||||
@@ -327,10 +325,9 @@ struct base_uint_test : beast::unit_test::suite
|
|||||||
std::string_view sView(str.data(), str.size());
|
std::string_view sView(str.data(), str.size());
|
||||||
[[maybe_unused]] test96 t96(sView);
|
[[maybe_unused]] test96 t96(sView);
|
||||||
}
|
}
|
||||||
catch (std::range_error const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||||
e.what() == std::string("invalid hex character"));
|
|
||||||
caught = true;
|
caught = true;
|
||||||
}
|
}
|
||||||
BEAST_EXPECT(caught);
|
BEAST_EXPECT(caught);
|
||||||
|
|||||||
@@ -324,6 +324,35 @@ public:
|
|||||||
BEAST_EXPECT(!is_loopback(ep));
|
BEAST_EXPECT(!is_loopback(ep));
|
||||||
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep));
|
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep));
|
||||||
|
|
||||||
|
// a link-local IPv6 is private
|
||||||
|
ep = Endpoint(boost::asio::ip::make_address_v6("fe80::1"));
|
||||||
|
BEAST_EXPECT(!is_public(ep));
|
||||||
|
BEAST_EXPECT(is_private(ep));
|
||||||
|
BEAST_EXPECT(!is_loopback(ep));
|
||||||
|
|
||||||
|
// the IPv6 loopback is private
|
||||||
|
ep = Endpoint(boost::asio::ip::make_address_v6("::1"));
|
||||||
|
BEAST_EXPECT(!is_public(ep));
|
||||||
|
BEAST_EXPECT(is_private(ep));
|
||||||
|
BEAST_EXPECT(is_loopback(ep));
|
||||||
|
|
||||||
|
// a global unicast IPv6 is public
|
||||||
|
ep = Endpoint(boost::asio::ip::make_address_v6("2001:db8::1"));
|
||||||
|
BEAST_EXPECT(!is_unspecified(ep));
|
||||||
|
BEAST_EXPECT(is_public(ep));
|
||||||
|
BEAST_EXPECT(!is_private(ep));
|
||||||
|
BEAST_EXPECT(!is_multicast(ep));
|
||||||
|
|
||||||
|
// multicast and unspecified IPv6 are neither public nor private
|
||||||
|
ep = Endpoint(boost::asio::ip::make_address_v6("ff02::1"));
|
||||||
|
BEAST_EXPECT(!is_public(ep));
|
||||||
|
BEAST_EXPECT(!is_private(ep));
|
||||||
|
BEAST_EXPECT(is_multicast(ep));
|
||||||
|
ep = Endpoint(AddressV6{});
|
||||||
|
BEAST_EXPECT(is_unspecified(ep));
|
||||||
|
BEAST_EXPECT(!is_public(ep));
|
||||||
|
BEAST_EXPECT(!is_private(ep));
|
||||||
|
|
||||||
{
|
{
|
||||||
ep = Endpoint::from_string("192.0.2.112");
|
ep = Endpoint::from_string("192.0.2.112");
|
||||||
BEAST_EXPECT(!is_unspecified(ep));
|
BEAST_EXPECT(!is_unspecified(ep));
|
||||||
|
|||||||
@@ -120,23 +120,28 @@ public:
|
|||||||
|
|
||||||
auto& jq = env.app().getJobQueue();
|
auto& jq = env.app().getJobQueue();
|
||||||
|
|
||||||
static int const N = 4;
|
// The test assumes that all the threads and coroutines it launches
|
||||||
std::array<std::shared_ptr<JobQueue::Coro>, N> a;
|
// all run in a single thread.
|
||||||
|
BEAST_EXPECT(jq.thread_count() == 1);
|
||||||
|
|
||||||
|
std::array<std::shared_ptr<JobQueue::Coro>, 4> a;
|
||||||
|
|
||||||
LocalValue<int> lv(-1);
|
LocalValue<int> lv(-1);
|
||||||
BEAST_EXPECT(*lv == -1);
|
BEAST_EXPECT(*lv == -1);
|
||||||
|
|
||||||
gate g;
|
gate g;
|
||||||
|
|
||||||
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
|
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
|
||||||
this->BEAST_EXPECT(*lv == -1);
|
this->BEAST_EXPECT(*lv == -1);
|
||||||
*lv = -2;
|
*lv = -2;
|
||||||
this->BEAST_EXPECT(*lv == -2);
|
this->BEAST_EXPECT(*lv == -2);
|
||||||
g.signal();
|
g.signal();
|
||||||
});
|
});
|
||||||
|
|
||||||
BEAST_EXPECT(g.wait_for(5s));
|
BEAST_EXPECT(g.wait_for(5s));
|
||||||
BEAST_EXPECT(*lv == -1);
|
BEAST_EXPECT(*lv == -1);
|
||||||
|
|
||||||
for (int i = 0; i < N; ++i)
|
for (int i = 0; i < a.size(); ++i)
|
||||||
{
|
{
|
||||||
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
|
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
|
||||||
a[id] = c;
|
a[id] = c;
|
||||||
@@ -154,12 +159,14 @@ public:
|
|||||||
BEAST_EXPECT(g.wait_for(5s));
|
BEAST_EXPECT(g.wait_for(5s));
|
||||||
a[i]->join();
|
a[i]->join();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& c : a)
|
for (auto const& c : a)
|
||||||
{
|
{
|
||||||
c->post();
|
c->post();
|
||||||
BEAST_EXPECT(g.wait_for(5s));
|
BEAST_EXPECT(g.wait_for(5s));
|
||||||
c->join();
|
c->join();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& c : a)
|
for (auto const& c : a)
|
||||||
{
|
{
|
||||||
c->post();
|
c->post();
|
||||||
@@ -170,6 +177,7 @@ public:
|
|||||||
this->BEAST_EXPECT(*lv == -2);
|
this->BEAST_EXPECT(*lv == -2);
|
||||||
g.signal();
|
g.signal();
|
||||||
});
|
});
|
||||||
|
|
||||||
BEAST_EXPECT(g.wait_for(5s));
|
BEAST_EXPECT(g.wait_for(5s));
|
||||||
BEAST_EXPECT(*lv == -1);
|
BEAST_EXPECT(*lv == -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
#include <xrpld/core/JobQueue.h>
|
#include <xrpld/core/JobQueue.h>
|
||||||
#include <xrpl/beast/unit_test.h>
|
#include <xrpl/beast/unit_test.h>
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
@@ -45,22 +50,19 @@ class JobQueue_test : public beast::unit_test::suite
|
|||||||
while (jobRan == false)
|
while (jobRan == false)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
// If the JobQueue is stopped, we should no
|
|
||||||
// longer be able to add Jobs (and calling addJob() should
|
|
||||||
// return false).
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
jQueue.stop();
|
|
||||||
|
|
||||||
// The Job should never run, so having the Job access this
|
// If the JobQueue is stopped or in the process of stopping, attempting
|
||||||
// unprotected variable on the stack should be completely safe.
|
// to schedule a coroutine should fail.
|
||||||
// Not recommended for the faint of heart...
|
jQueue.stop();
|
||||||
bool unprotected;
|
BEAST_EXPECT(jQueue.isStopping());
|
||||||
BEAST_EXPECT(
|
|
||||||
jQueue.addJob(jtCLIENT, "JobAddTest2", [&unprotected]() {
|
auto const coro = jQueue.postCoro(
|
||||||
unprotected = false;
|
jtCLIENT,
|
||||||
}) == false);
|
"JobAddTest2",
|
||||||
}
|
[](std::shared_ptr<JobQueue::Coro> const&) {
|
||||||
|
LogicError("Unscheduled coroutine improperly invoked!");
|
||||||
|
});
|
||||||
|
BEAST_EXPECT(coro == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -128,31 +130,77 @@ class JobQueue_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
BEAST_EXPECT(yieldCount == 4);
|
BEAST_EXPECT(yieldCount == 4);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
// If the JobQueue is stopped, we should no
|
|
||||||
// longer be able to add a Coro (and calling postCoro() should
|
|
||||||
// return false).
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
jQueue.stop();
|
|
||||||
|
|
||||||
// The Coro should never run, so having the Coro access this
|
// If the JobQueue is stopped or in the process of stopping, attempting
|
||||||
// unprotected variable on the stack should be completely safe.
|
// to schedule a coroutine should fail.
|
||||||
// Not recommended for the faint of heart...
|
jQueue.stop();
|
||||||
bool unprotected;
|
BEAST_EXPECT(jQueue.isStopping());
|
||||||
auto const coro = jQueue.postCoro(
|
|
||||||
jtCLIENT,
|
auto const coro = jQueue.postCoro(
|
||||||
"PostCoroTest3",
|
jtCLIENT,
|
||||||
[&unprotected](std::shared_ptr<JobQueue::Coro> const&) {
|
"PostCoroTest3",
|
||||||
unprotected = false;
|
[](std::shared_ptr<JobQueue::Coro> const&) {
|
||||||
});
|
LogicError("Unscheduled coroutine improperly invoked!");
|
||||||
BEAST_EXPECT(coro == nullptr);
|
});
|
||||||
}
|
BEAST_EXPECT(coro == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testPriority()
|
||||||
|
{
|
||||||
|
testcase("priority order");
|
||||||
|
|
||||||
|
jtx::Env env{*this};
|
||||||
|
JobQueue& jQueue = env.app().getJobQueue();
|
||||||
|
|
||||||
|
// A standalone Env runs the JobQueue on a single worker, which makes
|
||||||
|
// dispatch order deterministic.
|
||||||
|
if (!BEAST_EXPECT(jQueue.thread_count() == 1))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::promise<void> started;
|
||||||
|
auto startedFuture = started.get_future();
|
||||||
|
std::promise<void> release;
|
||||||
|
std::shared_future<void> const releaseFuture =
|
||||||
|
release.get_future().share();
|
||||||
|
|
||||||
|
// Occupy the only worker so the jobs below queue up behind it.
|
||||||
|
BEAST_EXPECT(
|
||||||
|
jQueue.addJob(jtCLIENT, "blocker", [&started, releaseFuture]() {
|
||||||
|
started.set_value();
|
||||||
|
releaseFuture.wait();
|
||||||
|
}));
|
||||||
|
startedFuture.wait();
|
||||||
|
|
||||||
|
std::mutex m;
|
||||||
|
std::vector<std::string> order;
|
||||||
|
auto record = [&](std::string tag) {
|
||||||
|
return [&m, &order, tag]() {
|
||||||
|
std::lock_guard lock(m);
|
||||||
|
order.push_back(tag);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low1", record("low1")));
|
||||||
|
BEAST_EXPECT(jQueue.addJob(jtPUBLEDGER, "high", record("high")));
|
||||||
|
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low2", record("low2")));
|
||||||
|
BEAST_EXPECT(jQueue.addJob(jtADMIN, "highest", record("highest")));
|
||||||
|
|
||||||
|
release.set_value();
|
||||||
|
jQueue.rendezvous();
|
||||||
|
|
||||||
|
// Higher job types run first; equal types run in FIFO order.
|
||||||
|
std::vector<std::string> const expected{
|
||||||
|
"highest", "high", "low1", "low2"};
|
||||||
|
std::lock_guard lock(m);
|
||||||
|
BEAST_EXPECT(order == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
|
testPriority();
|
||||||
testAddJob();
|
testAddJob();
|
||||||
testPostCoro();
|
testPostCoro();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -52,18 +52,10 @@ public:
|
|||||||
for (std::uint8_t i = 0; i < 64; i++)
|
for (std::uint8_t i = 0; i < 64; i++)
|
||||||
{
|
{
|
||||||
beast::rngfill(src, sizeof(src), default_prng());
|
beast::rngfill(src, sizeof(src), default_prng());
|
||||||
Seed const seed({src, sizeof(src)});
|
Seed const seed(src);
|
||||||
BEAST_EXPECT(memcmp(seed.data(), src, sizeof(src)) == 0);
|
BEAST_EXPECT(memcmp(seed.data(), src, sizeof(src)) == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
{
|
|
||||||
uint128 src;
|
|
||||||
beast::rngfill(src.data(), src.size(), default_prng());
|
|
||||||
Seed const seed(src);
|
|
||||||
BEAST_EXPECT(memcmp(seed.data(), src.data(), src.size()) == 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ class PWAServe_test : public beast::unit_test::suite,
|
|||||||
BEAST_EXPECT(html(r));
|
BEAST_EXPECT(html(r));
|
||||||
BEAST_EXPECT(r.body() == doc());
|
BEAST_EXPECT(r.body() == doc());
|
||||||
BEAST_EXPECT(hdr(r, "Connection") == "close");
|
BEAST_EXPECT(hdr(r, "Connection") == "close");
|
||||||
BEAST_EXPECT(hdr(r, "Server") == systemName());
|
BEAST_EXPECT(hdr(r, "Server") == systemName);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0);
|
hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0);
|
||||||
BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY");
|
BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY");
|
||||||
@@ -695,7 +695,7 @@ class PWAServe_test : public beast::unit_test::suite,
|
|||||||
yield);
|
yield);
|
||||||
BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0);
|
BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
s.find("\r\nServer: " + systemName() + "\r\n") !=
|
s.find("\r\nServer: " + std::string(systemName) + "\r\n") !=
|
||||||
std::string::npos);
|
std::string::npos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1260,13 +1260,29 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
|||||||
|
|
||||||
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
|
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
|
||||||
|
|
||||||
|
Section const& ignored = config_->section(SECTION_IGNORE_AMENDMENTS);
|
||||||
|
|
||||||
|
// [ignore_amendments] exists to keep long-lived test networks syncable
|
||||||
|
// after an amendment they activated is removed from the codebase. It
|
||||||
|
// must never be usable on a production network.
|
||||||
|
if (!ignored.lines().empty() &&
|
||||||
|
(config_->NETWORK_ID == 0 || config_->NETWORK_ID == 21337))
|
||||||
|
{
|
||||||
|
JLOG(m_journal.fatal()) << "[" SECTION_IGNORE_AMENDMENTS
|
||||||
|
"] is not permitted on "
|
||||||
|
"network_id "
|
||||||
|
<< config_->NETWORK_ID;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
m_amendmentTable = make_AmendmentTable(
|
m_amendmentTable = make_AmendmentTable(
|
||||||
*this,
|
*this,
|
||||||
config().AMENDMENT_MAJORITY_TIME,
|
config().AMENDMENT_MAJORITY_TIME,
|
||||||
supported,
|
supported,
|
||||||
upVoted,
|
upVoted,
|
||||||
downVoted,
|
downVoted,
|
||||||
logs_->journal("Amendments"));
|
logs_->journal("Amendments"),
|
||||||
|
ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pathfinder::initPathTable();
|
Pathfinder::initPathTable();
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ void
|
|||||||
printHelp(const po::options_description& desc)
|
printHelp(const po::options_description& desc)
|
||||||
{
|
{
|
||||||
std::cerr
|
std::cerr
|
||||||
<< systemName() << "d [options] <command> <params>\n"
|
<< systemName << "d [options] <command> <params>\n"
|
||||||
<< desc << std::endl
|
<< desc << std::endl
|
||||||
<< "Commands: \n"
|
<< "Commands: \n"
|
||||||
" account_currencies <account> [<ledger>]\n"
|
" account_currencies <account> [<ledger>]\n"
|
||||||
|
|||||||
@@ -211,7 +211,8 @@ make_AmendmentTable(
|
|||||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal);
|
beast::Journal journal,
|
||||||
|
Section const& ignored = Section{});
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -1621,12 +1621,10 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
|||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
if (!isTesSuccess(e.result))
|
if (!isTesSuccess(e.result))
|
||||||
{
|
{
|
||||||
std::string token, human;
|
if (std::string token = transToken(e.result); token != "-")
|
||||||
|
|
||||||
if (transResultInfo(e.result, token, human))
|
|
||||||
{
|
{
|
||||||
JLOG(m_journal.info())
|
JLOG(m_journal.debug()) << "Tx " << e.transaction->getID()
|
||||||
<< "TransactionResult: " << token << ": " << human;
|
<< ", result: " << token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ parseSection(Section const& section)
|
|||||||
|
|
||||||
uint256 id;
|
uint256 id;
|
||||||
|
|
||||||
if (!id.parseHex(match[1]))
|
if (!id.parseHex(match[1].str()))
|
||||||
Throw<std::runtime_error>(
|
Throw<std::runtime_error>(
|
||||||
"Invalid amendment ID '" + match[1] + "' in [" +
|
"Invalid amendment ID '" + match[1] + "' in [" +
|
||||||
section.name() + "]");
|
section.name() + "]");
|
||||||
@@ -302,6 +302,15 @@ struct AmendmentState
|
|||||||
/** Indicates an amendment that this server has code support for. */
|
/** Indicates an amendment that this server has code support for. */
|
||||||
bool supported = false;
|
bool supported = false;
|
||||||
|
|
||||||
|
/** Indicates an amendment listed in [ignore_amendments]. The server has
|
||||||
|
no code for it, but must not become amendment blocked if it is (or
|
||||||
|
becomes) enabled on the ledger. Such an amendment is reported as
|
||||||
|
`supported` internally so every blocking check (LedgerMaster,
|
||||||
|
NetworkOPs, Change) passes, but is never voted for and is shown as
|
||||||
|
unsupported + ignored over RPC.
|
||||||
|
*/
|
||||||
|
bool ignored = false;
|
||||||
|
|
||||||
/** The name of this amendment, possibly empty. */
|
/** The name of this amendment, possibly empty. */
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
@@ -476,7 +485,8 @@ public:
|
|||||||
std::vector<FeatureInfo> const& supported,
|
std::vector<FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal);
|
beast::Journal journal,
|
||||||
|
Section const& ignored);
|
||||||
|
|
||||||
uint256
|
uint256
|
||||||
find(std::string const& name) const override;
|
find(std::string const& name) const override;
|
||||||
@@ -543,7 +553,8 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
std::vector<FeatureInfo> const& supported,
|
std::vector<FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal,
|
||||||
|
Section const& ignored)
|
||||||
: lastUpdateSeq_(0)
|
: lastUpdateSeq_(0)
|
||||||
, majorityTime_(majorityTime)
|
, majorityTime_(majorityTime)
|
||||||
, unsupportedEnabled_(false)
|
, unsupportedEnabled_(false)
|
||||||
@@ -586,6 +597,59 @@ AmendmentTableImpl::AmendmentTableImpl(
|
|||||||
<< " voted by default if not enabled on the ledger.";
|
<< " voted by default if not enabled on the ledger.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse ignored amendments from config. Each line is either a registered
|
||||||
|
// feature name (e.g. "HookOnV2") or "<64 hex amendment ID> [name]". These
|
||||||
|
// are deliberately NOT persisted to wallet.db: removing the line from the
|
||||||
|
// config must restore normal amendment-blocking behaviour.
|
||||||
|
for (auto const& line : ignored.lines())
|
||||||
|
{
|
||||||
|
std::string const trimmed = boost::algorithm::trim_copy(line);
|
||||||
|
if (trimmed.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
boost::algorithm::split(
|
||||||
|
parts,
|
||||||
|
trimmed,
|
||||||
|
boost::algorithm::is_space(),
|
||||||
|
boost::algorithm::token_compress_on);
|
||||||
|
|
||||||
|
uint256 id;
|
||||||
|
std::string name;
|
||||||
|
if (id.parseHex(parts[0]))
|
||||||
|
name = parts.size() > 1 ? parts[1] : std::string{};
|
||||||
|
else if (auto const f = getRegisteredFeature(parts[0]))
|
||||||
|
{
|
||||||
|
id = *f;
|
||||||
|
name = parts[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Throw<std::runtime_error>(
|
||||||
|
"Invalid entry '" + line + "' in [" + ignored.name() +
|
||||||
|
"]: expected a registered feature name or amendment ID");
|
||||||
|
|
||||||
|
AmendmentState& s = add(id, lock);
|
||||||
|
if (s.supported)
|
||||||
|
{
|
||||||
|
JLOG(j_.warn()) << "[" << ignored.name() << "] lists amendment "
|
||||||
|
<< id << " (" << s.name
|
||||||
|
<< ") which this server supports; entry has no "
|
||||||
|
"effect.";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.empty())
|
||||||
|
s.name = name;
|
||||||
|
s.supported = true;
|
||||||
|
s.ignored = true;
|
||||||
|
s.vote = AmendmentVote::obsolete;
|
||||||
|
|
||||||
|
JLOG(j_.warn()) << "Amendment " << id << " (" << s.name
|
||||||
|
<< ") is unsupported but listed in [" << ignored.name()
|
||||||
|
<< "]: this server will NOT become amendment blocked "
|
||||||
|
"if it is enabled on the ledger.";
|
||||||
|
}
|
||||||
|
|
||||||
hash_set<uint256> detect_conflict;
|
hash_set<uint256> detect_conflict;
|
||||||
// Parse enabled amendments from config
|
// Parse enabled amendments from config
|
||||||
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
||||||
@@ -775,7 +839,12 @@ AmendmentTableImpl::enable(uint256 const& amendment)
|
|||||||
|
|
||||||
s.enabled = true;
|
s.enabled = true;
|
||||||
|
|
||||||
if (!s.supported)
|
if (s.ignored)
|
||||||
|
{
|
||||||
|
JLOG(j_.warn()) << "Ignored unsupported amendment " << amendment << " ("
|
||||||
|
<< s.name << ") is enabled on the ledger.";
|
||||||
|
}
|
||||||
|
else if (!s.supported)
|
||||||
{
|
{
|
||||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||||
<< " activated.";
|
<< " activated.";
|
||||||
@@ -1044,7 +1113,9 @@ AmendmentTableImpl::injectJson(
|
|||||||
if (!fs.name.empty())
|
if (!fs.name.empty())
|
||||||
v[jss::name] = fs.name;
|
v[jss::name] = fs.name;
|
||||||
|
|
||||||
v[jss::supported] = fs.supported;
|
v[jss::supported] = fs.supported && !fs.ignored;
|
||||||
|
if (fs.ignored)
|
||||||
|
v["ignored"] = true;
|
||||||
if (!fs.enabled && isAdmin)
|
if (!fs.enabled && isAdmin)
|
||||||
{
|
{
|
||||||
if (fs.vote == AmendmentVote::obsolete)
|
if (fs.vote == AmendmentVote::obsolete)
|
||||||
@@ -1113,10 +1184,11 @@ make_AmendmentTable(
|
|||||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||||
Section const& enabled,
|
Section const& enabled,
|
||||||
Section const& vetoed,
|
Section const& vetoed,
|
||||||
beast::Journal journal)
|
beast::Journal journal,
|
||||||
|
Section const& ignored)
|
||||||
{
|
{
|
||||||
return std::make_unique<AmendmentTableImpl>(
|
return std::make_unique<AmendmentTableImpl>(
|
||||||
app, majorityTime, supported, enabled, vetoed, journal);
|
app, majorityTime, supported, enabled, vetoed, journal, ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -198,7 +198,7 @@ PathRequest::isValid(std::shared_ptr<RippleLineCache> const& crCache)
|
|||||||
|
|
||||||
if (!sleDest)
|
if (!sleDest)
|
||||||
{
|
{
|
||||||
jvDestCur.append(Json::Value(systemCurrencyCode()));
|
jvDestCur.append(Json::Value(std::string(systemCurrencyCode)));
|
||||||
if (!saDstAmount.native())
|
if (!saDstAmount.native())
|
||||||
{
|
{
|
||||||
// Only XRP can be send to a non-existent account.
|
// Only XRP can be send to a non-existent account.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <xrpl/basics/CountedObject.h>
|
#include <xrpl/basics/CountedObject.h>
|
||||||
#include <xrpl/protocol/STAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
#include <xrpl/protocol/STPathSet.h>
|
#include <xrpl/protocol/STPathSet.h>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ private:
|
|||||||
bool convert_all_;
|
bool convert_all_;
|
||||||
|
|
||||||
std::shared_ptr<ReadView const> mLedger;
|
std::shared_ptr<ReadView const> mLedger;
|
||||||
std::unique_ptr<LoadEvent> m_loadEvent;
|
std::optional<LoadEvent> m_loadEvent;
|
||||||
std::shared_ptr<RippleLineCache> mRLCache;
|
std::shared_ptr<RippleLineCache> mRLCache;
|
||||||
|
|
||||||
STPathElement mSource;
|
STPathElement mSource;
|
||||||
|
|||||||
@@ -18,61 +18,21 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <xrpld/app/tx/applySteps.h>
|
#include <xrpld/app/tx/applySteps.h>
|
||||||
#include <xrpld/app/tx/detail/AMMBid.h>
|
#pragma push_macro("TRANSACTION")
|
||||||
#include <xrpld/app/tx/detail/AMMClawback.h>
|
#undef TRANSACTION
|
||||||
#include <xrpld/app/tx/detail/AMMCreate.h>
|
|
||||||
#include <xrpld/app/tx/detail/AMMDelete.h>
|
// Do nothing
|
||||||
#include <xrpld/app/tx/detail/AMMDeposit.h>
|
#define TRANSACTION(...)
|
||||||
#include <xrpld/app/tx/detail/AMMVote.h>
|
#define TRANSACTION_INCLUDE 1
|
||||||
#include <xrpld/app/tx/detail/AMMWithdraw.h>
|
|
||||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
#include <xrpl/protocol/detail/transactions.macro>
|
||||||
#include <xrpld/app/tx/detail/CancelCheck.h>
|
|
||||||
#include <xrpld/app/tx/detail/CancelOffer.h>
|
#undef TRANSACTION
|
||||||
#include <xrpld/app/tx/detail/CashCheck.h>
|
#pragma pop_macro("TRANSACTION")
|
||||||
#include <xrpld/app/tx/detail/Change.h>
|
|
||||||
#include <xrpld/app/tx/detail/ClaimReward.h>
|
// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
|
||||||
#include <xrpld/app/tx/detail/Clawback.h>
|
// See the instructions at the top of transactions.macro instead.
|
||||||
#include <xrpld/app/tx/detail/CreateCheck.h>
|
|
||||||
#include <xrpld/app/tx/detail/CreateOffer.h>
|
|
||||||
#include <xrpld/app/tx/detail/CreateTicket.h>
|
|
||||||
#include <xrpld/app/tx/detail/Credentials.h>
|
|
||||||
#include <xrpld/app/tx/detail/Cron.h>
|
|
||||||
#include <xrpld/app/tx/detail/CronSet.h>
|
|
||||||
#include <xrpld/app/tx/detail/DID.h>
|
|
||||||
#include <xrpld/app/tx/detail/DeleteAccount.h>
|
|
||||||
#include <xrpld/app/tx/detail/DeleteOracle.h>
|
|
||||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
|
||||||
#include <xrpld/app/tx/detail/Escrow.h>
|
|
||||||
#include <xrpld/app/tx/detail/GenesisMint.h>
|
|
||||||
#include <xrpld/app/tx/detail/Import.h>
|
|
||||||
#include <xrpld/app/tx/detail/Invoke.h>
|
|
||||||
#include <xrpld/app/tx/detail/LedgerStateFix.h>
|
|
||||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
|
||||||
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
|
||||||
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
|
||||||
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenBurn.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenMint.h>
|
|
||||||
#include <xrpld/app/tx/detail/NFTokenModify.h>
|
|
||||||
#include <xrpld/app/tx/detail/PayChan.h>
|
|
||||||
#include <xrpld/app/tx/detail/Payment.h>
|
|
||||||
#include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
|
|
||||||
#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
|
|
||||||
#include <xrpld/app/tx/detail/Remit.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetAccount.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetHook.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetManifest.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetOracle.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetRemarks.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
|
||||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
|
||||||
#include <xrpld/app/tx/detail/URIToken.h>
|
|
||||||
#include <xrpld/app/tx/detail/XChainBridge.h>
|
|
||||||
#include <xrpld/app/tx/detail/XahauGenesis.h>
|
|
||||||
#include <xrpl/protocol/TxFormats.h>
|
#include <xrpl/protocol/TxFormats.h>
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ struct ConfigSection
|
|||||||
#define SECTION_VALIDATORS "validators"
|
#define SECTION_VALIDATORS "validators"
|
||||||
#define SECTION_VALIDATOR_TOKEN "validator_token"
|
#define SECTION_VALIDATOR_TOKEN "validator_token"
|
||||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||||
|
// Testnet-only: amendments enabled on-ledger that this node should not treat
|
||||||
|
// as unsupported (i.e. should not become amendment blocked over).
|
||||||
|
#define SECTION_IGNORE_AMENDMENTS "ignore_amendments"
|
||||||
#define SECTION_WORKERS "workers"
|
#define SECTION_WORKERS "workers"
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
+8
-13
@@ -26,7 +26,7 @@ namespace ripple {
|
|||||||
|
|
||||||
template <class F>
|
template <class F>
|
||||||
JobQueue::Coro::Coro(
|
JobQueue::Coro::Coro(
|
||||||
Coro_create_t,
|
JobQueue::CoroCreator,
|
||||||
JobQueue& jq,
|
JobQueue& jq,
|
||||||
JobType type,
|
JobType type,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
@@ -48,6 +48,7 @@ JobQueue::Coro::Coro(
|
|||||||
},
|
},
|
||||||
boost::coroutines::attributes(megabytes(1)))
|
boost::coroutines::attributes(megabytes(1)))
|
||||||
{
|
{
|
||||||
|
++jq_.totalCoroutines_;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline JobQueue::Coro::~Coro()
|
inline JobQueue::Coro::~Coro()
|
||||||
@@ -55,15 +56,14 @@ inline JobQueue::Coro::~Coro()
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished");
|
XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
--jq_.totalCoroutines_;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
JobQueue::Coro::yield() const
|
JobQueue::Coro::yield() const
|
||||||
{
|
{
|
||||||
{
|
++jq_.suspendedCoroutines_;
|
||||||
std::lock_guard lock(jq_.m_mutex);
|
|
||||||
++jq_.nSuspend_;
|
|
||||||
}
|
|
||||||
(*yield_)();
|
(*yield_)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,9 +78,7 @@ JobQueue::Coro::post()
|
|||||||
// sp keeps 'this' alive
|
// sp keeps 'this' alive
|
||||||
if (jq_.addJob(
|
if (jq_.addJob(
|
||||||
type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
// The coroutine will not run. Clean up running_.
|
// The coroutine will not run. Clean up running_.
|
||||||
std::lock_guard lk(mutex_run_);
|
std::lock_guard lk(mutex_run_);
|
||||||
@@ -96,10 +94,7 @@ JobQueue::Coro::resume()
|
|||||||
std::lock_guard lk(mutex_run_);
|
std::lock_guard lk(mutex_run_);
|
||||||
running_ = true;
|
running_ = true;
|
||||||
}
|
}
|
||||||
{
|
--jq_.suspendedCoroutines_;
|
||||||
std::lock_guard lock(jq_.m_mutex);
|
|
||||||
--jq_.nSuspend_;
|
|
||||||
}
|
|
||||||
auto saved = detail::getLocalValues().release();
|
auto saved = detail::getLocalValues().release();
|
||||||
detail::getLocalValues().reset(&lvs_);
|
detail::getLocalValues().reset(&lvs_);
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
@@ -133,8 +128,8 @@ JobQueue::Coro::expectEarlyExit()
|
|||||||
//
|
//
|
||||||
// That said, since we're outside the Coro's stack, we need to
|
// That said, since we're outside the Coro's stack, we need to
|
||||||
// decrement the nSuspend that the Coro's call to yield caused.
|
// decrement the nSuspend that the Coro's call to yield caused.
|
||||||
std::lock_guard lock(jq_.m_mutex);
|
--jq_.suspendedCoroutines_;
|
||||||
--jq_.nSuspend_;
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+34
-42
@@ -21,9 +21,8 @@
|
|||||||
#define RIPPLE_CORE_JOB_H_INCLUDED
|
#define RIPPLE_CORE_JOB_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpld/core/ClosureCounter.h>
|
#include <xrpld/core/ClosureCounter.h>
|
||||||
#include <xrpld/core/LoadMonitor.h>
|
#include <xrpld/core/LoadEvent.h>
|
||||||
#include <xrpl/basics/CountedObject.h>
|
#include <xrpl/basics/CountedObject.h>
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
@@ -32,10 +31,7 @@ namespace ripple {
|
|||||||
// Note that this queue should only be used for CPU-bound jobs
|
// Note that this queue should only be used for CPU-bound jobs
|
||||||
// It is primarily intended for signature checking
|
// It is primarily intended for signature checking
|
||||||
|
|
||||||
enum JobType {
|
enum JobType : std::uint32_t {
|
||||||
// Special type indicating an invalid job - will go away soon.
|
|
||||||
jtINVALID = -1,
|
|
||||||
|
|
||||||
// Job types - the position in this enum indicates the job priority with
|
// Job types - the position in this enum indicates the job priority with
|
||||||
// earlier jobs having lower priority than later jobs. If you wish to
|
// earlier jobs having lower priority than later jobs. If you wish to
|
||||||
// insert a job at a specific priority, simply add it at the right location.
|
// insert a job at a specific priority, simply add it at the right location.
|
||||||
@@ -99,27 +95,15 @@ class Job : public CountedObject<Job>
|
|||||||
public:
|
public:
|
||||||
using clock_type = std::chrono::steady_clock;
|
using clock_type = std::chrono::steady_clock;
|
||||||
|
|
||||||
/** Default constructor.
|
Job() = delete;
|
||||||
|
|
||||||
Allows Job to be used as a container type.
|
|
||||||
|
|
||||||
This is used to allow things like jobMap [key] = value.
|
|
||||||
*/
|
|
||||||
// VFALCO NOTE I'd prefer not to have a default constructed object.
|
|
||||||
// What is the semantic meaning of a Job with no associated
|
|
||||||
// function? Having the invariant "all Job objects refer to
|
|
||||||
// a job" would reduce the number of states.
|
|
||||||
//
|
|
||||||
Job();
|
|
||||||
|
|
||||||
Job(JobType type, std::uint64_t index);
|
|
||||||
|
|
||||||
// VFALCO TODO try to remove the dependency on LoadMonitor.
|
|
||||||
Job(JobType type,
|
Job(JobType type,
|
||||||
std::string const& name,
|
std::string name,
|
||||||
std::uint64_t index,
|
std::uint64_t index,
|
||||||
LoadMonitor& lm,
|
std::reference_wrapper<LoadSampler const> sampler,
|
||||||
std::function<void()> const& job);
|
std::function<void()> job);
|
||||||
|
|
||||||
|
Job(Job&& other);
|
||||||
|
|
||||||
JobType
|
JobType
|
||||||
getType() const;
|
getType() const;
|
||||||
@@ -128,27 +112,35 @@ public:
|
|||||||
clock_type::time_point const&
|
clock_type::time_point const&
|
||||||
queue_time() const;
|
queue_time() const;
|
||||||
|
|
||||||
void
|
/** A description of this specific job.
|
||||||
doJob();
|
|
||||||
|
|
||||||
// These comparison operators make the jobs sort in priority order
|
Unlike the name associated with the type of this job, which is fixed,
|
||||||
// in the job set
|
the description may include additional information or context that
|
||||||
bool
|
distinguishes this from other jobs of the same type.
|
||||||
operator<(const Job& j) const;
|
*/
|
||||||
bool
|
[[maybe_unused]] std::string const&
|
||||||
operator>(const Job& j) const;
|
description() const;
|
||||||
bool
|
|
||||||
operator<=(const Job& j) const;
|
void
|
||||||
bool
|
execute();
|
||||||
operator>=(const Job& j) const;
|
|
||||||
|
// Sort jobs in dispatch order: JobQueue runs the first runnable job in
|
||||||
|
// the set, so higher-priority types (later in the JobType enum) must sort
|
||||||
|
// first, and jobs of the same type run in the order they were queued.
|
||||||
|
auto
|
||||||
|
operator<=>(Job const& other) const
|
||||||
|
{
|
||||||
|
if (type_ != other.type_)
|
||||||
|
return other.type_ <=> type_;
|
||||||
|
return index_ <=> other.index_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JobType mType;
|
JobType const type_;
|
||||||
std::uint64_t mJobIndex;
|
std::uint64_t const index_;
|
||||||
std::function<void()> mJob;
|
clock_type::time_point const queued_;
|
||||||
std::shared_ptr<LoadEvent> m_loadEvent;
|
std::function<void()> work_;
|
||||||
std::string mName;
|
LoadEvent loadEvent_;
|
||||||
clock_type::time_point m_queue_time;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using JobCounter = ClosureCounter<void>;
|
using JobCounter = ClosureCounter<void>;
|
||||||
|
|||||||
+127
-108
@@ -21,14 +21,15 @@
|
|||||||
#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
|
#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpld/core/ClosureCounter.h>
|
#include <xrpld/core/ClosureCounter.h>
|
||||||
#include <xrpld/core/JobTypeData.h>
|
|
||||||
#include <xrpld/core/JobTypes.h>
|
#include <xrpld/core/JobTypes.h>
|
||||||
#include <xrpld/core/detail/Workers.h>
|
#include <xrpld/core/detail/Workers.h>
|
||||||
#include <xrpl/basics/LocalValue.h>
|
#include <xrpl/basics/LocalValue.h>
|
||||||
|
#include <xrpl/beast/insight/Collector.h>
|
||||||
#include <xrpl/json/json_value.h>
|
#include <xrpl/json/json_value.h>
|
||||||
|
|
||||||
#include <boost/coroutine/all.hpp>
|
#include <boost/coroutine/all.hpp>
|
||||||
#include <boost/range/begin.hpp> // workaround for boost 1.72 bug
|
#include <boost/range/begin.hpp>
|
||||||
#include <boost/range/end.hpp> // workaround for boost 1.72 bug
|
#include <boost/range/end.hpp>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -37,10 +38,6 @@ class PerfLog;
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Logs;
|
class Logs;
|
||||||
struct Coro_create_t
|
|
||||||
{
|
|
||||||
explicit Coro_create_t() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** A pool of threads to perform work.
|
/** A pool of threads to perform work.
|
||||||
|
|
||||||
@@ -54,15 +51,23 @@ struct Coro_create_t
|
|||||||
*/
|
*/
|
||||||
class JobQueue : private Workers::Callback
|
class JobQueue : private Workers::Callback
|
||||||
{
|
{
|
||||||
|
// A small class used to prevent construction of coroutines without
|
||||||
|
// going through the JobQueue APIs.
|
||||||
|
struct CoroCreator
|
||||||
|
{
|
||||||
|
explicit constexpr CoroCreator() = default;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Coroutines must run to completion. */
|
/** Coroutines must run to completion. */
|
||||||
class Coro : public std::enable_shared_from_this<Coro>
|
class Coro : public std::enable_shared_from_this<Coro>,
|
||||||
|
public CountedObject<Coro>
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
detail::LocalValues lvs_;
|
detail::LocalValues lvs_;
|
||||||
JobQueue& jq_;
|
JobQueue& jq_;
|
||||||
JobType type_;
|
JobType const type_;
|
||||||
std::string name_;
|
std::string const name_;
|
||||||
bool running_;
|
bool running_;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
std::mutex mutex_run_;
|
std::mutex mutex_run_;
|
||||||
@@ -76,14 +81,23 @@ public:
|
|||||||
public:
|
public:
|
||||||
// Private: Used in the implementation
|
// Private: Used in the implementation
|
||||||
template <class F>
|
template <class F>
|
||||||
Coro(Coro_create_t, JobQueue&, JobType, std::string const&, F&&);
|
Coro(
|
||||||
|
JobQueue::CoroCreator,
|
||||||
|
JobQueue&,
|
||||||
|
JobType,
|
||||||
|
std::string const&,
|
||||||
|
F&&);
|
||||||
|
|
||||||
// Not copy-constructible or assignable
|
// Not copy-constructible or assignable
|
||||||
Coro(Coro const&) = delete;
|
Coro(Coro const&) = delete;
|
||||||
Coro&
|
Coro&
|
||||||
operator=(Coro const&) = delete;
|
operator=(Coro const&) = delete;
|
||||||
|
|
||||||
~Coro();
|
Coro(Coro&&) = delete;
|
||||||
|
Coro&
|
||||||
|
operator=(Coro&&) = delete;
|
||||||
|
|
||||||
|
virtual ~Coro();
|
||||||
|
|
||||||
/** Suspend coroutine execution.
|
/** Suspend coroutine execution.
|
||||||
Effects:
|
Effects:
|
||||||
@@ -167,9 +181,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (auto optionalCountedJob =
|
if (auto optionalCountedJob =
|
||||||
jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
|
jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
|
||||||
{
|
|
||||||
return addRefCountedJob(type, name, std::move(*optionalCountedJob));
|
return addRefCountedJob(type, name, std::move(*optionalCountedJob));
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,10 +189,10 @@ public:
|
|||||||
|
|
||||||
@param t The type of job.
|
@param t The type of job.
|
||||||
@param name Name of the job.
|
@param name Name of the job.
|
||||||
@param f Has a signature of void(std::shared_ptr<Coro>). Called when the
|
@param f The actual coroutine body; has a signature of:
|
||||||
job executes.
|
void(std::shared_ptr<Coro>)
|
||||||
|
|
||||||
@return shared_ptr to posted Coro. nullptr if post was not successful.
|
@return shared_ptr to posted Coro. nullptr if post was not successful.
|
||||||
*/
|
*/
|
||||||
template <class F>
|
template <class F>
|
||||||
std::shared_ptr<Coro>
|
std::shared_ptr<Coro>
|
||||||
@@ -201,10 +213,9 @@ public:
|
|||||||
int
|
int
|
||||||
getJobCountGE(JobType t) const;
|
getJobCountGE(JobType t) const;
|
||||||
|
|
||||||
/** Return a scoped LoadEvent.
|
/** Returns a new load event for the particular job. */
|
||||||
*/
|
LoadEvent
|
||||||
std::unique_ptr<LoadEvent>
|
makeLoadEvent(JobType t, std::string name);
|
||||||
makeLoadEvent(JobType t, std::string const& name);
|
|
||||||
|
|
||||||
/** Add multiple load events.
|
/** Add multiple load events.
|
||||||
*/
|
*/
|
||||||
@@ -226,52 +237,96 @@ public:
|
|||||||
void
|
void
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
bool
|
[[nodiscard]] bool
|
||||||
isStopping() const
|
isStopping() const noexcept
|
||||||
{
|
{
|
||||||
return stopping_;
|
return state_.load(std::memory_order_acquire) >= state::stopping;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We may be able to move away from this, but we can keep it during the
|
[[nodiscard]] bool
|
||||||
// transition.
|
isStopped() const noexcept
|
||||||
bool
|
{
|
||||||
isStopped() const;
|
return state_.load(std::memory_order_acquire) == state::stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the number of threads that this job queue is configured with. */
|
||||||
|
unsigned int
|
||||||
|
thread_count() const noexcept
|
||||||
|
{
|
||||||
|
return m_workers.count();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Coro;
|
friend class Coro;
|
||||||
|
|
||||||
using JobDataMap = std::map<JobType, JobTypeData>;
|
struct Data
|
||||||
|
{
|
||||||
|
int waiting = 0;
|
||||||
|
int running = 0;
|
||||||
|
int deferred = 0;
|
||||||
|
|
||||||
|
beast::insight::Event dequeue;
|
||||||
|
beast::insight::Event execute;
|
||||||
|
|
||||||
|
LoadMonitor load;
|
||||||
|
|
||||||
|
Data(
|
||||||
|
JobTypeInfo const& info,
|
||||||
|
beast::insight::Collector::ptr const& collector,
|
||||||
|
Logs& logs)
|
||||||
|
: load(
|
||||||
|
info.averageLatency,
|
||||||
|
info.peakLatency,
|
||||||
|
logs.journal("LoadMonitor"))
|
||||||
|
{
|
||||||
|
if (!info.special())
|
||||||
|
{
|
||||||
|
dequeue = collector->make_event(std::string(info.name) + "_q");
|
||||||
|
execute = collector->make_event(std::string(info.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Data(Data const&) = delete;
|
||||||
|
Data&
|
||||||
|
operator=(Data const&) = delete;
|
||||||
|
Data(Data&&) = delete;
|
||||||
|
Data&
|
||||||
|
operator=(Data&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
beast::Journal m_journal;
|
beast::Journal m_journal;
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
std::uint64_t m_lastJob;
|
std::uint64_t m_lastJob = 0;
|
||||||
std::set<Job> m_jobSet;
|
std::set<Job> m_jobSet;
|
||||||
JobCounter jobCounter_;
|
JobCounter jobCounter_;
|
||||||
std::atomic_bool stopping_{false};
|
|
||||||
std::atomic_bool stopped_{false};
|
|
||||||
JobDataMap m_jobData;
|
|
||||||
JobTypeData m_invalidJobData;
|
|
||||||
|
|
||||||
// The number of jobs currently in processTask()
|
enum class state : std::uint8_t { running = 0, stopping = 1, stopped = 2 };
|
||||||
int m_processCount;
|
|
||||||
|
std::atomic<state> state_ = state::running;
|
||||||
|
|
||||||
|
std::array<Data, jobTypes.size()> data_;
|
||||||
|
|
||||||
|
int activeThreads_ = 0;
|
||||||
|
|
||||||
|
// The number of coroutines (active or suspended)
|
||||||
|
std::atomic<int> totalCoroutines_ = 0;
|
||||||
|
|
||||||
// The number of suspended coroutines
|
// The number of suspended coroutines
|
||||||
int nSuspend_ = 0;
|
std::atomic<int> suspendedCoroutines_ = 0;
|
||||||
|
|
||||||
Workers m_workers;
|
Workers m_workers;
|
||||||
|
|
||||||
// Statistics tracking
|
|
||||||
perf::PerfLog& perfLog_;
|
perf::PerfLog& perfLog_;
|
||||||
|
// Statistics tracking
|
||||||
beast::insight::Collector::ptr m_collector;
|
beast::insight::Collector::ptr m_collector;
|
||||||
beast::insight::Gauge job_count;
|
beast::insight::Gauge job_count;
|
||||||
|
beast::insight::Gauge activeThreadsGauge_;
|
||||||
beast::insight::Hook hook;
|
beast::insight::Hook hook;
|
||||||
|
|
||||||
std::condition_variable cv_;
|
std::condition_variable cv_;
|
||||||
|
|
||||||
void
|
void
|
||||||
collect();
|
collect();
|
||||||
JobTypeData&
|
|
||||||
getJobTypeData(JobType type);
|
|
||||||
|
|
||||||
// Adds a reference counted job to the JobQueue.
|
// Adds a reference counted job to the JobQueue.
|
||||||
//
|
//
|
||||||
@@ -282,46 +337,7 @@ private:
|
|||||||
//
|
//
|
||||||
// return true if func added to queue.
|
// return true if func added to queue.
|
||||||
bool
|
bool
|
||||||
addRefCountedJob(
|
addRefCountedJob(JobType type, std::string const& name, JobFunction func);
|
||||||
JobType type,
|
|
||||||
std::string const& name,
|
|
||||||
JobFunction const& func);
|
|
||||||
|
|
||||||
// Returns the next Job we should run now.
|
|
||||||
//
|
|
||||||
// RunnableJob:
|
|
||||||
// A Job in the JobSet whose slots count for its type is greater than zero.
|
|
||||||
//
|
|
||||||
// Pre-conditions:
|
|
||||||
// mJobSet must not be empty.
|
|
||||||
// mJobSet holds at least one RunnableJob
|
|
||||||
//
|
|
||||||
// Post-conditions:
|
|
||||||
// job is a valid Job object.
|
|
||||||
// job is removed from mJobQueue.
|
|
||||||
// Waiting job count of its type is decremented
|
|
||||||
// Running job count of its type is incremented
|
|
||||||
//
|
|
||||||
// Invariants:
|
|
||||||
// The calling thread owns the JobLock
|
|
||||||
void
|
|
||||||
getNextJob(Job& job);
|
|
||||||
|
|
||||||
// Indicates that a running Job has completed its task.
|
|
||||||
//
|
|
||||||
// Pre-conditions:
|
|
||||||
// Job must not exist in mJobSet.
|
|
||||||
// The JobType must not be invalid.
|
|
||||||
//
|
|
||||||
// Post-conditions:
|
|
||||||
// The running count of that JobType is decremented
|
|
||||||
// A new task is signaled if there are more waiting Jobs than the limit, if
|
|
||||||
// any.
|
|
||||||
//
|
|
||||||
// Invariants:
|
|
||||||
// <none>
|
|
||||||
void
|
|
||||||
finishJob(JobType type);
|
|
||||||
|
|
||||||
// Runs the next appropriate waiting Job.
|
// Runs the next appropriate waiting Job.
|
||||||
//
|
//
|
||||||
@@ -334,13 +350,14 @@ private:
|
|||||||
// Invariants:
|
// Invariants:
|
||||||
// <none>
|
// <none>
|
||||||
void
|
void
|
||||||
processTask(int instance) override;
|
processTask(unsigned int instance) override;
|
||||||
|
|
||||||
// Returns the limit of running jobs for the given job type.
|
// Called when an uncaught exception occurs while processing a task
|
||||||
// For jobs with no limit, we return the largest int. Hopefully that
|
//
|
||||||
// will be enough.
|
// Invariants:
|
||||||
int
|
// <none>
|
||||||
getJobLimit(JobType type);
|
void
|
||||||
|
uncaughtException(unsigned int instance, std::exception_ptr eptr) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -354,11 +371,11 @@ private:
|
|||||||
coro_ member is initialized (a boost::coroutines::pull_type), execution
|
coro_ member is initialized (a boost::coroutines::pull_type), execution
|
||||||
automatically passes to the coroutine, which we don't want at this point,
|
automatically passes to the coroutine, which we don't want at this point,
|
||||||
since we are still in the handler thread context. It's important to note
|
since we are still in the handler thread context. It's important to note
|
||||||
here that construction of a boost pull_type automatically passes execution to
|
here that construction of a boost pull_type automatically passes execution
|
||||||
the coroutine. A pull_type object automatically generates a push_type that is
|
to the coroutine. A pull_type object automatically generates a push_type
|
||||||
passed as a parameter (do_yield) in the signature of the function the
|
that is passed as a parameter (do_yield) in the signature of the function
|
||||||
pull_type was created with. This function is immediately called during coro_
|
the pull_type was created with. This function is immediately called during
|
||||||
construction and within it, Coro::yield_ is assigned the push_type
|
coro_ construction and within it, Coro::yield_ is assigned the push_type
|
||||||
parameter (do_yield) address and called (yield()) so we can return execution
|
parameter (do_yield) address and called (yield()) so we can return execution
|
||||||
back to the caller's stack.
|
back to the caller's stack.
|
||||||
|
|
||||||
@@ -385,19 +402,20 @@ private:
|
|||||||
|
|
||||||
The race condition occurs as follows:
|
The race condition occurs as follows:
|
||||||
|
|
||||||
1- The coroutine is running.
|
1. The coroutine is running.
|
||||||
2- The coroutine is about to suspend, but before it can do so, it must
|
2. The coroutine is about to suspend, but before it can do so, it must
|
||||||
arrange for some event to wake it up.
|
arrange for some event to wake it up.
|
||||||
3- The coroutine arranges for some event to wake it up.
|
3. The coroutine arranges for some event to wake it up.
|
||||||
4- Before the coroutine can suspend, that event occurs and the
|
4. Before the coroutine can suspend, that event occurs and the
|
||||||
resumption of the coroutine is scheduled on the job queue. 5- Again, before
|
resumption of the coroutine is scheduled on the job queue.
|
||||||
the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
|
5. Again, before the coroutine can suspend, the resumption of the coroutine
|
||||||
Again, before the coroutine can suspend, the resumption code runs the
|
is dispatched.
|
||||||
coroutine.
|
6. Again, before the coroutine can suspend, the resumption code runs the
|
||||||
The coroutine is now running in two threads.
|
coroutine.
|
||||||
|
|
||||||
The lock prevents this from happening as step 6 will block until the
|
The coroutine is now running in two threads. The lock prevents this from
|
||||||
lock is released which only happens after the coroutine completes.
|
happening as step 6 will block until the lock is released which only
|
||||||
|
happens after the coroutine completes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -410,12 +428,12 @@ template <class F>
|
|||||||
std::shared_ptr<JobQueue::Coro>
|
std::shared_ptr<JobQueue::Coro>
|
||||||
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
||||||
{
|
{
|
||||||
/* First param is a detail type to make construction private.
|
if (state_.load(std::memory_order_acquire) != state::running)
|
||||||
Last param is the function the coroutine runs. Signature of
|
return nullptr;
|
||||||
void(std::shared_ptr<Coro>).
|
|
||||||
*/
|
|
||||||
auto coro = std::make_shared<Coro>(
|
auto coro = std::make_shared<Coro>(
|
||||||
Coro_create_t{}, *this, t, name, std::forward<F>(f));
|
CoroCreator{}, *this, t, name, std::forward<F>(f));
|
||||||
|
|
||||||
if (!coro->post())
|
if (!coro->post())
|
||||||
{
|
{
|
||||||
// The Coro was not successfully posted. Disable it so it's destructor
|
// The Coro was not successfully posted. Disable it so it's destructor
|
||||||
@@ -423,6 +441,7 @@ JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
|||||||
coro->expectEarlyExit();
|
coro->expectEarlyExit();
|
||||||
coro.reset();
|
coro.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
return coro;
|
return coro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
#define RIPPLE_CORE_JOBTYPES_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpld/core/Job.h>
|
#include <xrpld/core/Job.h>
|
||||||
#include <xrpld/core/JobTypeInfo.h>
|
#include <array>
|
||||||
#include <map>
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class JobTypes
|
/** Defines the fixed attributes of a particular type of job.
|
||||||
|
|
||||||
|
These attributes are defined at compile time and never change. They
|
||||||
|
include things like the name of the job, the limit of concurrent jobs
|
||||||
|
of this type, etc.
|
||||||
|
*/
|
||||||
|
struct JobTypeInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Map = std::map<JobType, JobTypeInfo>;
|
JobType const type;
|
||||||
using const_iterator = Map::const_iterator;
|
|
||||||
|
|
||||||
private:
|
/// The job's display name
|
||||||
JobTypes()
|
std::string_view const name;
|
||||||
: m_unknown(
|
|
||||||
jtINVALID,
|
/** The limit on the number of running jobs for this job type.
|
||||||
"invalid",
|
|
||||||
0,
|
A limit of 0 marks this as a "special job" which is not
|
||||||
std::chrono::milliseconds{0},
|
dispatched via the job queue.
|
||||||
std::chrono::milliseconds{0})
|
*/
|
||||||
|
int const limit;
|
||||||
|
|
||||||
|
/** Average and peak latencies for this job type, if relevant. */
|
||||||
|
/** @{ */
|
||||||
|
std::optional<std::chrono::milliseconds> const averageLatency;
|
||||||
|
std::optional<std::chrono::milliseconds> const peakLatency;
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
// Not default constructible
|
||||||
|
JobTypeInfo() = delete;
|
||||||
|
|
||||||
|
constexpr JobTypeInfo(
|
||||||
|
JobType type_,
|
||||||
|
std::string_view const name_,
|
||||||
|
int limit_,
|
||||||
|
std::optional<std::chrono::milliseconds> averageLatency_ = {},
|
||||||
|
std::optional<std::chrono::milliseconds> peakLatency_ = {})
|
||||||
|
: type(type_)
|
||||||
|
, name(name_)
|
||||||
|
, limit(limit_)
|
||||||
|
, averageLatency(averageLatency_)
|
||||||
|
, peakLatency(peakLatency_)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
}
|
||||||
int maxLimit = std::numeric_limits<int>::max();
|
|
||||||
|
|
||||||
auto add = [this](
|
bool
|
||||||
JobType jt,
|
special() const
|
||||||
std::string name,
|
{
|
||||||
int limit,
|
return limit == 0;
|
||||||
std::chrono::milliseconds avgLatency,
|
}
|
||||||
std::chrono::milliseconds peakLatency) {
|
};
|
||||||
XRPL_ASSERT(
|
|
||||||
m_map.find(jt) == m_map.end(),
|
|
||||||
"ripple::JobTypes::JobTypes::add : unique job type input");
|
|
||||||
|
|
||||||
[[maybe_unused]] auto const inserted =
|
inline auto constexpr jobTypes = []() consteval {
|
||||||
m_map
|
using namespace std::chrono_literals;
|
||||||
.emplace(
|
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(jt),
|
|
||||||
std::forward_as_tuple(
|
|
||||||
jt, name, limit, avgLatency, peakLatency))
|
|
||||||
.second;
|
|
||||||
|
|
||||||
XRPL_ASSERT(
|
|
||||||
inserted == true,
|
|
||||||
"ripple::JobTypes::JobTypes::add : input is inserted");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
constexpr JobTypeInfo jobs[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// avg peak
|
// avg peak
|
||||||
// JobType name limit latency latency
|
// JobType name limit latency latency
|
||||||
add(jtPACK, "makeFetchPack", 1, 0ms, 0ms);
|
{ jtPACK, "makeFetchPack", 1, },
|
||||||
add(jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms);
|
{ jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms },
|
||||||
add(jtVALIDATION_ut, "untrustedValidation", maxLimit, 2000ms, 5000ms);
|
{ jtCLIENT, "clientCommand", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtMANIFEST, "manifest", maxLimit, 2000ms, 5000ms);
|
{ jtCLIENT_SUBSCRIBE, "clientSubscribe", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtTRANSACTION_l, "localTransaction", maxLimit, 100ms, 500ms);
|
{ jtCLIENT_FEE_CHANGE, "clientFeeChange", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms);
|
{ jtCLIENT_CONSENSUS, "clientConsensus", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtLEDGER_REQ, "ledgerRequest", 3, 0ms, 0ms);
|
{ jtCLIENT_ACCT_HIST, "clientAccountHistory", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtPROPOSAL_ut, "untrustedProposal", maxLimit, 500ms, 1250ms);
|
|
||||||
add(jtREPLAY_TASK, "ledgerReplayTask", maxLimit, 0ms, 0ms);
|
|
||||||
add(jtLEDGER_DATA, "ledgerData", 3, 0ms, 0ms);
|
|
||||||
add(jtCLIENT, "clientCommand", maxLimit, 2000ms, 5000ms);
|
|
||||||
add(jtCLIENT_SUBSCRIBE, "clientSubscribe", maxLimit, 2000ms, 5000ms);
|
|
||||||
add(jtCLIENT_FEE_CHANGE, "clientFeeChange", maxLimit, 2000ms, 5000ms);
|
|
||||||
add(jtCLIENT_CONSENSUS, "clientConsensus", maxLimit, 2000ms, 5000ms);
|
|
||||||
add(jtCLIENT_ACCT_HIST, "clientAccountHistory", maxLimit, 2000ms, 5000ms);
|
|
||||||
add(jtCLIENT_RPC, "clientRPC", maxLimit, 2000ms, 5000ms);
|
|
||||||
// Web traffic from pwa ports: at most two workers, and no latency
|
// Web traffic from pwa ports: at most two workers, and no latency
|
||||||
// targets, so that it can never move the node's load factor.
|
// targets, so that it can never move the node's load factor.
|
||||||
add(jtCLIENT_PWA, "clientPWA", 2, 0ms, 0ms);
|
{ jtCLIENT_PWA, "clientPWA", 2, },
|
||||||
add(jtCLIENT_PWADNS, "clientPWADNS", 2, 0ms, 0ms);
|
{ jtCLIENT_PWADNS, "clientPWADNS", 2, },
|
||||||
add(jtCLIENT_WEBSOCKET, "clientWebsocket", maxLimit, 2000ms, 5000ms);
|
{ jtCLIENT_RPC, "clientRPC", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtRPC, "RPC", maxLimit, 0ms, 0ms);
|
{ jtCLIENT_WEBSOCKET, "clientWebsocket", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtUPDATE_PF, "updatePaths", 1, 0ms, 0ms);
|
{ jtRPC, "RPC", std::numeric_limits<int>::max(), },
|
||||||
add(jtTRANSACTION, "transaction", maxLimit, 250ms, 1000ms);
|
{ jtSWEEP, "sweep", 1, },
|
||||||
add(jtBATCH, "batch", maxLimit, 250ms, 1000ms);
|
{ jtVALIDATION_ut, "untrustedValidation", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtADVANCE, "advanceLedger", maxLimit, 0ms, 0ms);
|
{ jtMANIFEST, "manifest", std::numeric_limits<int>::max(), 2000ms, 5000ms },
|
||||||
add(jtPUBLEDGER, "publishNewLedger", maxLimit, 3000ms, 4500ms);
|
{ jtUPDATE_PF, "updatePaths", 1, },
|
||||||
add(jtTXN_DATA, "fetchTxnData", 5, 0ms, 0ms);
|
{ jtTRANSACTION_l, "localTransaction", std::numeric_limits<int>::max(), 100ms, 500ms },
|
||||||
add(jtWAL, "writeAhead", maxLimit, 1000ms, 2500ms);
|
{ jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms },
|
||||||
add(jtVALIDATION_t, "trustedValidation", maxLimit, 500ms, 1500ms);
|
{ jtLEDGER_REQ, "ledgerRequest", 3, },
|
||||||
add(jtWRITE, "writeObjects", maxLimit, 1750ms, 2500ms);
|
{ jtPROPOSAL_ut, "untrustedProposal", std::numeric_limits<int>::max(), 500ms, 1250ms },
|
||||||
add(jtACCEPT, "acceptLedger", maxLimit, 0ms, 0ms);
|
{ jtREPLAY_TASK, "ledgerReplayTask", std::numeric_limits<int>::max(), },
|
||||||
add(jtPROPOSAL_t, "trustedProposal", maxLimit, 100ms, 500ms);
|
{ jtTRANSACTION, "transaction", std::numeric_limits<int>::max(), 250ms, 1000ms },
|
||||||
add(jtSWEEP, "sweep", 1, 0ms, 0ms);
|
{ jtMISSING_TXN, "handleHaveTransactions", 1200, },
|
||||||
add(jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms);
|
{ jtREQUESTED_TXN, "doTransactions", 1200, },
|
||||||
add(jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms);
|
{ jtBATCH, "batch", std::numeric_limits<int>::max(), 250ms, 1000ms },
|
||||||
add(jtADMIN, "administration", maxLimit, 0ms, 0ms);
|
{ jtLEDGER_DATA, "ledgerData", 3, },
|
||||||
add(jtMISSING_TXN, "handleHaveTransactions", 1200, 0ms, 0ms);
|
{ jtADVANCE, "advanceLedger", std::numeric_limits<int>::max(), },
|
||||||
add(jtREQUESTED_TXN, "doTransactions", 1200, 0ms, 0ms);
|
{ jtPUBLEDGER, "publishNewLedger", std::numeric_limits<int>::max(), 3000ms, 4500ms },
|
||||||
|
{ jtTXN_DATA, "fetchTxnData", 5, },
|
||||||
add(jtPEER, "peerCommand", 0, 200ms, 2500ms);
|
{ jtWAL, "writeAhead", std::numeric_limits<int>::max(), 1000ms, 2500ms },
|
||||||
add(jtDISK, "diskAccess", 0, 500ms, 1000ms);
|
{ jtVALIDATION_t, "trustedValidation", std::numeric_limits<int>::max(), 500ms, 1500ms },
|
||||||
add(jtTXN_PROC, "processTransaction", 0, 0ms, 0ms);
|
{ jtWRITE, "writeObjects", std::numeric_limits<int>::max(), 1750ms, 2500ms },
|
||||||
add(jtOB_SETUP, "orderBookSetup", 0, 0ms, 0ms);
|
{ jtACCEPT, "acceptLedger", std::numeric_limits<int>::max(), },
|
||||||
add(jtPATH_FIND, "pathFind", 0, 0ms, 0ms);
|
{ jtPROPOSAL_t, "trustedProposal", std::numeric_limits<int>::max(), 100ms, 500ms },
|
||||||
add(jtHO_READ, "nodeRead", 0, 0ms, 0ms);
|
{ jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms },
|
||||||
add(jtHO_WRITE, "nodeWrite", 0, 0ms, 0ms);
|
{ jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms },
|
||||||
add(jtGENERIC, "generic", 0, 0ms, 0ms);
|
{ jtADMIN, "administration", std::numeric_limits<int>::max(), },
|
||||||
add(jtNS_SYNC_READ, "SyncReadNode", 0, 0ms, 0ms);
|
{ jtPEER, "peerCommand", 0, 200ms, 2500ms },
|
||||||
add(jtNS_ASYNC_READ, "AsyncReadNode", 0, 0ms, 0ms);
|
{ jtDISK, "diskAccess", 0, 500ms, 1000ms },
|
||||||
add(jtNS_WRITE, "WriteNode", 0, 0ms, 0ms);
|
{ jtTXN_PROC, "processTransaction", 0 },
|
||||||
|
{ jtOB_SETUP, "orderBookSetup", 0 },
|
||||||
|
{ jtPATH_FIND, "pathFind", 0 },
|
||||||
|
{ jtHO_READ, "nodeRead", 0 },
|
||||||
|
{ jtHO_WRITE, "nodeWrite", 0 },
|
||||||
|
{ jtGENERIC, "generic", 0 },
|
||||||
|
{ jtNS_SYNC_READ, "SyncReadNode", 0 },
|
||||||
|
{ jtNS_ASYNC_READ, "AsyncReadNode", 0 },
|
||||||
|
{ jtNS_WRITE, "WriteNode", 0 }
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
};
|
||||||
|
|
||||||
public:
|
// Ensure that the order of entries in the table corresponds to the
|
||||||
static JobTypes const&
|
// order of entries in the enum:
|
||||||
instance()
|
std::underlying_type_t<JobType> idx = 0;
|
||||||
|
|
||||||
|
for (auto const& jt : jobs)
|
||||||
{
|
{
|
||||||
static JobTypes const types;
|
if (static_cast<std::underlying_type_t<JobType>>(jt.type) != idx)
|
||||||
return types;
|
throw "The JobTypes array is improperly ordered";
|
||||||
|
|
||||||
|
++idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string const&
|
return std::to_array(jobs);
|
||||||
name(JobType jt)
|
}();
|
||||||
{
|
|
||||||
return instance().get(jt).name();
|
|
||||||
}
|
|
||||||
|
|
||||||
JobTypeInfo const&
|
|
||||||
get(JobType jt) const
|
|
||||||
{
|
|
||||||
Map::const_iterator const iter(m_map.find(jt));
|
|
||||||
XRPL_ASSERT(iter != m_map.end(), "ripple::JobTypes::get : valid input");
|
|
||||||
|
|
||||||
if (iter != m_map.end())
|
|
||||||
return iter->second;
|
|
||||||
|
|
||||||
return m_unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
JobTypeInfo const&
|
|
||||||
getInvalid() const
|
|
||||||
{
|
|
||||||
return m_unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map::size_type
|
|
||||||
size() const
|
|
||||||
{
|
|
||||||
return m_map.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
begin() const
|
|
||||||
{
|
|
||||||
return m_map.cbegin();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cbegin() const
|
|
||||||
{
|
|
||||||
return m_map.cbegin();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
end() const
|
|
||||||
{
|
|
||||||
return m_map.cend();
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator
|
|
||||||
cend() const
|
|
||||||
{
|
|
||||||
return m_map.cend();
|
|
||||||
}
|
|
||||||
|
|
||||||
JobTypeInfo m_unknown;
|
|
||||||
Map m_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
+23
-26
@@ -20,24 +20,27 @@
|
|||||||
#ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED
|
#ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED
|
||||||
#define RIPPLE_CORE_LOADEVENT_H_INCLUDED
|
#define RIPPLE_CORE_LOADEVENT_H_INCLUDED
|
||||||
|
|
||||||
|
#include <xrpld/core/LoadMonitor.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class LoadMonitor;
|
|
||||||
|
|
||||||
// VFALCO NOTE What is the difference between a LoadEvent and a LoadMonitor?
|
|
||||||
// VFALCO TODO Rename LoadEvent to ScopedLoadSample
|
// VFALCO TODO Rename LoadEvent to ScopedLoadSample
|
||||||
//
|
|
||||||
// This looks like a scoped elapsed time measuring class
|
|
||||||
//
|
|
||||||
class LoadEvent
|
class LoadEvent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// VFALCO TODO remove the dependency on LoadMonitor. Is that possible?
|
LoadEvent(
|
||||||
LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart);
|
std::reference_wrapper<LoadSampler const> callback,
|
||||||
|
std::string name,
|
||||||
|
bool shouldStart);
|
||||||
|
|
||||||
|
LoadEvent(LoadEvent&& other);
|
||||||
|
LoadEvent&
|
||||||
|
operator=(LoadEvent&& other);
|
||||||
|
|
||||||
|
LoadEvent&
|
||||||
|
operator=(LoadEvent const&) = delete;
|
||||||
LoadEvent(LoadEvent const&) = delete;
|
LoadEvent(LoadEvent const&) = delete;
|
||||||
|
|
||||||
~LoadEvent();
|
~LoadEvent();
|
||||||
@@ -45,17 +48,6 @@ public:
|
|||||||
std::string const&
|
std::string const&
|
||||||
name() const;
|
name() const;
|
||||||
|
|
||||||
// The time spent waiting.
|
|
||||||
std::chrono::steady_clock::duration
|
|
||||||
waitTime() const;
|
|
||||||
|
|
||||||
// The time spent running.
|
|
||||||
std::chrono::steady_clock::duration
|
|
||||||
runTime() const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setName(std::string const& name);
|
|
||||||
|
|
||||||
// Start the measurement. If already started, then
|
// Start the measurement. If already started, then
|
||||||
// restart, assigning the elapsed time to the "waiting"
|
// restart, assigning the elapsed time to the "waiting"
|
||||||
// state.
|
// state.
|
||||||
@@ -69,20 +61,25 @@ public:
|
|||||||
stop();
|
stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LoadMonitor& monitor_;
|
// The name for this event.
|
||||||
|
|
||||||
// Represents our current state
|
|
||||||
bool running_;
|
|
||||||
|
|
||||||
// The name associated with this event, if any.
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
|
||||||
|
// The callback to invoke when we stop. This will only
|
||||||
|
// be invoked if `neutered_` is `false`.
|
||||||
|
std::reference_wrapper<LoadSampler const> callback_;
|
||||||
|
|
||||||
// Represents the time we last transitioned states
|
// Represents the time we last transitioned states
|
||||||
std::chrono::steady_clock::time_point mark_;
|
std::chrono::steady_clock::time_point mark_;
|
||||||
|
|
||||||
// The time we spent waiting and running respectively
|
// The time we spent waiting and running respectively
|
||||||
std::chrono::steady_clock::duration timeWaiting_;
|
std::chrono::steady_clock::duration timeWaiting_;
|
||||||
std::chrono::steady_clock::duration timeRunning_;
|
std::chrono::steady_clock::duration timeRunning_;
|
||||||
|
|
||||||
|
// Represents our current state
|
||||||
|
bool running_;
|
||||||
|
|
||||||
|
// Determines whether the callback should be invoked
|
||||||
|
bool neutered_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -20,69 +20,82 @@
|
|||||||
#ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED
|
#ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED
|
||||||
#define RIPPLE_CORE_LOADMONITOR_H_INCLUDED
|
#define RIPPLE_CORE_LOADMONITOR_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpld/core/LoadEvent.h>
|
|
||||||
#include <xrpl/basics/UptimeClock.h>
|
#include <xrpl/basics/UptimeClock.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
// Monitors load levels and response times
|
using LoadSampler = std::function<void(
|
||||||
|
char const*,
|
||||||
|
std::chrono::steady_clock::duration,
|
||||||
|
std::chrono::steady_clock::duration)>;
|
||||||
|
|
||||||
// VFALCO TODO Rename this. Having both LoadManager and LoadMonitor is
|
/** Monitors load levels and response times
|
||||||
// confusing.
|
|
||||||
//
|
@todo Consider renaming this, since having both a LoadManager and a
|
||||||
|
LoadMonitor is confusing, especially considering that they do
|
||||||
|
different things.
|
||||||
|
*/
|
||||||
class LoadMonitor
|
class LoadMonitor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit LoadMonitor(beast::Journal j);
|
struct Stats
|
||||||
|
{
|
||||||
|
std::uint64_t count = 0;
|
||||||
|
|
||||||
void
|
std::chrono::milliseconds averageLatency{};
|
||||||
addLoadSample(LoadEvent const& sample);
|
std::chrono::milliseconds peakLatency{};
|
||||||
|
|
||||||
|
bool isOverloaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit LoadMonitor(
|
||||||
|
std::optional<std::chrono::milliseconds> targetAverageLatency,
|
||||||
|
std::optional<std::chrono::milliseconds> targetPeakLatency,
|
||||||
|
beast::Journal j);
|
||||||
|
|
||||||
|
LoadMonitor(LoadMonitor const& other) = delete;
|
||||||
|
LoadMonitor&
|
||||||
|
operator=(LoadMonitor const& other) = delete;
|
||||||
|
|
||||||
|
LoadMonitor(LoadMonitor&& other) = delete;
|
||||||
|
LoadMonitor&
|
||||||
|
operator=(LoadMonitor&& other) = delete;
|
||||||
|
|
||||||
void
|
void
|
||||||
addSamples(int count, std::chrono::milliseconds latency);
|
addSamples(int count, std::chrono::milliseconds latency);
|
||||||
|
|
||||||
void
|
|
||||||
setTargetLatency(
|
|
||||||
std::chrono::milliseconds avg,
|
|
||||||
std::chrono::milliseconds pk);
|
|
||||||
|
|
||||||
bool
|
|
||||||
isOverTarget(std::chrono::milliseconds avg, std::chrono::milliseconds peak);
|
|
||||||
|
|
||||||
// VFALCO TODO make this return the values in a struct.
|
|
||||||
struct Stats
|
|
||||||
{
|
|
||||||
Stats();
|
|
||||||
|
|
||||||
std::uint64_t count;
|
|
||||||
std::chrono::milliseconds latencyAvg;
|
|
||||||
std::chrono::milliseconds latencyPeak;
|
|
||||||
bool isOverloaded;
|
|
||||||
};
|
|
||||||
|
|
||||||
Stats
|
Stats
|
||||||
getStats();
|
getStats();
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isOver();
|
isOver();
|
||||||
|
|
||||||
|
/** A reference to a function object used to report performance events. */
|
||||||
|
std::reference_wrapper<LoadSampler const>
|
||||||
|
sample()
|
||||||
|
{
|
||||||
|
return std::cref(eventCallback_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
void
|
||||||
update();
|
update();
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|
||||||
std::uint64_t mCounts;
|
std::uint64_t samples_ = 0;
|
||||||
int mLatencyEvents;
|
std::chrono::milliseconds averageLatency_{};
|
||||||
std::chrono::milliseconds mLatencyMSAvg;
|
std::chrono::milliseconds peakLatency_{};
|
||||||
std::chrono::milliseconds mLatencyMSPeak;
|
std::optional<std::chrono::milliseconds> targetAverageLatency_{};
|
||||||
std::chrono::milliseconds mTargetLatencyAvg;
|
std::optional<std::chrono::milliseconds> targetPeakLatency_{};
|
||||||
std::chrono::milliseconds mTargetLatencyPk;
|
UptimeClock::time_point lastUpdate_;
|
||||||
UptimeClock::time_point mLastUpdate;
|
|
||||||
beast::Journal const j_;
|
beast::Journal const j_;
|
||||||
|
LoadSampler eventCallback_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -367,15 +367,15 @@ Config::setup(
|
|||||||
strXdgDataHome = strHome + "/.local/share";
|
strXdgDataHome = strHome + "/.local/share";
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_DIR = strXdgConfigHome + "/" + systemName();
|
CONFIG_DIR = strXdgConfigHome + "/" + std::string(systemName);
|
||||||
CONFIG_FILE = CONFIG_DIR / strConfFile;
|
CONFIG_FILE = CONFIG_DIR / strConfFile;
|
||||||
dataDir = strXdgDataHome + "/" + systemName();
|
dataDir = strXdgDataHome + "/" + std::string(systemName);
|
||||||
|
|
||||||
if (!boost::filesystem::exists(CONFIG_FILE))
|
if (!boost::filesystem::exists(CONFIG_FILE))
|
||||||
{
|
{
|
||||||
CONFIG_DIR = "/etc/opt/" + systemName();
|
CONFIG_DIR = "/etc/opt/" + std::string(systemName);
|
||||||
CONFIG_FILE = CONFIG_DIR / strConfFile;
|
CONFIG_FILE = CONFIG_DIR / strConfFile;
|
||||||
dataDir = "/var/opt/" + systemName();
|
dataDir = "/var/opt/" + std::string(systemName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,101 +23,60 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
Job::Job() : mType(jtINVALID), mJobIndex(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Job::Job(JobType type, std::uint64_t index) : mType(type), mJobIndex(index)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Job::Job(
|
Job::Job(
|
||||||
JobType type,
|
JobType type,
|
||||||
std::string const& name,
|
std::string name,
|
||||||
std::uint64_t index,
|
std::uint64_t index,
|
||||||
LoadMonitor& lm,
|
std::reference_wrapper<LoadSampler const> sample,
|
||||||
std::function<void()> const& job)
|
std::function<void()> job)
|
||||||
: mType(type)
|
: type_(type)
|
||||||
, mJobIndex(index)
|
, index_(index)
|
||||||
, mJob(job)
|
, queued_(clock_type::now())
|
||||||
, mName(name)
|
, work_(std::move(job))
|
||||||
, m_queue_time(clock_type::now())
|
, loadEvent_(sample, std::move(name), false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Job::Job(Job&& other)
|
||||||
|
: type_(other.type_)
|
||||||
|
, index_(other.index_)
|
||||||
|
, queued_(other.queued_)
|
||||||
|
, work_(std::move(other.work_))
|
||||||
|
, loadEvent_(std::move(other.loadEvent_))
|
||||||
|
|
||||||
{
|
{
|
||||||
m_loadEvent = std::make_shared<LoadEvent>(std::ref(lm), name, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JobType
|
JobType
|
||||||
Job::getType() const
|
Job::getType() const
|
||||||
{
|
{
|
||||||
return mType;
|
return type_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Job::clock_type::time_point const&
|
Job::clock_type::time_point const&
|
||||||
Job::queue_time() const
|
Job::queue_time() const
|
||||||
{
|
{
|
||||||
return m_queue_time;
|
return queued_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const&
|
||||||
|
Job::description() const
|
||||||
|
{
|
||||||
|
return loadEvent_.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Job::doJob()
|
Job::execute()
|
||||||
{
|
{
|
||||||
beast::setCurrentThreadName("doJob: " + mName);
|
beast::setCurrentThreadName("Job: " + loadEvent_.name());
|
||||||
m_loadEvent->start();
|
|
||||||
m_loadEvent->setName(mName);
|
|
||||||
|
|
||||||
mJob();
|
loadEvent_.start();
|
||||||
|
|
||||||
|
work_();
|
||||||
|
|
||||||
// Destroy the lambda, otherwise we won't include
|
// Destroy the lambda, otherwise we won't include
|
||||||
// its duration in the time measurement
|
// its duration in the time measurement
|
||||||
mJob = nullptr;
|
work_ = nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Job::operator>(const Job& j) const
|
|
||||||
{
|
|
||||||
if (mType < j.mType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mType > j.mType)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return mJobIndex > j.mJobIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Job::operator>=(const Job& j) const
|
|
||||||
{
|
|
||||||
if (mType < j.mType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mType > j.mType)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return mJobIndex >= j.mJobIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Job::operator<(const Job& j) const
|
|
||||||
{
|
|
||||||
if (mType < j.mType)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mType > j.mType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return mJobIndex < j.mJobIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Job::operator<=(const Job& j) const
|
|
||||||
{
|
|
||||||
if (mType < j.mType)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mType > j.mType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return mJobIndex <= j.mJobIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
+178
-268
@@ -20,7 +20,10 @@
|
|||||||
#include <xrpld/core/JobQueue.h>
|
#include <xrpld/core/JobQueue.h>
|
||||||
#include <xrpld/perflog/PerfLog.h>
|
#include <xrpld/perflog/PerfLog.h>
|
||||||
#include <xrpl/basics/contract.h>
|
#include <xrpl/basics/contract.h>
|
||||||
|
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -31,36 +34,22 @@ JobQueue::JobQueue(
|
|||||||
Logs& logs,
|
Logs& logs,
|
||||||
perf::PerfLog& perfLog)
|
perf::PerfLog& perfLog)
|
||||||
: m_journal(journal)
|
: m_journal(journal)
|
||||||
, m_lastJob(0)
|
, data_([&]<std::size_t... I>(std::index_sequence<I...>) {
|
||||||
, m_invalidJobData(JobTypes::instance().getInvalid(), collector, logs)
|
return std::array<Data, sizeof...(I)>{
|
||||||
, m_processCount(0)
|
Data{jobTypes[I], collector, logs}...};
|
||||||
, m_workers(*this, &perfLog, "JobQueue", threadCount)
|
}(std::make_index_sequence<jobTypes.size()>()))
|
||||||
|
, m_workers(*this, "JobQueue", threadCount)
|
||||||
, perfLog_(perfLog)
|
, perfLog_(perfLog)
|
||||||
, m_collector(collector)
|
, m_collector(collector)
|
||||||
{
|
{
|
||||||
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
|
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
|
||||||
|
|
||||||
hook = m_collector->make_hook(std::bind(&JobQueue::collect, this));
|
// This is important to do before dispatching any jobs
|
||||||
|
// so that the logger knows the number of threads.
|
||||||
|
perfLog_.resizeJobs(threadCount);
|
||||||
|
|
||||||
|
hook = m_collector->make_hook([this] { collect(); });
|
||||||
job_count = m_collector->make_gauge("job_count");
|
job_count = m_collector->make_gauge("job_count");
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard lock(m_mutex);
|
|
||||||
|
|
||||||
for (auto const& x : JobTypes::instance())
|
|
||||||
{
|
|
||||||
JobTypeInfo const& jt = x.second;
|
|
||||||
|
|
||||||
// And create dynamic information for all jobs
|
|
||||||
auto const result(m_jobData.emplace(
|
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(jt.type()),
|
|
||||||
std::forward_as_tuple(jt, m_collector, logs)));
|
|
||||||
XRPL_ASSERT(
|
|
||||||
result.second == true,
|
|
||||||
"ripple::JobQueue::JobQueue : jobs added");
|
|
||||||
(void)result.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JobQueue::~JobQueue()
|
JobQueue::~JobQueue()
|
||||||
@@ -74,65 +63,42 @@ JobQueue::collect()
|
|||||||
{
|
{
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
job_count = m_jobSet.size();
|
job_count = m_jobSet.size();
|
||||||
|
activeThreadsGauge_ = activeThreads_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
JobQueue::addRefCountedJob(
|
JobQueue::addRefCountedJob(
|
||||||
JobType type,
|
JobType type,
|
||||||
std::string const& name,
|
std::string const& name,
|
||||||
JobFunction const& func)
|
JobFunction func)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
JLOG(m_journal.debug()) << "Adding job '" << name << "' (" << type << ")";
|
||||||
type != jtINVALID,
|
|
||||||
"ripple::JobQueue::addRefCountedJob : valid input job type");
|
|
||||||
|
|
||||||
auto iter(m_jobData.find(type));
|
|
||||||
XRPL_ASSERT(
|
|
||||||
iter != m_jobData.end(),
|
|
||||||
"ripple::JobQueue::addRefCountedJob : job type found in jobs");
|
|
||||||
if (iter == m_jobData.end())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
JLOG(m_journal.debug())
|
|
||||||
<< __func__ << " : Adding job : " << name << " : " << type;
|
|
||||||
JobTypeData& data(iter->second);
|
|
||||||
|
|
||||||
// FIXME: Workaround incorrect client shutdown ordering
|
// FIXME: Workaround incorrect client shutdown ordering
|
||||||
// do not add jobs to a queue with no threads
|
// do not add jobs to a queue with no threads
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
(type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) ||
|
(type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) ||
|
||||||
m_workers.getNumberOfThreads() > 0,
|
m_workers.count() > 0,
|
||||||
"ripple::JobQueue::addRefCountedJob : threads available or job "
|
"ripple::JobQueue::addRefCountedJob : threads available or job "
|
||||||
"requires no threads");
|
"requires no threads");
|
||||||
|
|
||||||
{
|
std::lock_guard lock(m_mutex);
|
||||||
std::lock_guard lock(m_mutex);
|
|
||||||
auto result =
|
|
||||||
m_jobSet.emplace(type, name, ++m_lastJob, data.load(), func);
|
|
||||||
auto const& job = *result.first;
|
|
||||||
|
|
||||||
JobType const type(job.getType());
|
auto& d = data_[type];
|
||||||
XRPL_ASSERT(
|
|
||||||
type != jtINVALID,
|
|
||||||
"ripple::JobQueue::addRefCountedJob : has valid job type");
|
|
||||||
XRPL_ASSERT(
|
|
||||||
m_jobSet.find(job) != m_jobSet.end(),
|
|
||||||
"ripple::JobQueue::addRefCountedJob : job found");
|
|
||||||
perfLog_.jobQueue(type);
|
|
||||||
|
|
||||||
JobTypeData& data(getJobTypeData(type));
|
auto r = m_jobSet.emplace(
|
||||||
|
type, name, ++m_lastJob, d.load.sample(), std::move(func));
|
||||||
|
assert(r.second && m_jobSet.find(*r.first) != m_jobSet.end());
|
||||||
|
|
||||||
|
perfLog_.jobQueue(type);
|
||||||
|
|
||||||
|
if (d.waiting + d.running < jobTypes[type].limit)
|
||||||
|
m_workers.addTask();
|
||||||
|
else
|
||||||
|
++d.deferred;
|
||||||
|
|
||||||
|
++d.waiting;
|
||||||
|
|
||||||
if (data.waiting + data.running < getJobLimit(type))
|
|
||||||
{
|
|
||||||
m_workers.addTask();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// defer the task until we go below the limit
|
|
||||||
++data.deferred;
|
|
||||||
}
|
|
||||||
++data.waiting;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,125 +106,100 @@ int
|
|||||||
JobQueue::getJobCount(JobType t) const
|
JobQueue::getJobCount(JobType t) const
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
|
return data_[t].waiting;
|
||||||
JobDataMap::const_iterator c = m_jobData.find(t);
|
|
||||||
|
|
||||||
return (c == m_jobData.end()) ? 0 : c->second.waiting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
JobQueue::getJobCountTotal(JobType t) const
|
JobQueue::getJobCountTotal(JobType t) const
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
|
auto const& d = data_[t];
|
||||||
JobDataMap::const_iterator c = m_jobData.find(t);
|
return d.waiting + d.running;
|
||||||
|
|
||||||
return (c == m_jobData.end()) ? 0 : (c->second.waiting + c->second.running);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
JobQueue::getJobCountGE(JobType t) const
|
JobQueue::getJobCountGE(JobType t) const
|
||||||
{
|
{
|
||||||
// return the number of jobs at this priority level or greater
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
|
|
||||||
for (auto const& x : m_jobData)
|
for (std::size_t i = 0; i < data_.size(); ++i)
|
||||||
{
|
{
|
||||||
if (x.first >= t)
|
if (static_cast<JobType>(i) >= t)
|
||||||
ret += x.second.waiting;
|
ret += data_[i].waiting;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<LoadEvent>
|
LoadEvent
|
||||||
JobQueue::makeLoadEvent(JobType t, std::string const& name)
|
JobQueue::makeLoadEvent(JobType t, std::string name)
|
||||||
{
|
{
|
||||||
JobDataMap::iterator iter(m_jobData.find(t));
|
return {data_[t].load.sample(), std::move(name), true};
|
||||||
XRPL_ASSERT(
|
|
||||||
iter != m_jobData.end(),
|
|
||||||
"ripple::JobQueue::makeLoadEvent : valid job type input");
|
|
||||||
|
|
||||||
if (iter == m_jobData.end())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return std::make_unique<LoadEvent>(iter->second.load(), name, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
|
JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
|
||||||
{
|
{
|
||||||
if (isStopped())
|
if (!isStopped()) [[likely]]
|
||||||
LogicError("JobQueue::addLoadEvents() called after JobQueue stopped");
|
data_[t].load.addSamples(count, elapsed);
|
||||||
|
|
||||||
JobDataMap::iterator iter(m_jobData.find(t));
|
|
||||||
XRPL_ASSERT(
|
|
||||||
iter != m_jobData.end(),
|
|
||||||
"ripple::JobQueue::addLoadEvents : valid job type input");
|
|
||||||
iter->second.load().addSamples(count, elapsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
JobQueue::isOverloaded()
|
JobQueue::isOverloaded()
|
||||||
{
|
{
|
||||||
return std::any_of(m_jobData.begin(), m_jobData.end(), [](auto& entry) {
|
return std::any_of(
|
||||||
return entry.second.load().isOver();
|
data_.begin(), data_.end(), [](auto& d) { return d.load.isOver(); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
JobQueue::getJson(int c)
|
JobQueue::getJson(int)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
Json::Value ret(Json::objectValue);
|
Json::Value ret(Json::objectValue);
|
||||||
|
|
||||||
ret["threads"] = m_workers.getNumberOfThreads();
|
ret["threads"] = m_workers.count();
|
||||||
|
ret["coro.suspended"] = suspendedCoroutines_.load();
|
||||||
|
ret["coro.total"] = totalCoroutines_.load();
|
||||||
|
|
||||||
Json::Value priorities = Json::arrayValue;
|
Json::Value priorities = Json::arrayValue;
|
||||||
|
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
|
|
||||||
for (auto& x : m_jobData)
|
for (std::size_t i = 0; i < data_.size(); ++i)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
auto const type = static_cast<JobType>(i);
|
||||||
x.first != jtINVALID, "ripple::JobQueue::getJson : valid job type");
|
|
||||||
|
|
||||||
if (x.first == jtGENERIC)
|
if (type == jtGENERIC)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
JobTypeData& data(x.second);
|
auto& d = data_[i];
|
||||||
|
auto const s = d.load.getStats();
|
||||||
|
|
||||||
LoadMonitor::Stats stats(data.stats());
|
if (s.count || d.waiting || d.running || (s.peakLatency != 0ms))
|
||||||
|
|
||||||
int waiting(data.waiting);
|
|
||||||
int running(data.running);
|
|
||||||
|
|
||||||
if ((stats.count != 0) || (waiting != 0) ||
|
|
||||||
(stats.latencyPeak != 0ms) || (running != 0))
|
|
||||||
{
|
{
|
||||||
Json::Value& pri = priorities.append(Json::objectValue);
|
Json::Value& pri = priorities.append(Json::objectValue);
|
||||||
|
|
||||||
pri["job_type"] = data.name();
|
pri["job_type"] = std::string(jobTypes[i].name);
|
||||||
|
|
||||||
if (stats.isOverloaded)
|
if (s.isOverloaded)
|
||||||
pri["over_target"] = true;
|
pri["over_target"] = true;
|
||||||
|
|
||||||
if (waiting != 0)
|
if (d.waiting != 0)
|
||||||
pri["waiting"] = waiting;
|
pri["waiting"] = d.waiting;
|
||||||
|
|
||||||
if (stats.count != 0)
|
if (s.count != 0)
|
||||||
pri["per_second"] = static_cast<int>(stats.count);
|
pri["per_second"] = static_cast<int>(s.count);
|
||||||
|
|
||||||
if (stats.latencyPeak != 0ms)
|
if (s.peakLatency != 0ms)
|
||||||
pri["peak_time"] = static_cast<int>(stats.latencyPeak.count());
|
pri["peak_time"] = static_cast<int>(s.peakLatency.count());
|
||||||
|
|
||||||
if (stats.latencyAvg != 0ms)
|
if (s.averageLatency != 0ms)
|
||||||
pri["avg_time"] = static_cast<int>(stats.latencyAvg.count());
|
pri["avg_time"] = static_cast<int>(s.averageLatency.count());
|
||||||
|
|
||||||
if (running != 0)
|
if (d.running != 0)
|
||||||
pri["in_progress"] = running;
|
pri["in_progress"] = d.running;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,179 +212,148 @@ void
|
|||||||
JobQueue::rendezvous()
|
JobQueue::rendezvous()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_mutex);
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
cv_.wait(lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
|
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
|
||||||
}
|
|
||||||
|
|
||||||
JobTypeData&
|
|
||||||
JobQueue::getJobTypeData(JobType type)
|
|
||||||
{
|
|
||||||
JobDataMap::iterator c(m_jobData.find(type));
|
|
||||||
XRPL_ASSERT(
|
|
||||||
c != m_jobData.end(),
|
|
||||||
"ripple::JobQueue::getJobTypeData : valid job type input");
|
|
||||||
|
|
||||||
// NIKB: This is ugly and I hate it. We must remove jtINVALID completely
|
|
||||||
// and use something sane.
|
|
||||||
if (c == m_jobData.end())
|
|
||||||
return m_invalidJobData;
|
|
||||||
|
|
||||||
return c->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
JobQueue::stop()
|
JobQueue::stop()
|
||||||
{
|
{
|
||||||
stopping_ = true;
|
if (state expected = state::running; !state_.compare_exchange_strong(
|
||||||
using namespace std::chrono_literals;
|
expected, state::stopping, std::memory_order_acq_rel))
|
||||||
jobCounter_.join("JobQueue", 1s, m_journal);
|
|
||||||
{
|
{
|
||||||
// After the JobCounter is joined, all jobs have finished executing
|
while (state_.load(std::memory_order_acquire) != state::stopped)
|
||||||
// (i.e. returned from `Job::doJob`) and no more are being accepted,
|
std::this_thread::yield();
|
||||||
// but there may still be some threads between the return of
|
return;
|
||||||
// `Job::doJob` and the return of `JobQueue::processTask`. That is why
|
|
||||||
// we must wait on the condition variable to make these assertions.
|
|
||||||
std::unique_lock<std::mutex> lock(m_mutex);
|
|
||||||
cv_.wait(
|
|
||||||
lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
|
|
||||||
XRPL_ASSERT(
|
|
||||||
m_processCount == 0,
|
|
||||||
"ripple::JobQueue::stop : all processes completed");
|
|
||||||
XRPL_ASSERT(
|
|
||||||
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
|
|
||||||
XRPL_ASSERT(
|
|
||||||
nSuspend_ == 0, "ripple::JobQueue::stop : no coros suspended");
|
|
||||||
stopped_ = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
jobCounter_.join("JobQueue", std::chrono::seconds(1), m_journal);
|
||||||
JobQueue::isStopped() const
|
|
||||||
{
|
// All jobs have finished executing (i.e. returned from `Job::doJob`) and
|
||||||
return stopped_;
|
// no more are being accepted, but there may still be some threads between
|
||||||
|
// the return of `Job::doJob` and the return of `JobQueue::processTask`.
|
||||||
|
// That is why we must wait on the condition variable to make these
|
||||||
|
// assertions.
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
|
||||||
|
|
||||||
|
XRPL_ASSERT(
|
||||||
|
activeThreads_ == 0,
|
||||||
|
"ripple::JobQueue::stop : all processes completed");
|
||||||
|
XRPL_ASSERT(
|
||||||
|
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
|
||||||
|
XRPL_ASSERT(
|
||||||
|
suspendedCoroutines_ == 0,
|
||||||
|
"ripple::JobQueue::stop : no coros suspended");
|
||||||
|
|
||||||
|
state_.store(state::stopped, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
JobQueue::getNextJob(Job& job)
|
JobQueue::uncaughtException(unsigned int instance, std::exception_ptr eptr)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
try
|
||||||
!m_jobSet.empty(), "ripple::JobQueue::getNextJob : non-empty jobs");
|
|
||||||
|
|
||||||
std::set<Job>::const_iterator iter;
|
|
||||||
for (iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
|
|
||||||
{
|
{
|
||||||
JobType const type = iter->getType();
|
if (eptr)
|
||||||
XRPL_ASSERT(
|
std::rethrow_exception(eptr);
|
||||||
type != jtINVALID, "ripple::JobQueue::getNextJob : valid job type");
|
|
||||||
|
|
||||||
JobTypeData& data(getJobTypeData(type));
|
LogicError(
|
||||||
XRPL_ASSERT(
|
beast::getCurrentThreadName() +
|
||||||
data.running <= getJobLimit(type),
|
": Uncaught exception handler invoked with no exception_ptr");
|
||||||
"ripple::JobQueue::getNextJob : maximum jobs running");
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
// Run this job if we're running below the limit.
|
{
|
||||||
if (data.running < getJobLimit(data.type()))
|
LogicError(
|
||||||
{
|
beast::getCurrentThreadName() +
|
||||||
XRPL_ASSERT(
|
": Exception caught during task processing: " + e.what());
|
||||||
data.waiting > 0,
|
}
|
||||||
"ripple::JobQueue::getNextJob : positive data waiting");
|
catch (...)
|
||||||
--data.waiting;
|
{
|
||||||
++data.running;
|
LogicError(
|
||||||
break;
|
beast::getCurrentThreadName() +
|
||||||
}
|
": Unknown exception caught during task processing");
|
||||||
}
|
}
|
||||||
|
|
||||||
XRPL_ASSERT(
|
|
||||||
iter != m_jobSet.end(),
|
|
||||||
"ripple::JobQueue::getNextJob : found next job");
|
|
||||||
job = *iter;
|
|
||||||
m_jobSet.erase(iter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
JobQueue::finishJob(JobType type)
|
JobQueue::processTask(unsigned int instance)
|
||||||
{
|
|
||||||
XRPL_ASSERT(
|
|
||||||
type != jtINVALID,
|
|
||||||
"ripple::JobQueue::finishJob : valid input job type");
|
|
||||||
|
|
||||||
JobTypeData& data = getJobTypeData(type);
|
|
||||||
|
|
||||||
// Queue a deferred task if possible
|
|
||||||
if (data.deferred > 0)
|
|
||||||
{
|
|
||||||
XRPL_ASSERT(
|
|
||||||
data.running + data.waiting >= getJobLimit(type),
|
|
||||||
"ripple::JobQueue::finishJob : job limit");
|
|
||||||
|
|
||||||
--data.deferred;
|
|
||||||
m_workers.addTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
--data.running;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
JobQueue::processTask(int instance)
|
|
||||||
{
|
{
|
||||||
JobType type;
|
JobType type;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Job job = [this] {
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
|
||||||
|
assert(!m_jobSet.empty());
|
||||||
|
|
||||||
|
for (auto iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
|
||||||
|
{
|
||||||
|
JobType const t = iter->getType();
|
||||||
|
|
||||||
|
auto& d = data_[t];
|
||||||
|
assert(d.running <= jobTypes[t].limit);
|
||||||
|
|
||||||
|
if (d.running < jobTypes[t].limit)
|
||||||
|
{
|
||||||
|
assert(d.waiting > 0);
|
||||||
|
--d.waiting;
|
||||||
|
++d.running;
|
||||||
|
++activeThreads_;
|
||||||
|
return std::move(m_jobSet.extract(iter).value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicError("Attempt to get a job when none are available");
|
||||||
|
}();
|
||||||
|
|
||||||
|
type = job.getType();
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
Job::clock_type::time_point const start_time(Job::clock_type::now());
|
|
||||||
|
auto const start_time = Job::clock_type::now();
|
||||||
|
auto const q_time = ceil<microseconds>(start_time - job.queue_time());
|
||||||
|
perfLog_.jobStart(type, q_time, start_time, instance);
|
||||||
|
|
||||||
|
JLOG(m_journal.trace())
|
||||||
|
<< "Starting: " << jobTypes[type].name
|
||||||
|
<< " (q_time=" << q_time.count() << " microseconds)";
|
||||||
|
|
||||||
|
job.execute();
|
||||||
|
|
||||||
|
auto const x_time =
|
||||||
|
ceil<microseconds>(Job::clock_type::now() - start_time);
|
||||||
|
|
||||||
|
JLOG(m_journal.trace())
|
||||||
|
<< "Finished: " << jobTypes[type].name
|
||||||
|
<< " (x_time=" << x_time.count() << " microseconds)";
|
||||||
|
|
||||||
|
if (x_time >= 10ms || q_time >= 10ms)
|
||||||
{
|
{
|
||||||
Job job;
|
data_[type].dequeue.notify(q_time);
|
||||||
{
|
data_[type].execute.notify(x_time);
|
||||||
std::lock_guard lock(m_mutex);
|
|
||||||
getNextJob(job);
|
|
||||||
++m_processCount;
|
|
||||||
}
|
|
||||||
type = job.getType();
|
|
||||||
JobTypeData& data(getJobTypeData(type));
|
|
||||||
JLOG(m_journal.trace()) << "Doing " << data.name() << "job";
|
|
||||||
|
|
||||||
// The amount of time that the job was in the queue
|
|
||||||
auto const q_time =
|
|
||||||
ceil<microseconds>(start_time - job.queue_time());
|
|
||||||
perfLog_.jobStart(type, q_time, start_time, instance);
|
|
||||||
|
|
||||||
job.doJob();
|
|
||||||
|
|
||||||
// The amount of time it took to execute the job
|
|
||||||
auto const x_time =
|
|
||||||
ceil<microseconds>(Job::clock_type::now() - start_time);
|
|
||||||
|
|
||||||
if (x_time >= 10ms || q_time >= 10ms)
|
|
||||||
{
|
|
||||||
getJobTypeData(type).dequeue.notify(q_time);
|
|
||||||
getJobTypeData(type).execute.notify(x_time);
|
|
||||||
}
|
|
||||||
perfLog_.jobFinish(type, x_time, instance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
perfLog_.jobFinish(type, x_time, instance);
|
||||||
|
|
||||||
|
// Note that the Job object gets destroyed at this point.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
|
||||||
|
auto& d = data_[type];
|
||||||
|
|
||||||
|
// Queue a deferred task if possible. This doesn't mean the task will
|
||||||
|
// run next; only that one is now runnable.
|
||||||
|
if (d.deferred > 0)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_mutex);
|
assert(d.running + d.waiting >= jobTypes[type].limit);
|
||||||
// Job should be destroyed before stopping
|
--d.deferred;
|
||||||
// otherwise destructors with side effects can access
|
m_workers.addTask();
|
||||||
// parent objects that are already destroyed.
|
|
||||||
finishJob(type);
|
|
||||||
if (--m_processCount == 0 && m_jobSet.empty())
|
|
||||||
cv_.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that when Job::~Job is called, the last reference
|
--d.running;
|
||||||
// to the associated LoadEvent object (in the Job) may be destroyed.
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
if (--activeThreads_ == 0 && m_jobSet.empty())
|
||||||
JobQueue::getJobLimit(JobType type)
|
cv_.notify_all();
|
||||||
{
|
|
||||||
JobTypeInfo const& j(JobTypes::instance().get(type));
|
|
||||||
XRPL_ASSERT(
|
|
||||||
j.type() != jtINVALID,
|
|
||||||
"ripple::JobQueue::getJobLimit : valid job type");
|
|
||||||
|
|
||||||
return j.limit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -18,25 +18,63 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <xrpld/core/LoadEvent.h>
|
#include <xrpld/core/LoadEvent.h>
|
||||||
#include <xrpld/core/LoadMonitor.h>
|
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
LoadEvent::LoadEvent(
|
LoadEvent::LoadEvent(
|
||||||
LoadMonitor& monitor,
|
std::reference_wrapper<LoadSampler const> callback,
|
||||||
std::string const& name,
|
std::string name,
|
||||||
bool shouldStart)
|
bool shouldStart)
|
||||||
: monitor_(monitor)
|
: name_(std::move(name))
|
||||||
, running_(shouldStart)
|
, callback_(callback)
|
||||||
, name_(name)
|
, mark_(std::chrono::steady_clock::now())
|
||||||
, mark_{std::chrono::steady_clock::now()}
|
|
||||||
, timeWaiting_{}
|
, timeWaiting_{}
|
||||||
, timeRunning_{}
|
, timeRunning_{}
|
||||||
|
, running_(shouldStart)
|
||||||
|
, neutered_(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadEvent::LoadEvent(LoadEvent&& other)
|
||||||
|
: name_(std::move(other.name_))
|
||||||
|
, callback_(other.callback_)
|
||||||
|
, mark_(other.mark_)
|
||||||
|
, timeWaiting_(other.timeWaiting_)
|
||||||
|
, timeRunning_(other.timeRunning_)
|
||||||
|
, running_(other.running_)
|
||||||
|
, neutered_(other.neutered_)
|
||||||
|
{
|
||||||
|
other.running_ = false;
|
||||||
|
other.neutered_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadEvent&
|
||||||
|
LoadEvent::operator=(LoadEvent&& other)
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
// If we are currently measuring something, report it before we
|
||||||
|
// take over the other event, so that the sample isn't lost.
|
||||||
|
if (running_)
|
||||||
|
stop();
|
||||||
|
|
||||||
|
name_ = std::move(other.name_);
|
||||||
|
callback_ = other.callback_;
|
||||||
|
running_ = other.running_;
|
||||||
|
mark_ = other.mark_;
|
||||||
|
timeWaiting_ = other.timeWaiting_;
|
||||||
|
timeRunning_ = other.timeRunning_;
|
||||||
|
neutered_ = other.neutered_;
|
||||||
|
|
||||||
|
// Leave the moved-from object in a sane but "neutered" state.
|
||||||
|
other.running_ = false;
|
||||||
|
other.neutered_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
LoadEvent::~LoadEvent()
|
LoadEvent::~LoadEvent()
|
||||||
{
|
{
|
||||||
if (running_)
|
if (running_)
|
||||||
@@ -49,27 +87,11 @@ LoadEvent::name() const
|
|||||||
return name_;
|
return name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::steady_clock::duration
|
|
||||||
LoadEvent::waitTime() const
|
|
||||||
{
|
|
||||||
return timeWaiting_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::chrono::steady_clock::duration
|
|
||||||
LoadEvent::runTime() const
|
|
||||||
{
|
|
||||||
return timeRunning_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
LoadEvent::setName(std::string const& name)
|
|
||||||
{
|
|
||||||
name_ = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
LoadEvent::start()
|
LoadEvent::start()
|
||||||
{
|
{
|
||||||
|
XRPL_ASSERT(!neutered_, "ripple::LoadEvent::start : is neutered");
|
||||||
|
|
||||||
auto const now = std::chrono::steady_clock::now();
|
auto const now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// If we had already called start, this call will
|
// If we had already called start, this call will
|
||||||
@@ -91,7 +113,8 @@ LoadEvent::stop()
|
|||||||
mark_ = now;
|
mark_ = now;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
monitor_.addLoadSample(*this);
|
if (!neutered_)
|
||||||
|
callback_(name_.c_str(), timeRunning_, timeWaiting_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -23,104 +23,95 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
/*
|
LoadMonitor::LoadMonitor(
|
||||||
|
std::optional<std::chrono::milliseconds> targetAverageLatency,
|
||||||
TODO
|
std::optional<std::chrono::milliseconds> targetPeakLatency,
|
||||||
----
|
beast::Journal j)
|
||||||
|
: targetAverageLatency_([&targetAverageLatency]() {
|
||||||
- Use Journal for logging
|
using namespace std::chrono_literals;
|
||||||
|
if (targetAverageLatency.has_value() && targetAverageLatency == 0ms)
|
||||||
*/
|
targetAverageLatency.reset();
|
||||||
|
return targetAverageLatency;
|
||||||
//------------------------------------------------------------------------------
|
}())
|
||||||
|
, targetPeakLatency_([&targetPeakLatency]() {
|
||||||
LoadMonitor::Stats::Stats()
|
using namespace std::chrono_literals;
|
||||||
: count(0), latencyAvg(0), latencyPeak(0), isOverloaded(false)
|
if (targetPeakLatency.has_value() && targetPeakLatency == 0ms)
|
||||||
{
|
targetPeakLatency.reset();
|
||||||
}
|
return targetPeakLatency;
|
||||||
|
}())
|
||||||
//------------------------------------------------------------------------------
|
, lastUpdate_(UptimeClock::now())
|
||||||
|
|
||||||
LoadMonitor::LoadMonitor(beast::Journal j)
|
|
||||||
: mCounts(0)
|
|
||||||
, mLatencyEvents(0)
|
|
||||||
, mLatencyMSAvg(0)
|
|
||||||
, mLatencyMSPeak(0)
|
|
||||||
, mTargetLatencyAvg(0)
|
|
||||||
, mTargetLatencyPk(0)
|
|
||||||
, mLastUpdate(UptimeClock::now())
|
|
||||||
, j_(j)
|
, j_(j)
|
||||||
|
, eventCallback_([this](
|
||||||
|
char const* name,
|
||||||
|
std::chrono::steady_clock::duration run,
|
||||||
|
std::chrono::steady_clock::duration wait) {
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
auto const latency = [total = run + wait] {
|
||||||
|
// Don't include "jitter" as part of the latency
|
||||||
|
if (total < 5ms)
|
||||||
|
return 0ms;
|
||||||
|
|
||||||
|
return round<milliseconds>(total);
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (latency > targetPeakLatency_.value_or(1s))
|
||||||
|
{
|
||||||
|
// Log at a higher severity if the latency is much greater than
|
||||||
|
// the peak latency we want to target.
|
||||||
|
auto mj = (latency > 2 * targetPeakLatency_.value_or(latency))
|
||||||
|
? j_.warn()
|
||||||
|
: j_.info();
|
||||||
|
|
||||||
|
JLOG(mj) << "Job: " << name
|
||||||
|
<< " run: " << round<milliseconds>(run).count() << "ms"
|
||||||
|
<< " wait: " << round<milliseconds>(wait).count() << "ms";
|
||||||
|
}
|
||||||
|
|
||||||
|
addSamples(1, latency);
|
||||||
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// VFALCO NOTE WHY do we need "the mutex?" This dependence on
|
|
||||||
// a hidden global, especially a synchronization primitive,
|
|
||||||
// is a flawed design.
|
|
||||||
// It's not clear exactly which data needs to be protected.
|
|
||||||
//
|
|
||||||
// call with the mutex
|
|
||||||
void
|
void
|
||||||
LoadMonitor::update()
|
LoadMonitor::update()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
auto now = UptimeClock::now();
|
|
||||||
if (now == mLastUpdate) // current
|
|
||||||
return;
|
|
||||||
|
|
||||||
// VFALCO TODO Why 8?
|
if (auto const now = UptimeClock::now(); now != lastUpdate_)
|
||||||
if ((now < mLastUpdate) || (now > (mLastUpdate + 8s)))
|
|
||||||
{
|
{
|
||||||
// way out of date
|
// VFALCO TODO Why 8?
|
||||||
mCounts = 0;
|
if ((now < lastUpdate_) || (now > (lastUpdate_ + 8s)))
|
||||||
mLatencyEvents = 0;
|
{
|
||||||
mLatencyMSAvg = 0ms;
|
// way out of date
|
||||||
mLatencyMSPeak = 0ms;
|
samples_ = 0;
|
||||||
mLastUpdate = now;
|
averageLatency_ = 0ms;
|
||||||
return;
|
peakLatency_ = 0ms;
|
||||||
|
lastUpdate_ = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do exponential decay
|
||||||
|
/*
|
||||||
|
David:
|
||||||
|
|
||||||
|
"Imagine if you add 10 to something every second. And you
|
||||||
|
also reduce it by 1/4 every second. It will "idle" at 40,
|
||||||
|
correponding to 10 counts per second."
|
||||||
|
*/
|
||||||
|
do
|
||||||
|
{
|
||||||
|
lastUpdate_ += 1s;
|
||||||
|
samples_ -= ((samples_ + 3) / 4);
|
||||||
|
averageLatency_ -= (averageLatency_ / 4);
|
||||||
|
peakLatency_ -= (peakLatency_ / 4);
|
||||||
|
} while (lastUpdate_ < now);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do exponential decay
|
|
||||||
/*
|
|
||||||
David:
|
|
||||||
|
|
||||||
"Imagine if you add 10 to something every second. And you
|
|
||||||
also reduce it by 1/4 every second. It will "idle" at 40,
|
|
||||||
correponding to 10 counts per second."
|
|
||||||
*/
|
|
||||||
do
|
|
||||||
{
|
|
||||||
mLastUpdate += 1s;
|
|
||||||
mCounts -= ((mCounts + 3) / 4);
|
|
||||||
mLatencyEvents -= ((mLatencyEvents + 3) / 4);
|
|
||||||
mLatencyMSAvg -= (mLatencyMSAvg / 4);
|
|
||||||
mLatencyMSPeak -= (mLatencyMSPeak / 4);
|
|
||||||
} while (mLastUpdate < now);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
LoadMonitor::addLoadSample(LoadEvent const& s)
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
auto const total = s.runTime() + s.waitTime();
|
|
||||||
// Don't include "jitter" as part of the latency
|
|
||||||
auto const latency = total < 2ms ? 0ms : round<milliseconds>(total);
|
|
||||||
|
|
||||||
if (latency > 500ms)
|
|
||||||
{
|
|
||||||
auto mj = (latency > 1s) ? j_.warn() : j_.info();
|
|
||||||
JLOG(mj) << "Job: " << s.name()
|
|
||||||
<< " run: " << round<milliseconds>(s.runTime()).count() << "ms"
|
|
||||||
<< " wait: " << round<milliseconds>(s.waitTime()).count()
|
|
||||||
<< "ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
addSamples(1, latency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add multiple samples
|
/* Add multiple samples
|
||||||
@param count The number of samples to add
|
@param count The number of samples to add
|
||||||
@param latencyMS The total number of milliseconds
|
@param latency The total number of milliseconds
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
|
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
|
||||||
@@ -128,77 +119,45 @@ LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
|
|||||||
std::lock_guard sl(mutex_);
|
std::lock_guard sl(mutex_);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
mCounts += count;
|
samples_ += count;
|
||||||
mLatencyEvents += count;
|
averageLatency_ += latency;
|
||||||
mLatencyMSAvg += latency;
|
peakLatency_ += latency;
|
||||||
mLatencyMSPeak += latency;
|
|
||||||
|
|
||||||
auto const latencyPeak = mLatencyEvents * latency * 4 / count;
|
auto const latencyPeak = samples_ * latency * 4 / count;
|
||||||
|
|
||||||
if (mLatencyMSPeak < latencyPeak)
|
if (peakLatency_ < latencyPeak)
|
||||||
mLatencyMSPeak = latencyPeak;
|
peakLatency_ = latencyPeak;
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
LoadMonitor::setTargetLatency(
|
|
||||||
std::chrono::milliseconds avg,
|
|
||||||
std::chrono::milliseconds pk)
|
|
||||||
{
|
|
||||||
mTargetLatencyAvg = avg;
|
|
||||||
mTargetLatencyPk = pk;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
LoadMonitor::isOverTarget(
|
|
||||||
std::chrono::milliseconds avg,
|
|
||||||
std::chrono::milliseconds peak)
|
|
||||||
{
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
return (mTargetLatencyPk > 0ms && (peak > mTargetLatencyPk)) ||
|
|
||||||
(mTargetLatencyAvg > 0ms && (avg > mTargetLatencyAvg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
LoadMonitor::isOver()
|
LoadMonitor::isOver()
|
||||||
{
|
{
|
||||||
std::lock_guard sl(mutex_);
|
return getStats().isOverloaded;
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
if (mLatencyEvents == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return isOverTarget(
|
|
||||||
mLatencyMSAvg / (mLatencyEvents * 4),
|
|
||||||
mLatencyMSPeak / (mLatencyEvents * 4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadMonitor::Stats
|
LoadMonitor::Stats
|
||||||
LoadMonitor::getStats()
|
LoadMonitor::getStats()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
Stats stats;
|
|
||||||
|
|
||||||
std::lock_guard sl(mutex_);
|
std::lock_guard sl(mutex_);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
stats.count = mCounts / 4;
|
Stats s;
|
||||||
|
s.count = samples_ / 4;
|
||||||
|
|
||||||
if (mLatencyEvents == 0)
|
if (samples_ != 0)
|
||||||
{
|
{
|
||||||
stats.latencyAvg = 0ms;
|
s.averageLatency = averageLatency_ / (samples_ * 4);
|
||||||
stats.latencyPeak = 0ms;
|
s.peakLatency = peakLatency_ / (samples_ * 4);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stats.latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4);
|
|
||||||
stats.latencyPeak = mLatencyMSPeak / (mLatencyEvents * 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.isOverloaded = isOverTarget(stats.latencyAvg, stats.latencyPeak);
|
s.isOverloaded =
|
||||||
|
(s.averageLatency > targetAverageLatency_.value_or(s.averageLatency)) ||
|
||||||
|
(s.peakLatency > targetPeakLatency_.value_or(s.peakLatency));
|
||||||
|
|
||||||
return stats;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -18,265 +18,124 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <xrpld/core/detail/Workers.h>
|
#include <xrpld/core/detail/Workers.h>
|
||||||
#include <xrpld/perflog/PerfLog.h>
|
|
||||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
Workers::Workers(
|
Workers::Workers(
|
||||||
Callback& callback,
|
Callback& callback,
|
||||||
perf::PerfLog* perfLog,
|
std::string const& name,
|
||||||
std::string const& threadNames,
|
unsigned int count)
|
||||||
int numberOfThreads)
|
: callback_(callback)
|
||||||
: m_callback(callback)
|
|
||||||
, perfLog_(perfLog)
|
|
||||||
, m_threadNames(threadNames)
|
|
||||||
, m_allPaused(true)
|
|
||||||
, m_semaphore(0)
|
|
||||||
, m_numberOfThreads(0)
|
|
||||||
, m_activeCount(0)
|
|
||||||
, m_pauseCount(0)
|
|
||||||
, m_runningTaskCount(0)
|
|
||||||
{
|
{
|
||||||
setNumberOfThreads(numberOfThreads);
|
assert(count != 0);
|
||||||
|
|
||||||
|
// It is important that head and tail are equal but non-zero on
|
||||||
|
// startup: we use zero as a special value to indicate that the
|
||||||
|
// thread must terminate; if zero was a 'legal' value then we'd
|
||||||
|
// encounter the ABA problem with std::atomic::wait.
|
||||||
|
assert(head_ == tail_ && head_ != 0);
|
||||||
|
|
||||||
|
while (count--)
|
||||||
|
{
|
||||||
|
// We need to increment this outside of the thread, to avoid a
|
||||||
|
// subtle (if unlikely) race condition during shutdown.
|
||||||
|
++threads_;
|
||||||
|
|
||||||
|
std::thread th(
|
||||||
|
[this, name](unsigned int instance) {
|
||||||
|
auto const n = name + ":" + std::to_string(instance);
|
||||||
|
|
||||||
|
while (head_)
|
||||||
|
{
|
||||||
|
auto t = tail_.load();
|
||||||
|
auto h = head_.load();
|
||||||
|
|
||||||
|
assert((h == 0) || (h >= t));
|
||||||
|
|
||||||
|
if (h == t)
|
||||||
|
{
|
||||||
|
// Only pay for the thread rename when we are
|
||||||
|
// actually about to go to sleep.
|
||||||
|
beast::setCurrentThreadName(n + " [zZz]");
|
||||||
|
|
||||||
|
while (h == t)
|
||||||
|
{
|
||||||
|
// This will block until we're notified.
|
||||||
|
head_.wait(h);
|
||||||
|
|
||||||
|
t = tail_.load();
|
||||||
|
h = head_.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As long as we aren't stopping and there is a task
|
||||||
|
// that's waiting, we try to do work.
|
||||||
|
if (h > t && tail_.compare_exchange_strong(t, t + 1))
|
||||||
|
{
|
||||||
|
// Restore the name, in case the previous callback
|
||||||
|
// had changed it.
|
||||||
|
beast::setCurrentThreadName(n);
|
||||||
|
|
||||||
|
// Exceptions should never bubble up to here but
|
||||||
|
// just in case, if one does catch it.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
callback_.processTask(instance);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
callback_.uncaughtException(
|
||||||
|
instance, std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track number of threads
|
||||||
|
--threads_;
|
||||||
|
},
|
||||||
|
count);
|
||||||
|
|
||||||
|
// We detach from this thread; it will continue to run in the
|
||||||
|
// background until we instruct it to stop.
|
||||||
|
th.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Workers::~Workers()
|
Workers::~Workers()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
deleteWorkers(m_everyone);
|
assert(head_.load() == 0 && threads_.load() == 0);
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
Workers::getNumberOfThreads() const noexcept
|
|
||||||
{
|
|
||||||
return m_numberOfThreads;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VFALCO NOTE if this function is called quickly to reduce then
|
|
||||||
// increase the number of threads, it could result in
|
|
||||||
// more paused threads being created than expected.
|
|
||||||
//
|
|
||||||
void
|
|
||||||
Workers::setNumberOfThreads(int numberOfThreads)
|
|
||||||
{
|
|
||||||
static int instance{0};
|
|
||||||
if (m_numberOfThreads == numberOfThreads)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (perfLog_)
|
|
||||||
perfLog_->resizeJobs(numberOfThreads);
|
|
||||||
|
|
||||||
if (numberOfThreads > m_numberOfThreads)
|
|
||||||
{
|
|
||||||
// Increasing the number of working threads
|
|
||||||
int const amount = numberOfThreads - m_numberOfThreads;
|
|
||||||
|
|
||||||
for (int i = 0; i < amount; ++i)
|
|
||||||
{
|
|
||||||
// See if we can reuse a paused worker
|
|
||||||
Worker* worker = m_paused.pop_front();
|
|
||||||
|
|
||||||
if (worker != nullptr)
|
|
||||||
{
|
|
||||||
// If we got here then the worker thread is at [1]
|
|
||||||
// This will unblock their call to wait()
|
|
||||||
//
|
|
||||||
worker->notify();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
worker = new Worker(*this, m_threadNames, instance++);
|
|
||||||
m_everyone.push_front(worker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Decreasing the number of working threads
|
|
||||||
int const amount = m_numberOfThreads - numberOfThreads;
|
|
||||||
|
|
||||||
for (int i = 0; i < amount; ++i)
|
|
||||||
{
|
|
||||||
++m_pauseCount;
|
|
||||||
|
|
||||||
// Pausing a thread counts as one "internal task"
|
|
||||||
m_semaphore.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_numberOfThreads = numberOfThreads;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Workers::stop()
|
Workers::stop()
|
||||||
{
|
{
|
||||||
setNumberOfThreads(0);
|
head_.store(0);
|
||||||
|
head_.notify_all();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lk{m_mut};
|
while (threads_.load() != 0)
|
||||||
m_cv.wait(lk, [this] { return m_allPaused; });
|
std::this_thread::yield();
|
||||||
lk.unlock();
|
|
||||||
|
|
||||||
XRPL_ASSERT(
|
|
||||||
numberOfCurrentlyRunningTasks() == 0,
|
|
||||||
"ripple::Workers::stop : zero running tasks");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Workers::addTask()
|
Workers::addTask()
|
||||||
{
|
{
|
||||||
m_semaphore.notify();
|
auto h = head_.load();
|
||||||
}
|
|
||||||
|
|
||||||
int
|
while (h != 0)
|
||||||
Workers::numberOfCurrentlyRunningTasks() const noexcept
|
|
||||||
{
|
|
||||||
return m_runningTaskCount.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Workers::deleteWorkers(beast::LockFreeStack<Worker>& stack)
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
Worker* const worker = stack.pop_front();
|
if (head_.compare_exchange_strong(h, h + 1))
|
||||||
|
|
||||||
if (worker != nullptr)
|
|
||||||
{
|
{
|
||||||
// This call blocks until the thread orderly exits
|
head_.notify_one();
|
||||||
delete worker;
|
h = 0;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Workers::Worker::Worker(
|
|
||||||
Workers& workers,
|
|
||||||
std::string const& threadName,
|
|
||||||
int const instance)
|
|
||||||
: m_workers{workers}
|
|
||||||
, threadName_{threadName}
|
|
||||||
, instance_{instance}
|
|
||||||
, wakeCount_{0}
|
|
||||||
, shouldExit_{false}
|
|
||||||
{
|
|
||||||
thread_ = std::thread{&Workers::Worker::run, this};
|
|
||||||
}
|
|
||||||
|
|
||||||
Workers::Worker::~Worker()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard lock{mutex_};
|
|
||||||
++wakeCount_;
|
|
||||||
shouldExit_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
wakeup_.notify_one();
|
|
||||||
thread_.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Workers::Worker::notify()
|
|
||||||
{
|
|
||||||
std::lock_guard lock{mutex_};
|
|
||||||
++wakeCount_;
|
|
||||||
wakeup_.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Workers::Worker::run()
|
|
||||||
{
|
|
||||||
bool shouldExit = true;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// Increment the count of active workers, and if
|
|
||||||
// we are the first one then reset the "all paused" event
|
|
||||||
//
|
|
||||||
if (++m_workers.m_activeCount == 1)
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_workers.m_mut};
|
|
||||||
m_workers.m_allPaused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
// Put the name back in case the callback changed it
|
|
||||||
beast::setCurrentThreadName(threadName_);
|
|
||||||
|
|
||||||
// Acquire a task or "internal task."
|
|
||||||
//
|
|
||||||
m_workers.m_semaphore.wait();
|
|
||||||
|
|
||||||
// See if there's a pause request. This
|
|
||||||
// counts as an "internal task."
|
|
||||||
//
|
|
||||||
int pauseCount = m_workers.m_pauseCount.load();
|
|
||||||
|
|
||||||
if (pauseCount > 0)
|
|
||||||
{
|
|
||||||
// Try to decrement
|
|
||||||
pauseCount = --m_workers.m_pauseCount;
|
|
||||||
|
|
||||||
if (pauseCount >= 0)
|
|
||||||
{
|
|
||||||
// We got paused
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Undo our decrement
|
|
||||||
++m_workers.m_pauseCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We couldn't pause so we must have gotten
|
|
||||||
// unblocked in order to process a task.
|
|
||||||
//
|
|
||||||
++m_workers.m_runningTaskCount;
|
|
||||||
m_workers.m_callback.processTask(instance_);
|
|
||||||
--m_workers.m_runningTaskCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any worker that goes into the paused list must
|
|
||||||
// guarantee that it will eventually block on its
|
|
||||||
// event object.
|
|
||||||
//
|
|
||||||
m_workers.m_paused.push_front(this);
|
|
||||||
|
|
||||||
// Decrement the count of active workers, and if we
|
|
||||||
// are the last one then signal the "all paused" event.
|
|
||||||
//
|
|
||||||
if (--m_workers.m_activeCount == 0)
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_workers.m_mut};
|
|
||||||
m_workers.m_allPaused = true;
|
|
||||||
m_workers.m_cv.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set inactive thread name.
|
|
||||||
beast::setCurrentThreadName("(" + threadName_ + ")");
|
|
||||||
|
|
||||||
// [1] We will be here when the paused list is popped
|
|
||||||
//
|
|
||||||
// We block on our condition_variable, wakeup_, a requirement of being
|
|
||||||
// put into the paused list.
|
|
||||||
//
|
|
||||||
// wakeup_ will get signaled by either Worker::notify() or ~Worker.
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock{mutex_};
|
|
||||||
wakeup_.wait(lock, [this] { return this->wakeCount_ > 0; });
|
|
||||||
|
|
||||||
shouldExit = shouldExit_;
|
|
||||||
--wakeCount_;
|
|
||||||
}
|
|
||||||
} while (!shouldExit);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
+73
-142
@@ -20,61 +20,33 @@
|
|||||||
#ifndef RIPPLE_CORE_WORKERS_H_INCLUDED
|
#ifndef RIPPLE_CORE_WORKERS_H_INCLUDED
|
||||||
#define RIPPLE_CORE_WORKERS_H_INCLUDED
|
#define RIPPLE_CORE_WORKERS_H_INCLUDED
|
||||||
|
|
||||||
#include <xrpld/core/detail/semaphore.h>
|
|
||||||
#include <xrpl/beast/core/LockFreeStack.h>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <exception>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
namespace perf {
|
/** A simple thread pool.
|
||||||
class PerfLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
This class is a simple, fixed-sized thread pool. The pool only tracks
|
||||||
* `Workers` is effectively a thread pool. The constructor takes a "callback"
|
the number of outstanding tasks, and dispatches work. When the pool detects
|
||||||
* that has a `void processTask(int instance)` method, and a number of
|
that there is no work to be done, it puts threads to sleep.
|
||||||
* workers. It creates that many `Worker`s and then waits for calls to
|
|
||||||
* `Workers::addTask()`. It holds a semaphore that counts the number of
|
The pool does not decide which task to run; that is handled by a callback
|
||||||
* pending "tasks", and a condition variable for the event when the last
|
that the pool invokes. This makes it possible to implement the dispatch
|
||||||
* worker pauses itself.
|
strategy (e.g. FIFO or priority queues) that makes sense without requiring
|
||||||
*
|
changes in the thread pool itself.
|
||||||
* A "task" is just a call to the callback's `processTask` method.
|
|
||||||
* "Adding a task" means calling that method now, or remembering to call it in
|
Threads will continue to run (or sleep) until the thread pool is stopped
|
||||||
* the future.
|
which can happen via an explicit call to the stop method, or automatically
|
||||||
* This is implemented with a semaphore.
|
by the pool's destructor.
|
||||||
* If there are any workers waiting when a task is added, then one will be
|
|
||||||
* woken to claim the task.
|
Note that servicing existing tasks take priority over stopping and stopping
|
||||||
* If not, then the next worker to wait on the semaphore will claim the task.
|
takes priority over servicing new tasks; this means that once a stop request
|
||||||
*
|
has been made, the worker threads will complete their current tasks (if any)
|
||||||
* Creating a `Worker` creates a thread that calls `Worker::run()`. When that
|
and then exit, potentially leaving work unfinished.
|
||||||
* thread enters `Worker::run`, it increments the count of active workers in
|
|
||||||
* the parent `Workers` object and then tries to claim a task, which blocks if
|
|
||||||
* there are none pending.
|
|
||||||
* It will be unblocked whenever the semaphore is notified (i.e. when the
|
|
||||||
* number of pending tasks is incremented).
|
|
||||||
* That only happens in two circumstances: (1) when
|
|
||||||
* `Workers::addTask` is called and (2) when `Workers` wants to pause some
|
|
||||||
* workers ("pause one worker" is considered one task), which happens when
|
|
||||||
* someone wants to stop the workers or shrink the threadpool. No worker
|
|
||||||
* threads are ever destroyed until `Workers` is destroyed; it merely pauses
|
|
||||||
* workers until then.
|
|
||||||
*
|
|
||||||
* When a waiting worker is woken, it checks whether `Workers` is trying to
|
|
||||||
* pause workers. If so, it changes its status from active to paused and
|
|
||||||
* blocks on
|
|
||||||
* its own condition variable. If not, then it calls `processTask` on the
|
|
||||||
* "callback" held by `Workers`.
|
|
||||||
*
|
|
||||||
* When a paused worker is woken, it checks whether it should exit. The signal
|
|
||||||
* to exit is only set in the destructor of `Worker`, which unblocks the
|
|
||||||
* paused thread and waits for it to exit. A `Worker::run` thread checks
|
|
||||||
* whether it needs to exit only when it is woken from a pause (not when it is
|
|
||||||
* woken from waiting). This is why the destructor for `Workers` pauses all
|
|
||||||
* the workers before destroying them.
|
|
||||||
*/
|
*/
|
||||||
class Workers
|
class Workers
|
||||||
{
|
{
|
||||||
@@ -90,50 +62,54 @@ public:
|
|||||||
|
|
||||||
/** Perform a task.
|
/** Perform a task.
|
||||||
|
|
||||||
The call is made on a thread owned by Workers. It is important
|
The function is invoked precisely once for every call to the
|
||||||
that you only process one task from inside your callback. Each
|
thread pool's addTask method. It executes on one of the thread
|
||||||
call to addTask will result in exactly one call to processTask.
|
pool's threads.
|
||||||
|
|
||||||
|
This function should process precisely one task.
|
||||||
|
|
||||||
@param instance The worker thread instance.
|
@param instance The worker thread instance.
|
||||||
|
|
||||||
|
@throws This function should NOT throw an exception; if it does
|
||||||
|
the exception will be captured and passed to the
|
||||||
|
uncaughtException callback.
|
||||||
|
|
||||||
@see Workers::addTask
|
@see Workers::addTask
|
||||||
*/
|
*/
|
||||||
virtual void
|
virtual void
|
||||||
processTask(int instance) = 0;
|
processTask(unsigned int instance) = 0;
|
||||||
|
|
||||||
|
/** Indicates that processTask threw an unexpected exception.
|
||||||
|
|
||||||
|
@param instance The worker thread instance.
|
||||||
|
@param eptr The exception that was thrown.
|
||||||
|
* */
|
||||||
|
virtual void
|
||||||
|
uncaughtException(unsigned int, std::exception_ptr)
|
||||||
|
{
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Create the object.
|
/** Create the object.
|
||||||
|
|
||||||
A number of initial threads may be optionally specified. The
|
@param callback The task selection & execution algorithm.
|
||||||
default is to create one thread per CPU.
|
@param name The name for this pool (used to name threads)
|
||||||
|
@param size The number of threads (must not be 0!) for this thread pool
|
||||||
@param threadNames The name given to each created worker thread.
|
|
||||||
*/
|
*/
|
||||||
explicit Workers(
|
explicit Workers(
|
||||||
Callback& callback,
|
Callback& callback,
|
||||||
perf::PerfLog* perfLog,
|
std::string const& name,
|
||||||
std::string const& threadNames = "Worker",
|
unsigned int count);
|
||||||
int numberOfThreads =
|
|
||||||
static_cast<int>(std::thread::hardware_concurrency()));
|
|
||||||
|
|
||||||
~Workers();
|
~Workers();
|
||||||
|
|
||||||
/** Retrieve the desired number of threads.
|
/** Retrieve the number of threads in the thread pool. */
|
||||||
|
unsigned int
|
||||||
This just returns the number of active threads that were requested. If
|
count() const noexcept
|
||||||
there was a recent call to setNumberOfThreads, the actual number of
|
{
|
||||||
active threads may be temporarily different from what was last requested.
|
return threads_.load();
|
||||||
|
}
|
||||||
@note This function is not thread-safe.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
getNumberOfThreads() const noexcept;
|
|
||||||
|
|
||||||
/** Set the desired number of threads.
|
|
||||||
@note This function is not thread-safe.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setNumberOfThreads(int numberOfThreads);
|
|
||||||
|
|
||||||
/** Pause all threads and wait until they are paused.
|
/** Pause all threads and wait until they are paused.
|
||||||
|
|
||||||
@@ -157,79 +133,34 @@ public:
|
|||||||
void
|
void
|
||||||
addTask();
|
addTask();
|
||||||
|
|
||||||
/** Get the number of currently executing calls of Callback::processTask.
|
|
||||||
While this function is thread-safe, the value may not stay
|
|
||||||
accurate for very long. It's mainly for diagnostic purposes.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
numberOfCurrentlyRunningTasks() const noexcept;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PausedTag
|
Callback& callback_;
|
||||||
{
|
|
||||||
explicit PausedTag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* A Worker executes tasks on its provided thread.
|
/// This represents the total number of threads in the thread pool:
|
||||||
|
std::atomic<unsigned int> threads_ = 0;
|
||||||
|
|
||||||
These are the states:
|
/** Queued task tracking
|
||||||
|
|
||||||
Active: Running the task processing loop.
|
To minimize overhead, we track two things: total number of tasks
|
||||||
Idle: Active, but blocked on waiting for a task.
|
added, and total number of tasks dispatched. The key insight is
|
||||||
Paused: Blocked waiting to exit or become active.
|
that both of these numbers are only ever incremented and never
|
||||||
*/
|
decremented. Along with atomic operations, this allows us to
|
||||||
class Worker : public beast::LockFreeStack<Worker>::Node,
|
write lock free code.
|
||||||
public beast::LockFreeStack<Worker, PausedTag>::Node
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Worker(
|
|
||||||
Workers& workers,
|
|
||||||
std::string const& threadName,
|
|
||||||
int const instance);
|
|
||||||
|
|
||||||
~Worker();
|
The choice of 64-bit unsigned integers helps to avoid overflow. A
|
||||||
|
32-bit value would overflow too fast even at low queue rates (e.g.
|
||||||
|
at a 1,000 tasks per second, the counter would overflow in about 50
|
||||||
|
days). With 64 bits, even at a truly obscene rate of 1,000,000,000
|
||||||
|
increments per second, this counter is good for over 580 years, at
|
||||||
|
which point the server is really due for a reboot.
|
||||||
|
*/
|
||||||
|
/** @{ */
|
||||||
|
/// The total number of tasks that have been queued since startup.
|
||||||
|
std::atomic<std::uint64_t> head_ = 1;
|
||||||
|
|
||||||
void
|
/// The total number of tasks that have been dispatched for processing.
|
||||||
notify();
|
std::atomic<std::uint64_t> tail_ = 1;
|
||||||
|
/** @} */
|
||||||
private:
|
|
||||||
void
|
|
||||||
run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Workers& m_workers;
|
|
||||||
std::string const threadName_;
|
|
||||||
int const instance_;
|
|
||||||
|
|
||||||
std::thread thread_;
|
|
||||||
std::mutex mutex_;
|
|
||||||
std::condition_variable wakeup_;
|
|
||||||
int wakeCount_; // how many times to un-pause
|
|
||||||
bool shouldExit_;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void
|
|
||||||
deleteWorkers(beast::LockFreeStack<Worker>& stack);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Callback& m_callback;
|
|
||||||
perf::PerfLog* perfLog_;
|
|
||||||
std::string m_threadNames; // The name to give each thread
|
|
||||||
std::condition_variable m_cv; // signaled when all threads paused
|
|
||||||
std::mutex m_mut;
|
|
||||||
bool m_allPaused;
|
|
||||||
semaphore m_semaphore; // each pending task is 1 resource
|
|
||||||
int m_numberOfThreads; // how many we want active now
|
|
||||||
std::atomic<int> m_activeCount; // to know when all are paused
|
|
||||||
std::atomic<int> m_pauseCount; // how many threads need to pause now
|
|
||||||
std::atomic<int>
|
|
||||||
m_runningTaskCount; // how many calls to processTask() active
|
|
||||||
beast::LockFreeStack<Worker> m_everyone; // holds all created workers
|
|
||||||
beast::LockFreeStack<Worker, PausedTag>
|
|
||||||
m_paused; // holds just paused workers
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -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?
|
// BuildInfo::getFullVersionString () as well?
|
||||||
|
|
||||||
s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n"
|
s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n"
|
||||||
<< "User-Agent: " << systemName() << "-json-rpc/v1\r\n"
|
<< "User-Agent: " << systemName << "-json-rpc/v1\r\n"
|
||||||
<< "Host: " << strHost << "\r\n"
|
<< "Host: " << strHost << "\r\n"
|
||||||
<< "Content-Type: application/json\r\n"
|
<< "Content-Type: application/json\r\n"
|
||||||
<< "Content-Length: " << strMsg.size() << "\r\n"
|
<< "Content-Length: " << strMsg.size() << "\r\n"
|
||||||
|
|||||||
@@ -31,12 +31,23 @@ class DummyScheduler : public Scheduler
|
|||||||
public:
|
public:
|
||||||
DummyScheduler() = default;
|
DummyScheduler() = default;
|
||||||
~DummyScheduler() = default;
|
~DummyScheduler() = default;
|
||||||
|
|
||||||
void
|
void
|
||||||
scheduleTask(Task& task) override;
|
scheduleTask(Task& task) override
|
||||||
|
{
|
||||||
|
// Invoke the task synchronously.
|
||||||
|
task.performScheduledTask();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
onFetch(FetchReport const& report) override;
|
onFetch(FetchReport const& report) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
onBatchWrite(BatchWriteReport const& report) override;
|
onBatchWrite(BatchWriteReport const& report) override
|
||||||
|
{
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NodeStore
|
} // namespace NodeStore
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
/*
|
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
#include <xrpld/nodestore/DummyScheduler.h>
|
|
||||||
|
|
||||||
namespace ripple {
|
|
||||||
namespace NodeStore {
|
|
||||||
|
|
||||||
void
|
|
||||||
DummyScheduler::scheduleTask(Task& task)
|
|
||||||
{
|
|
||||||
// Invoke the task synchronously.
|
|
||||||
task.performScheduledTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
DummyScheduler::onFetch(const FetchReport& report)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
DummyScheduler::onBatchWrite(const BatchWriteReport& report)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace NodeStore
|
|
||||||
} // namespace ripple
|
|
||||||
@@ -177,7 +177,7 @@ private:
|
|||||||
std::queue<std::shared_ptr<Message>> send_queue_;
|
std::queue<std::shared_ptr<Message>> send_queue_;
|
||||||
bool gracefulClose_ = false;
|
bool gracefulClose_ = false;
|
||||||
int large_sendq_ = 0;
|
int large_sendq_ = 0;
|
||||||
std::unique_ptr<LoadEvent> load_event_;
|
std::optional<LoadEvent> load_event_;
|
||||||
// The highest sequence of each PublisherList that has
|
// The highest sequence of each PublisherList that has
|
||||||
// been sent to or received from this peer.
|
// been sent to or received from this peer.
|
||||||
hash_map<PublicKey, std::size_t> publisherListSequences_;
|
hash_map<PublicKey, std::size_t> publisherListSequences_;
|
||||||
|
|||||||
@@ -41,9 +41,7 @@
|
|||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace perf {
|
namespace perf {
|
||||||
|
|
||||||
PerfLogImp::Counters::Counters(
|
PerfLogImp::Counters::Counters(std::set<char const*> const& labels)
|
||||||
std::set<char const*> const& labels,
|
|
||||||
JobTypes const& jobTypes)
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
// populateRpc
|
// populateRpc
|
||||||
@@ -63,9 +61,9 @@ PerfLogImp::Counters::Counters(
|
|||||||
{
|
{
|
||||||
// populateJq
|
// populateJq
|
||||||
jq_.reserve(jobTypes.size());
|
jq_.reserve(jobTypes.size());
|
||||||
for (auto const& [jobType, _] : jobTypes)
|
for (auto const& jobType : jobTypes)
|
||||||
{
|
{
|
||||||
auto const inserted = jq_.emplace(jobType, Jq()).second;
|
auto const inserted = jq_.emplace(jobType.type, Jq()).second;
|
||||||
if (!inserted)
|
if (!inserted)
|
||||||
{
|
{
|
||||||
// Ensure that no other function populates this entry.
|
// Ensure that no other function populates this entry.
|
||||||
@@ -148,7 +146,7 @@ PerfLogImp::Counters::countersJson() const
|
|||||||
j[jss::running_duration_us] =
|
j[jss::running_duration_us] =
|
||||||
std::to_string(value.runningDuration.count());
|
std::to_string(value.runningDuration.count());
|
||||||
totalJq.runningDuration += value.runningDuration;
|
totalJq.runningDuration += value.runningDuration;
|
||||||
jqobj[JobTypes::name(proc.first)] = j;
|
jqobj[std::string(jobTypes[proc.first].name)] = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalJq.queued)
|
if (totalJq.queued)
|
||||||
@@ -185,12 +183,12 @@ PerfLogImp::Counters::currentJson() const
|
|||||||
|
|
||||||
for (auto const& j : jobs)
|
for (auto const& j : jobs)
|
||||||
{
|
{
|
||||||
if (j.first == jtINVALID)
|
if (!j)
|
||||||
continue;
|
continue;
|
||||||
Json::Value jobj(Json::objectValue);
|
Json::Value jobj(Json::objectValue);
|
||||||
jobj[jss::job] = JobTypes::name(j.first);
|
jobj[jss::job] = std::string(jobTypes[j->first].name);
|
||||||
jobj[jss::duration_us] = std::to_string(
|
jobj[jss::duration_us] = std::to_string(
|
||||||
std::chrono::duration_cast<microseconds>(present - j.second)
|
std::chrono::duration_cast<microseconds>(present - j->second)
|
||||||
.count());
|
.count());
|
||||||
jobsArray.append(jobj);
|
jobsArray.append(jobj);
|
||||||
}
|
}
|
||||||
@@ -414,7 +412,7 @@ PerfLogImp::jobStart(
|
|||||||
}
|
}
|
||||||
std::lock_guard lock(counters_.jobsMutex_);
|
std::lock_guard lock(counters_.jobsMutex_);
|
||||||
if (instance >= 0 && instance < counters_.jobs_.size())
|
if (instance >= 0 && instance < counters_.jobs_.size())
|
||||||
counters_.jobs_[instance] = {type, startTime};
|
counters_.jobs_[instance].emplace(type, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -434,7 +432,7 @@ PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
|
|||||||
}
|
}
|
||||||
std::lock_guard lock(counters_.jobsMutex_);
|
std::lock_guard lock(counters_.jobsMutex_);
|
||||||
if (instance >= 0 && instance < counters_.jobs_.size())
|
if (instance >= 0 && instance < counters_.jobs_.size())
|
||||||
counters_.jobs_[instance] = {jtINVALID, steady_time_point()};
|
counters_.jobs_[instance].reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -442,7 +440,7 @@ PerfLogImp::resizeJobs(int const resize)
|
|||||||
{
|
{
|
||||||
std::lock_guard lock(counters_.jobsMutex_);
|
std::lock_guard lock(counters_.jobsMutex_);
|
||||||
if (resize > counters_.jobs_.size())
|
if (resize > counters_.jobs_.size())
|
||||||
counters_.jobs_.resize(resize, {jtINVALID, steady_time_point()});
|
counters_.jobs_.resize(resize, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -25,12 +25,15 @@
|
|||||||
#include <xrpl/basics/chrono.h>
|
#include <xrpl/basics/chrono.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
#include <xrpl/protocol/jss.h>
|
#include <xrpl/protocol/jss.h>
|
||||||
|
|
||||||
#include <boost/asio/ip/host_name.hpp>
|
#include <boost/asio/ip/host_name.hpp>
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -108,12 +111,12 @@ class PerfLogImp : public PerfLog
|
|||||||
// keys and values are created before more threads are started.
|
// keys and values are created before more threads are started.
|
||||||
std::unordered_map<std::string, Locked<Rpc>> rpc_;
|
std::unordered_map<std::string, Locked<Rpc>> rpc_;
|
||||||
std::unordered_map<JobType, Locked<Jq>> jq_;
|
std::unordered_map<JobType, Locked<Jq>> jq_;
|
||||||
std::vector<std::pair<JobType, steady_time_point>> jobs_;
|
std::vector<std::optional<std::pair<JobType, steady_time_point>>> jobs_;
|
||||||
mutable std::mutex jobsMutex_;
|
mutable std::mutex jobsMutex_;
|
||||||
std::unordered_map<std::uint64_t, MethodStart> methods_;
|
std::unordered_map<std::uint64_t, MethodStart> methods_;
|
||||||
mutable std::mutex methodsMutex_;
|
mutable std::mutex methodsMutex_;
|
||||||
|
|
||||||
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes);
|
Counters(std::set<char const*> const& labels);
|
||||||
Json::Value
|
Json::Value
|
||||||
countersJson() const;
|
countersJson() const;
|
||||||
Json::Value
|
Json::Value
|
||||||
@@ -124,7 +127,7 @@ class PerfLogImp : public PerfLog
|
|||||||
Application& app_;
|
Application& app_;
|
||||||
beast::Journal const j_;
|
beast::Journal const j_;
|
||||||
std::function<void()> const signalStop_;
|
std::function<void()> const signalStop_;
|
||||||
Counters counters_{ripple::RPC::getHandlerNames(), JobTypes::instance()};
|
Counters counters_{ripple::RPC::getHandlerNames()};
|
||||||
std::ofstream logFile_;
|
std::ofstream logFile_;
|
||||||
std::thread thread_;
|
std::thread thread_;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|||||||
@@ -538,7 +538,7 @@ pwaReply(
|
|||||||
{
|
{
|
||||||
std::string out =
|
std::string out =
|
||||||
"HTTP/1.1 " + std::to_string(status) + " " + pwaReason(status) + "\r\n";
|
"HTTP/1.1 " + std::to_string(status) + " " + pwaReason(status) + "\r\n";
|
||||||
out += "Server: " + systemName() +
|
out += "Server: " + std::string(systemName) +
|
||||||
"\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n"
|
"\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n"
|
||||||
"Referrer-Policy: no-referrer\r\n";
|
"Referrer-Policy: no-referrer\r\n";
|
||||||
for (auto const& h : headers)
|
for (auto const& h : headers)
|
||||||
@@ -1985,8 +1985,10 @@ ServerHandler::statusResponse(http_request_type const& request) const
|
|||||||
if (app_.serverOkay(reason))
|
if (app_.serverOkay(reason))
|
||||||
{
|
{
|
||||||
msg.result(boost::beast::http::status::ok);
|
msg.result(boost::beast::http::status::ok);
|
||||||
msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
|
msg.body() = "<!DOCTYPE html><html><head><title>" +
|
||||||
" Test page for rippled</title></head><body><h1>" + systemName() +
|
std::string(systemName) +
|
||||||
|
" Test page for rippled</title></head><body><h1>" +
|
||||||
|
std::string(systemName) +
|
||||||
" Test</h1><p>This page shows rippled http(s) "
|
" Test</h1><p>This page shows rippled http(s) "
|
||||||
"connectivity is working.</p></body></html>";
|
"connectivity is working.</p></body></html>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ private:
|
|||||||
static_cast<uint32_t>(entry.first);
|
static_cast<uint32_t>(entry.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[jss::native_currency_code] = systemCurrencyCode();
|
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
|
||||||
|
|
||||||
// Hook API: enough to regenerate hook/extern.h, hookapi.h keylet
|
// Hook API: enough to regenerate hook/extern.h, hookapi.h keylet
|
||||||
// constants and error.h without the xahaud source tree.
|
// constants and error.h without the xahaud source tree.
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ doServerInfo(RPC::JsonContext& context)
|
|||||||
context.params.isMember(jss::counters) &&
|
context.params.isMember(jss::counters) &&
|
||||||
context.params[jss::counters].asBool());
|
context.params[jss::counters].asBool());
|
||||||
|
|
||||||
ret[jss::native_currency_code] = systemCurrencyCode();
|
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ doServerState(RPC::JsonContext& context)
|
|||||||
context.params.isMember(jss::counters) &&
|
context.params.isMember(jss::counters) &&
|
||||||
context.params[jss::counters].asBool());
|
context.params[jss::counters].asBool());
|
||||||
|
|
||||||
ret[jss::native_currency_code] = systemCurrencyCode();
|
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Json::Value
|
|||||||
doStop(RPC::JsonContext& context)
|
doStop(RPC::JsonContext& context)
|
||||||
{
|
{
|
||||||
context.app.signalStop();
|
context.app.signalStop();
|
||||||
return RPC::makeObjectValue(systemName() + " server stopping");
|
return RPC::makeObjectValue(std::string(systemName) + " server stopping");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
@@ -114,15 +114,15 @@ namespace detail {
|
|||||||
// The slab cutoffs and the number of megabytes per allocation are customized
|
// The slab cutoffs and the number of megabytes per allocation are customized
|
||||||
// based on the number of objects of each size we expect to need at any point
|
// based on the number of objects of each size we expect to need at any point
|
||||||
// in time and with an eye to minimize the number of slack bytes in a block.
|
// in time and with an eye to minimize the number of slack bytes in a block.
|
||||||
inline SlabAllocatorSet<SHAMapItem> slabber({
|
inline constinit slab::allocator_t<SHAMapItem,
|
||||||
{ 128, megabytes(std::size_t(60)) },
|
slab::config<1000000, 128>,
|
||||||
{ 192, megabytes(std::size_t(46)) },
|
slab::config<1000000, 296>,
|
||||||
{ 272, megabytes(std::size_t(60)) },
|
slab::config<125000, 392>,
|
||||||
{ 384, megabytes(std::size_t(56)) },
|
slab::config<125000, 520>,
|
||||||
{ 564, megabytes(std::size_t(40)) },
|
slab::config<62500, 760>,
|
||||||
{ 772, megabytes(std::size_t(46)) },
|
slab::config<62500, 856>,
|
||||||
{ 1052, megabytes(std::size_t(60)) },
|
slab::config<31250, 1048>
|
||||||
});
|
> slabber;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@@ -130,7 +130,11 @@ inline SlabAllocatorSet<SHAMapItem> slabber({
|
|||||||
inline void
|
inline void
|
||||||
intrusive_ptr_add_ref(SHAMapItem const* x)
|
intrusive_ptr_add_ref(SHAMapItem const* x)
|
||||||
{
|
{
|
||||||
// This can only happen if someone releases the last reference to the
|
assert(x);
|
||||||
|
|
||||||
|
// In order to call this, we must already have an intrusive pointer
|
||||||
|
// to this item, so it's reference count should be at least 1. This
|
||||||
|
// would only happen if someone released that last reference to the
|
||||||
// item while we were trying to increment the refcount.
|
// item while we were trying to increment the refcount.
|
||||||
if (x->refcount_++ == 0)
|
if (x->refcount_++ == 0)
|
||||||
LogicError("SHAMapItem: the reference count is 0!");
|
LogicError("SHAMapItem: the reference count is 0!");
|
||||||
@@ -139,20 +143,23 @@ intrusive_ptr_add_ref(SHAMapItem const* x)
|
|||||||
inline void
|
inline void
|
||||||
intrusive_ptr_release(SHAMapItem const* x)
|
intrusive_ptr_release(SHAMapItem const* x)
|
||||||
{
|
{
|
||||||
|
assert(x);
|
||||||
|
|
||||||
if (--x->refcount_ == 0)
|
if (--x->refcount_ == 0)
|
||||||
{
|
{
|
||||||
auto p = reinterpret_cast<std::uint8_t const*>(x);
|
auto p = reinterpret_cast<std::uint8_t const*>(x);
|
||||||
|
|
||||||
// The SHAMapItem constuctor isn't trivial (because the destructor
|
// We need to invoke the destructor for this object before we
|
||||||
// for CountedObject isn't) so we can't avoid calling it here, but
|
// release the memory.
|
||||||
// plan for a future where we might not need to.
|
std::destroy_at(x);
|
||||||
if constexpr (!std::is_trivially_destructible_v<SHAMapItem>)
|
|
||||||
std::destroy_at(x);
|
|
||||||
|
|
||||||
// If the slabber doens't claim this pointer, it was allocated
|
// If the slabber doesn't claim this pointer, it was allocated
|
||||||
// manually, so we free it manually.
|
// manually (with the aligned operator new[] in make_shamapitem),
|
||||||
|
// so we free it manually with the matching aligned delete.
|
||||||
if (!detail::slabber.deallocate(const_cast<std::uint8_t*>(p)))
|
if (!detail::slabber.deallocate(const_cast<std::uint8_t*>(p)))
|
||||||
delete[] p;
|
::operator delete[](
|
||||||
|
const_cast<std::uint8_t*>(p),
|
||||||
|
std::align_val_t(alignof(SHAMapItem)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,18 +175,20 @@ make_shamapitem(uint256 const& tag, Slice data)
|
|||||||
// If we can't grab memory from the slab allocators, we fall back to
|
// If we can't grab memory from the slab allocators, we fall back to
|
||||||
// the standard library and try to grab a precisely-sized memory block:
|
// the standard library and try to grab a precisely-sized memory block:
|
||||||
if (raw == nullptr)
|
if (raw == nullptr)
|
||||||
raw = new std::uint8_t[sizeof(SHAMapItem) + data.size()];
|
raw = new (std::align_val_t(alignof(SHAMapItem)))
|
||||||
|
std::uint8_t[sizeof(SHAMapItem) + data.size()];
|
||||||
|
assert(reinterpret_cast<std::uintptr_t>(raw) % alignof(SHAMapItem) == 0);
|
||||||
|
|
||||||
// We do not increment the reference count here on purpose: the
|
// We do not increment the reference count here on purpose: the
|
||||||
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
|
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
|
||||||
// that the refcount can never be zero before incrementing as an
|
// that the refcount can never be zero before incrementing as an
|
||||||
// invariant.
|
// invariant.
|
||||||
return {new (raw) SHAMapItem{tag, data}, false};
|
return {
|
||||||
|
new (std::assume_aligned<alignof(SHAMapItem)>(raw))
|
||||||
|
SHAMapItem{tag, data},
|
||||||
|
false};
|
||||||
}
|
}
|
||||||
|
|
||||||
static_assert(alignof(SHAMapItem) != 40);
|
|
||||||
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4);
|
|
||||||
|
|
||||||
inline boost::intrusive_ptr<SHAMapItem>
|
inline boost::intrusive_ptr<SHAMapItem>
|
||||||
make_shamapitem(SHAMapItem const& other)
|
make_shamapitem(SHAMapItem const& other)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user