xrpld
Loading...
Searching...
No Matches
IntrusiveRefCounts.h
1#pragma once
2
3#include <xrpl/beast/utility/instrumentation.h>
4
5#include <atomic>
6#include <cstddef>
7#include <cstdint>
8
9namespace xrpl {
10
24
35
44{
45 virtual ~IntrusiveRefCounts() noexcept;
46
47 // This must be `noexcept` or the make_SharedIntrusive function could leak
48 // memory.
49 void
50 addStrongRef() const noexcept;
51
52 void
53 addWeakRef() const noexcept;
54
56 releaseStrongRef() const;
57
58 // Same as:
59 // {
60 // addWeakRef();
61 // return releaseStrongRef;
62 // }
63 // done as one atomic operation
66
68 releaseWeakRef() const;
69
70 // Returns true is able to checkout a strong ref. False otherwise
71 bool
72 checkoutStrongRefFromWeak() const noexcept;
73
74 bool
75 expired() const noexcept;
76
78 useCount() const noexcept;
79
80 // This function MUST be called after a partial destructor finishes running.
81 // Calling this function may cause other threads to delete the object
82 // pointed to by `o`, so `o` should never be used after calling this
83 // function. The parameter will be set to a `nullptr` after calling this
84 // function to emphasize that it should not be used.
85 // Note: This is intentionally NOT called at the end of `partialDestructor`.
86 // The reason for this is if new classes are written to support this smart
87 // pointer class, they need to write their own `partialDestructor` function
88 // and ensure `partialDestructorFinished` is called at the end. Putting this
89 // call inside the smart pointer class itself is expected to be less error
90 // prone.
91 // Note: The "two-star" programming is intentional. It emphasizes that `o`
92 // may be deleted and the unergonomic API is meant to signal the special
93 // nature of this function call to callers.
94 // Note: This is a template to support incompletely defined classes.
95 template <class T>
96 friend void
98
99private:
100 // TODO: We may need to use a uint64_t for both counts. This will reduce the
101 // memory savings. We need to audit the code to make sure 16 bit counts are
102 // enough for strong pointers and 14 bit counts are enough for weak
103 // pointers. Use type aliases to make it easy to switch types.
105 static constexpr size_t kStrongCountNumBits = sizeof(CountType) * 8;
106 static constexpr size_t kWeakCountNumBits = kStrongCountNumBits - 2;
108 static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
109 static constexpr FieldType kOne = 1;
110
143
145
152 static constexpr FieldType kStrongDelta = 1;
153
161
170
178
184
189 static constexpr FieldType kValueMask = ~kTagMask;
190
194 static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
195
199 static constexpr FieldType kWeakMask =
201
206 {
222 RefCountPair(FieldType v) noexcept;
223 RefCountPair(CountType s, CountType w) noexcept;
224
228 [[nodiscard]] FieldType
229 combinedValue() const noexcept;
230
231 static constexpr CountType kMaxStrongValue =
232 static_cast<CountType>((kOne << kStrongCountNumBits) - 1);
233 static constexpr CountType kMaxWeakValue =
234 static_cast<CountType>((kOne << kWeakCountNumBits) - 1);
242 };
243};
244
245inline void
247{
248 refCounts_.fetch_add(kStrongDelta, std::memory_order_acq_rel);
249}
250
251inline void
253{
254 refCounts_.fetch_add(kWeakDelta, std::memory_order_acq_rel);
255}
256
259{
260 // Subtract `strongDelta` from refCounts. If this releases the last strong
261 // ref, set the `partialDestroyStarted` bit. It is important that the ref
262 // count and the `partialDestroyStartedBit` are changed atomically (hence
263 // the loop and `compare_exchange` op). If this didn't need to be done
264 // atomically, the loop could be replaced with a `fetch_sub` and a
265 // conditional `fetch_or`. This loop will almost always run once.
266
267 using enum ReleaseStrongRefAction;
268 auto prevIntVal = refCounts_.load(std::memory_order_acquire);
269 while (true)
270 {
271 RefCountPair const prevVal{prevIntVal};
272 XRPL_ASSERT(
273 (prevVal.strong >= kStrongDelta),
274 "xrpl::IntrusiveRefCounts::releaseStrongRef : previous ref "
275 "higher than new");
276 auto nextIntVal = prevIntVal - kStrongDelta;
278 if (prevVal.strong == 1)
279 {
280 if (prevVal.weak == 0)
281 {
282 action = Destroy;
283 }
284 else
285 {
286 nextIntVal |= kPartialDestroyStartedMask;
287 action = PartialDestroy;
288 }
289 }
290
291 if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
292 {
293 // Can't be in partial destroy because only decrementing the strong
294 // count to zero can start a partial destroy, and that can't happen
295 // twice.
296 XRPL_ASSERT(
297 (action == NoOp) || !(prevIntVal & kPartialDestroyStartedMask),
298 "xrpl::IntrusiveRefCounts::releaseStrongRef : not in partial "
299 "destroy");
300 return action;
301 }
302 }
303}
304
307{
308 using enum ReleaseStrongRefAction;
309
310 static_assert(kWeakDelta > kStrongDelta);
311 static constexpr auto kDelta = kWeakDelta - kStrongDelta;
312 auto prevIntVal = refCounts_.load(std::memory_order_acquire);
313 // This loop will almost always run once. The loop is needed to atomically
314 // change the counts and flags (the count could be atomically changed, but
315 // the flags depend on the current value of the counts).
316 //
317 // Note: If this becomes a perf bottleneck, the `partialDestroyStartedMask`
318 // may be able to be set non-atomically. But it is easier to reason about
319 // the code if the flag is set atomically.
320 while (true)
321 {
322 RefCountPair const prevVal{prevIntVal};
323 // Converted the last strong pointer to a weak pointer.
324 //
325 // Can't be in partial destroy because only decrementing the
326 // strong count to zero can start a partial destroy, and that
327 // can't happen twice.
328 XRPL_ASSERT(
329 (!prevVal.partialDestroyStartedBit),
330 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not in "
331 "partial destroy");
332
333 auto nextIntVal = prevIntVal + kDelta;
335 if (prevVal.strong == 1)
336 {
337 if (prevVal.weak == 0)
338 {
339 action = NoOp;
340 }
341 else
342 {
343 nextIntVal |= kPartialDestroyStartedMask;
344 action = PartialDestroy;
345 }
346 }
347 if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
348 {
349 XRPL_ASSERT(
350 (!(prevIntVal & kPartialDestroyStartedMask)),
351 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not "
352 "started partial destroy");
353 return action;
354 }
355 }
356}
357
360{
361 auto prevIntVal = refCounts_.fetch_sub(kWeakDelta, std::memory_order_acq_rel);
362 RefCountPair prev = prevIntVal;
363 if (prev.weak == 1 && prev.strong == 0)
364 {
365 if (prev.partialDestroyStartedBit == 0u)
366 {
367 // This case should only be hit if the partialDestroyStartedBit is
368 // set non-atomically (and even then very rarely). The code is kept
369 // in case we need to set the flag non-atomically for perf reasons.
370 refCounts_.wait(prevIntVal, std::memory_order_acquire);
371 prevIntVal = refCounts_.load(std::memory_order_acquire);
372 prev = RefCountPair{prevIntVal};
373 }
374 if (prev.partialDestroyFinishedBit == 0u)
375 {
376 // partial destroy MUST finish before running a full destroy (when
377 // using weak pointers)
378 refCounts_.wait(prevIntVal - kWeakDelta, std::memory_order_acquire);
379 }
381 }
383}
384
385inline bool
387{
388 auto curValue = RefCountPair{1, 1}.combinedValue();
389 auto desiredValue = RefCountPair{2, 1}.combinedValue();
390
391 while (!refCounts_.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel))
392 {
393 RefCountPair const prev{curValue};
394 if (prev.strong == 0u)
395 return false;
396
397 desiredValue = curValue + kStrongDelta;
398 }
399 return true;
400}
401
402inline bool
404{
405 RefCountPair const val = refCounts_.load(std::memory_order_acquire);
406 return val.strong == 0;
407}
408
409inline std::size_t
411{
412 RefCountPair const val = refCounts_.load(std::memory_order_acquire);
413 return val.strong;
414}
415
417{
418#ifndef NDEBUG
419 auto v = refCounts_.load(std::memory_order_acquire);
420 XRPL_ASSERT(
421 (!(v & kValueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
422 auto t = v & kTagMask;
423 XRPL_ASSERT((!t || t == kTagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
424#endif
425}
426
427//------------------------------------------------------------------------------
428
430 : strong{static_cast<CountType>(v & kStrongMask)}
431 , weak{static_cast<CountType>((v & kWeakMask) >> kStrongCountNumBits)}
434{
435 XRPL_ASSERT(
437 "xrpl::IntrusiveRefCounts::RefCountPair(FieldType) : inputs inside "
438 "range");
439}
440
444 : strong{s}, weak{w}
445{
446 XRPL_ASSERT(
448 "xrpl::IntrusiveRefCounts::RefCountPair(CountType, CountType) : "
449 "inputs inside range");
450}
451
454{
455 XRPL_ASSERT(
457 "xrpl::IntrusiveRefCounts::RefCountPair::combinedValue : inputs "
458 "inside range");
459 return (static_cast<IntrusiveRefCounts::FieldType>(weak)
463}
464
465template <class T>
466inline void
468{
469 T& self = **o;
471 self.refCounts_.fetch_or(IntrusiveRefCounts::kPartialDestroyFinishedMask);
472 XRPL_ASSERT(
474 "xrpl::partialDestructorFinished : not a weak ref");
475 if (!p.weak)
476 {
477 // There was a weak count before the partial destructor ran (or we would
478 // have run the full destructor) and now there isn't a weak count. Some
479 // thread is waiting to run the destructor.
480 self.refCounts_.notify_one();
481 }
482 // Set the pointer to null to emphasize that the object shouldn't be used
483 // after calling this function as it may be destroyed in another thread.
484 *o = nullptr;
485}
486//------------------------------------------------------------------------------
487
488} // namespace xrpl
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
ReleaseStrongRefAction
Action to perform when releasing a strong pointer.
ReleaseWeakRefAction
Action to perform when releasing a weak pointer.
Unpack the count and tag fields from the packed atomic integer form.
FieldType combinedValue() const noexcept
Convert back to the packed integer form.
static constexpr CountType kMaxStrongValue
FieldType partialDestroyStartedBit
The partialDestroyStartedBit is set to on when the partial destroy function is started.
static constexpr CountType kCheckStrongMaxValue
Put an extra margin to detect when running up against limits.
FieldType partialDestroyFinishedBit
The partialDestroyFinishedBit is set to on when the partial destroy function has finished.
static constexpr CountType kCheckWeakMaxValue
Implement the strong count, weak count, and bit flags for an intrusive pointer.
bool checkoutStrongRefFromWeak() const noexcept
static constexpr FieldType kPartialDestroyFinishedMask
Flag that is set when the partialDestroy function has finished running.
static constexpr FieldType kWeakMask
Mask that will zero out everything except the weak count.
static constexpr size_t kWeakCountNumBits
ReleaseStrongRefAction addWeakReleaseStrongRef() const
static constexpr FieldType kTagMask
Mask that will zero out all the count bits and leave the tag bits unchanged.
void addWeakRef() const noexcept
std::atomic< FieldType > refCounts_
refCounts consists of four fields that are treated atomically:
static constexpr FieldType kStrongDelta
Amount to change the strong count when adding or releasing a reference.
bool expired() const noexcept
static constexpr FieldType kStrongMask
Mask that will zero out everything except the strong count.
friend void partialDestructorFinished(T **o)
static constexpr size_t kFieldTypeBits
virtual ~IntrusiveRefCounts() noexcept
static constexpr FieldType kWeakDelta
Amount to change the weak count when adding or releasing a reference.
static constexpr FieldType kValueMask
Mask that will zero out the tag bits and leave the count bits unchanged.
ReleaseWeakRefAction releaseWeakRef() const
ReleaseStrongRefAction releaseStrongRef() const
void addStrongRef() const noexcept
std::size_t useCount() const noexcept
static constexpr FieldType kPartialDestroyStartedMask
Flag that is set when the partialDestroy function has started running (or is about to start running).
static constexpr FieldType kOne
static constexpr size_t kStrongCountNumBits