22
22
23
23
#include < xrpl/beast/utility/instrumentation.h>
24
24
25
+ #include < thread>
25
26
#include < atomic>
26
27
#include < charconv>
27
28
#include < cstring>
34
35
#include < string_view>
35
36
#include < utility>
36
37
38
+ namespace beast {
39
+
40
+ class StringBufferPool {
41
+ public:
42
+ // ----- Empty index marker -----
43
+ static constexpr std::uint32_t kEmptyIdx = std::numeric_limits<std::uint32_t >::max();
44
+
45
+ // ----- Single-word CAS target: {tag | idx} with pack/unpack -----
46
+ struct Head {
47
+ std::uint32_t tag;
48
+ std::uint32_t idx; // kEmptyIdx means empty
49
+
50
+ static std::uint64_t pack (Head h) noexcept {
51
+ return (std::uint64_t (h.tag ) << 32 ) | h.idx ;
52
+ }
53
+ static Head unpack (std::uint64_t v) noexcept {
54
+ return Head{ std::uint32_t (v >> 32 ), std::uint32_t (v) };
55
+ }
56
+ };
57
+
58
+ // ----- Internal node -----
59
+ struct Node {
60
+ std::uint32_t next_idx{kEmptyIdx };
61
+ std::uint32_t self_idx{kEmptyIdx };
62
+ std::string buf{};
63
+ };
64
+ static_assert (std::is_standard_layout_v<Node>, " Node must be standard layout" );
65
+
66
+ // ----- User-facing move-only RAII handle -----
67
+ class Handle {
68
+ public:
69
+ Handle () = default ;
70
+ Handle (Handle&& other) noexcept
71
+ : owner_(other.owner_), node_(other.node_) {
72
+ other.owner_ = nullptr ; other.node_ = nullptr ;
73
+ }
74
+ Handle& operator =(Handle&& other) noexcept {
75
+ if (this != &other) {
76
+ // Return current if still held
77
+ if (owner_ && node_) owner_->give_back (std::move (*this ));
78
+ owner_ = other.owner_ ;
79
+ node_ = other.node_ ;
80
+ other.owner_ = nullptr ;
81
+ other.node_ = nullptr ;
82
+ }
83
+ return *this ;
84
+ }
85
+
86
+ Handle (const Handle&) = delete ;
87
+ Handle& operator =(const Handle&) = delete ;
88
+
89
+ ~Handle () noexcept {
90
+ if (owner_ && node_) owner_->give_back (std::move (*this ));
91
+ }
92
+
93
+ bool valid () const noexcept { return node_ != nullptr ; }
94
+ std::string& string () noexcept { return node_->buf ; }
95
+ const std::string& string () const noexcept { return node_->buf ; }
96
+
97
+ private:
98
+ friend class StringBufferPool ;
99
+ Handle (StringBufferPool* owner, Node* n) : owner_(owner), node_(n) {}
100
+
101
+ StringBufferPool* owner_ = nullptr ;
102
+ Node* node_ = nullptr ;
103
+ };
104
+
105
+ explicit StringBufferPool (std::uint32_t grow_by = 20 )
106
+ : grow_by_(grow_by), head_(Head::pack({0 , kEmptyIdx })) {}
107
+
108
+ // Rent a buffer; grows on demand. Returns move-only RAII handle.
109
+ Handle rent () {
110
+ for (;;) {
111
+ std::uint64_t old64 = head_.load (std::memory_order_acquire);
112
+ Head old = Head::unpack (old64);
113
+ if (old.idx == kEmptyIdx ) { grow_ (); continue ; } // rare slow path
114
+
115
+ Node& n = nodes_[old.idx ];
116
+ std::uint32_t next = n.next_idx ;
117
+
118
+ Head neu{ std::uint32_t (old.tag + 1 ), next };
119
+ if (head_.compare_exchange_weak (old64, Head::pack (neu),
120
+ std::memory_order_acq_rel,
121
+ std::memory_order_acquire)) {
122
+ return {this , &n};
123
+ }
124
+ }
125
+ }
126
+
127
+ private:
128
+ // Only the pool/handle can call this
129
+ void give_back (Handle&& h) noexcept {
130
+ Node* node = h.node_ ;
131
+ if (!node) return ; // already invalid
132
+ const std::uint32_t idx = node->self_idx ;
133
+
134
+ node->buf .clear ();
135
+
136
+ for (;;) {
137
+ std::uint64_t old64 = head_.load (std::memory_order_acquire);
138
+ Head old = Head::unpack (old64);
139
+
140
+ node->next_idx = old.idx ;
141
+
142
+ Head neu{ std::uint32_t (old.tag + 1 ), idx };
143
+ if (head_.compare_exchange_weak (old64, Head::pack (neu),
144
+ std::memory_order_acq_rel,
145
+ std::memory_order_acquire)) {
146
+ // Invalidate handle (prevents double return)
147
+ h.owner_ = nullptr ;
148
+ h.node_ = nullptr ;
149
+ return ;
150
+ }
151
+ }
152
+ }
153
+
154
+ void grow_ () {
155
+ if (Head::unpack (head_.load (std::memory_order_acquire)).idx != kEmptyIdx ) return ;
156
+ std::scoped_lock lk (grow_mu_);
157
+ if (Head::unpack (head_.load (std::memory_order_acquire)).idx != kEmptyIdx ) return ;
158
+
159
+ const std::uint32_t base = static_cast <std::uint32_t >(nodes_.size ());
160
+ nodes_.resize (base + grow_by_); // indices [base .. base+grow_by_-1]
161
+
162
+ // Init nodes and local chain
163
+ for (std::uint32_t i = 0 ; i < grow_by_; ++i) {
164
+ std::uint32_t idx = base + i;
165
+ Node& n = nodes_[idx];
166
+ n.self_idx = idx;
167
+ n.next_idx = (i + 1 < grow_by_) ? (idx + 1 ) : kEmptyIdx ;
168
+ }
169
+
170
+ // Splice chain onto global head: [base .. base+grow_by_-1]
171
+ const std::uint32_t chain_head = base;
172
+ const std::uint32_t chain_tail = base + grow_by_ - 1 ;
173
+
174
+ for (;;) {
175
+ std::uint64_t old64 = head_.load (std::memory_order_acquire);
176
+ Head old = Head::unpack (old64);
177
+
178
+ nodes_[chain_tail].next_idx = old.idx ; // tail -> old head
179
+ Head neu{ std::uint32_t (old.tag + 1 ), chain_head };
180
+
181
+ if (head_.compare_exchange_weak (old64, Head::pack (neu),
182
+ std::memory_order_acq_rel,
183
+ std::memory_order_acquire)) {
184
+ break ;
185
+ }
186
+ }
187
+ }
188
+
189
+ const std::uint32_t grow_by_;
190
+ std::atomic<std::uint64_t > head_; // single 64-bit CAS (Head packed)
191
+ std::mutex grow_mu_; // only during growth
192
+ std::deque<Node> nodes_; // stable storage for nodes/strings
193
+ };
194
+ } // namespace beast
195
+
37
196
namespace ripple ::log {
38
197
template <typename T>
39
198
class LogParameter
@@ -181,10 +340,10 @@ class SimpleJsonWriter
181
340
buffer_.append (str);
182
341
}
183
342
184
- [[nodiscard]] std::string_view
343
+ void
185
344
finish ()
186
345
{
187
- return std::string_view{ buffer_.c_str (), buffer_. size () - 1 } ;
346
+ buffer_.pop_back () ;
188
347
}
189
348
190
349
private:
@@ -323,18 +482,25 @@ class Journal
323
482
324
483
class Sink ;
325
484
485
+ using MessagePoolNode = lockfree::queue<std::string>::Node*;
486
+
326
487
class JsonLogContext
327
488
{
328
- std::string buffer_ ;
489
+ MessagePoolNode messageBuffer_ ;
329
490
detail::SimpleJsonWriter messageParamsWriter_;
330
491
bool hasMessageParams_ = false ;
331
492
332
493
public:
333
- JsonLogContext () : messageParamsWriter_(buffer_)
494
+ explicit JsonLogContext ()
495
+ : messageBuffer_(rentFromPool())
496
+ , messageParamsWriter_(messageBuffer_->data)
334
497
{
335
- buffer_ .reserve (1024 * 5 );
498
+ messageBuffer_-> data .reserve (1024 * 5 );
336
499
}
337
500
501
+ MessagePoolNode
502
+ messageBuffer () { return messageBuffer_; }
503
+
338
504
void
339
505
startMessageParams ()
340
506
{
@@ -379,6 +545,7 @@ class Journal
379
545
static std::shared_mutex globalLogAttributesMutex_;
380
546
static bool jsonLogsEnabled_;
381
547
548
+ static lockfree::queue<std::string> messagePool_;
382
549
static thread_local JsonLogContext currentJsonLogContext_;
383
550
384
551
// Invariant: m_sink always points to a valid Sink
@@ -389,12 +556,26 @@ class Journal
389
556
std::source_location location,
390
557
severities::Severity severity) const ;
391
558
392
- static std::string_view
559
+ static MessagePoolNode
393
560
formatLog (std::string const & message);
394
561
395
562
public:
396
563
// --------------------------------------------------------------------------
397
564
565
+ static MessagePoolNode
566
+ rentFromPool ()
567
+ {
568
+ auto node = messagePool_.pop ();
569
+ if (!node)
570
+ {
571
+ node = new lockfree::queue<std::string>::Node ();
572
+ }
573
+ return node;
574
+ }
575
+
576
+ static void
577
+ returnMessageNode (MessagePoolNode node) { messagePool_.push (node); }
578
+
398
579
static void
399
580
enableStructuredJournal ();
400
581
@@ -444,7 +625,7 @@ class Journal
444
625
level is below the current threshold().
445
626
*/
446
627
virtual void
447
- write (Severity level, std::string_view text) = 0 ;
628
+ write (Severity level, std::string_view text, MessagePoolNode owner = nullptr ) = 0 ;
448
629
449
630
/* * Bypass filter and write text to the sink at the specified severity.
450
631
* Always write the message, but maintain the same formatting as if
@@ -454,7 +635,7 @@ class Journal
454
635
* @param text Text to write to sink.
455
636
*/
456
637
virtual void
457
- writeAlways (Severity level, std::string_view text) = 0 ;
638
+ writeAlways (Severity level, std::string_view text, MessagePoolNode owner = nullptr ) = 0 ;
458
639
459
640
private:
460
641
Severity thresh_;
0 commit comments