mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
@@ -34,6 +35,164 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
|
||||
class StringBufferPool {
|
||||
public:
|
||||
// ----- Empty index marker -----
|
||||
static constexpr std::uint32_t kEmptyIdx = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
// ----- Single-word CAS target: {tag | idx} with pack/unpack -----
|
||||
struct Head {
|
||||
std::uint32_t tag;
|
||||
std::uint32_t idx; // kEmptyIdx means empty
|
||||
|
||||
static std::uint64_t pack(Head h) noexcept {
|
||||
return (std::uint64_t(h.tag) << 32) | h.idx;
|
||||
}
|
||||
static Head unpack(std::uint64_t v) noexcept {
|
||||
return Head{ std::uint32_t(v >> 32), std::uint32_t(v) };
|
||||
}
|
||||
};
|
||||
|
||||
// ----- Internal node -----
|
||||
struct Node {
|
||||
std::uint32_t next_idx{kEmptyIdx};
|
||||
std::uint32_t self_idx{kEmptyIdx};
|
||||
std::string buf{};
|
||||
};
|
||||
static_assert(std::is_standard_layout_v<Node>, "Node must be standard layout");
|
||||
|
||||
// ----- User-facing move-only RAII handle -----
|
||||
class Handle {
|
||||
public:
|
||||
Handle() = default;
|
||||
Handle(Handle&& other) noexcept
|
||||
: owner_(other.owner_), node_(other.node_) {
|
||||
other.owner_ = nullptr; other.node_ = nullptr;
|
||||
}
|
||||
Handle& operator=(Handle&& other) noexcept {
|
||||
if (this != &other) {
|
||||
// Return current if still held
|
||||
if (owner_ && node_) owner_->give_back(std::move(*this));
|
||||
owner_ = other.owner_;
|
||||
node_ = other.node_;
|
||||
other.owner_ = nullptr;
|
||||
other.node_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Handle(const Handle&) = delete;
|
||||
Handle& operator=(const Handle&) = delete;
|
||||
|
||||
~Handle() noexcept {
|
||||
if (owner_ && node_) owner_->give_back(std::move(*this));
|
||||
}
|
||||
|
||||
bool valid() const noexcept { return node_ != nullptr; }
|
||||
std::string& string() noexcept { return node_->buf; }
|
||||
const std::string& string() const noexcept { return node_->buf; }
|
||||
|
||||
private:
|
||||
friend class StringBufferPool;
|
||||
Handle(StringBufferPool* owner, Node* n) : owner_(owner), node_(n) {}
|
||||
|
||||
StringBufferPool* owner_ = nullptr;
|
||||
Node* node_ = nullptr;
|
||||
};
|
||||
|
||||
explicit StringBufferPool(std::uint32_t grow_by = 20)
|
||||
: grow_by_(grow_by), head_(Head::pack({0, kEmptyIdx})) {}
|
||||
|
||||
// Rent a buffer; grows on demand. Returns move-only RAII handle.
|
||||
Handle rent() {
|
||||
for (;;) {
|
||||
std::uint64_t old64 = head_.load(std::memory_order_acquire);
|
||||
Head old = Head::unpack(old64);
|
||||
if (old.idx == kEmptyIdx) { grow_(); continue; } // rare slow path
|
||||
|
||||
Node& n = nodes_[old.idx];
|
||||
std::uint32_t next = n.next_idx;
|
||||
|
||||
Head neu{ std::uint32_t(old.tag + 1), next };
|
||||
if (head_.compare_exchange_weak(old64, Head::pack(neu),
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire)) {
|
||||
return {this, &n};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Only the pool/handle can call this
|
||||
void give_back(Handle&& h) noexcept {
|
||||
Node* node = h.node_;
|
||||
if (!node) return; // already invalid
|
||||
const std::uint32_t idx = node->self_idx;
|
||||
|
||||
node->buf.clear();
|
||||
|
||||
for (;;) {
|
||||
std::uint64_t old64 = head_.load(std::memory_order_acquire);
|
||||
Head old = Head::unpack(old64);
|
||||
|
||||
node->next_idx = old.idx;
|
||||
|
||||
Head neu{ std::uint32_t(old.tag + 1), idx };
|
||||
if (head_.compare_exchange_weak(old64, Head::pack(neu),
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire)) {
|
||||
// Invalidate handle (prevents double return)
|
||||
h.owner_ = nullptr;
|
||||
h.node_ = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void grow_() {
|
||||
if (Head::unpack(head_.load(std::memory_order_acquire)).idx != kEmptyIdx) return;
|
||||
std::scoped_lock lk(grow_mu_);
|
||||
if (Head::unpack(head_.load(std::memory_order_acquire)).idx != kEmptyIdx) return;
|
||||
|
||||
const std::uint32_t base = static_cast<std::uint32_t>(nodes_.size());
|
||||
nodes_.resize(base + grow_by_); // indices [base .. base+grow_by_-1]
|
||||
|
||||
// Init nodes and local chain
|
||||
for (std::uint32_t i = 0; i < grow_by_; ++i) {
|
||||
std::uint32_t idx = base + i;
|
||||
Node& n = nodes_[idx];
|
||||
n.self_idx = idx;
|
||||
n.next_idx = (i + 1 < grow_by_) ? (idx + 1) : kEmptyIdx;
|
||||
}
|
||||
|
||||
// Splice chain onto global head: [base .. base+grow_by_-1]
|
||||
const std::uint32_t chain_head = base;
|
||||
const std::uint32_t chain_tail = base + grow_by_ - 1;
|
||||
|
||||
for (;;) {
|
||||
std::uint64_t old64 = head_.load(std::memory_order_acquire);
|
||||
Head old = Head::unpack(old64);
|
||||
|
||||
nodes_[chain_tail].next_idx = old.idx; // tail -> old head
|
||||
Head neu{ std::uint32_t(old.tag + 1), chain_head };
|
||||
|
||||
if (head_.compare_exchange_weak(old64, Head::pack(neu),
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint32_t grow_by_;
|
||||
std::atomic<std::uint64_t> head_; // single 64-bit CAS (Head packed)
|
||||
std::mutex grow_mu_; // only during growth
|
||||
std::deque<Node> nodes_; // stable storage for nodes/strings
|
||||
};
|
||||
} // namespace beast
|
||||
|
||||
namespace ripple::log {
|
||||
template <typename T>
|
||||
class LogParameter
|
||||
@@ -181,10 +340,10 @@ public:
|
||||
buffer_.append(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string_view
|
||||
void
|
||||
finish()
|
||||
{
|
||||
return std::string_view{buffer_.c_str(), buffer_.size() - 1};
|
||||
buffer_.pop_back();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -323,18 +482,25 @@ public:
|
||||
|
||||
class Sink;
|
||||
|
||||
using MessagePoolNode = lockfree::queue<std::string>::Node*;
|
||||
|
||||
class JsonLogContext
|
||||
{
|
||||
std::string buffer_;
|
||||
MessagePoolNode messageBuffer_;
|
||||
detail::SimpleJsonWriter messageParamsWriter_;
|
||||
bool hasMessageParams_ = false;
|
||||
|
||||
public:
|
||||
JsonLogContext() : messageParamsWriter_(buffer_)
|
||||
explicit JsonLogContext()
|
||||
: messageBuffer_(rentFromPool())
|
||||
, messageParamsWriter_(messageBuffer_->data)
|
||||
{
|
||||
buffer_.reserve(1024 * 5);
|
||||
messageBuffer_->data.reserve(1024 * 5);
|
||||
}
|
||||
|
||||
MessagePoolNode
|
||||
messageBuffer() { return messageBuffer_; }
|
||||
|
||||
void
|
||||
startMessageParams()
|
||||
{
|
||||
@@ -379,6 +545,7 @@ private:
|
||||
static std::shared_mutex globalLogAttributesMutex_;
|
||||
static bool jsonLogsEnabled_;
|
||||
|
||||
static lockfree::queue<std::string> messagePool_;
|
||||
static thread_local JsonLogContext currentJsonLogContext_;
|
||||
|
||||
// Invariant: m_sink always points to a valid Sink
|
||||
@@ -389,12 +556,26 @@ private:
|
||||
std::source_location location,
|
||||
severities::Severity severity) const;
|
||||
|
||||
static std::string_view
|
||||
static MessagePoolNode
|
||||
formatLog(std::string const& message);
|
||||
|
||||
public:
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static MessagePoolNode
|
||||
rentFromPool()
|
||||
{
|
||||
auto node = messagePool_.pop();
|
||||
if (!node)
|
||||
{
|
||||
node = new lockfree::queue<std::string>::Node();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static void
|
||||
returnMessageNode(MessagePoolNode node) { messagePool_.push(node); }
|
||||
|
||||
static void
|
||||
enableStructuredJournal();
|
||||
|
||||
@@ -444,7 +625,7 @@ public:
|
||||
level is below the current threshold().
|
||||
*/
|
||||
virtual void
|
||||
write(Severity level, std::string_view text) = 0;
|
||||
write(Severity level, std::string_view text, MessagePoolNode owner = nullptr) = 0;
|
||||
|
||||
/** Bypass filter and write text to the sink at the specified severity.
|
||||
* Always write the message, but maintain the same formatting as if
|
||||
@@ -454,7 +635,7 @@ public:
|
||||
* @param text Text to write to sink.
|
||||
*/
|
||||
virtual void
|
||||
writeAlways(Severity level, std::string_view text) = 0;
|
||||
writeAlways(Severity level, std::string_view text, MessagePoolNode owner = nullptr) = 0;
|
||||
|
||||
private:
|
||||
Severity thresh_;
|
||||
|
||||
Reference in New Issue
Block a user