rippled
Loading...
Searching...
No Matches
IntrusiveRefCounts.h
1#pragma once
2
3#include <xrpl/beast/utility/instrumentation.h>
4
5#include <atomic>
6#include <cstdint>
7
8namespace xrpl {
9
22
32
40{
41 virtual ~IntrusiveRefCounts() noexcept;
42
43 // This must be `noexcept` or the make_SharedIntrusive function could leak
44 // memory.
45 void
46 addStrongRef() const noexcept;
47
48 void
49 addWeakRef() const noexcept;
50
52 releaseStrongRef() const;
53
54 // Same as:
55 // {
56 // addWeakRef();
57 // return releaseStrongRef;
58 // }
59 // done as one atomic operation
62
64 releaseWeakRef() const;
65
66 // Returns true is able to checkout a strong ref. False otherwise
67 bool
68 checkoutStrongRefFromWeak() const noexcept;
69
70 bool
71 expired() const noexcept;
72
74 use_count() const noexcept;
75
76 // This function MUST be called after a partial destructor finishes running.
77 // Calling this function may cause other threads to delete the object
78 // pointed to by `o`, so `o` should never be used after calling this
79 // function. The parameter will be set to a `nullptr` after calling this
80 // function to emphasize that it should not be used.
81 // Note: This is intentionally NOT called at the end of `partialDestructor`.
82 // The reason for this is if new classes are written to support this smart
83 // pointer class, they need to write their own `partialDestructor` function
84 // and ensure `partialDestructorFinished` is called at the end. Putting this
85 // call inside the smart pointer class itself is expected to be less error
86 // prone.
87 // Note: The "two-star" programming is intentional. It emphasizes that `o`
88 // may be deleted and the unergonomic API is meant to signal the special
89 // nature of this function call to callers.
90 // Note: This is a template to support incompletely defined classes.
91 template <class T>
92 friend void
94
95private:
96 // TODO: We may need to use a uint64_t for both counts. This will reduce the
97 // memory savings. We need to audit the code to make sure 16 bit counts are
98 // enough for strong pointers and 14 bit counts are enough for weak
99 // pointers. Use type aliases to make it easy to switch types.
101 static constexpr size_t StrongCountNumBits = sizeof(CountType) * 8;
102 static constexpr size_t WeakCountNumBits = StrongCountNumBits - 2;
104 static constexpr size_t FieldTypeBits = sizeof(FieldType) * 8;
105 static constexpr FieldType one = 1;
106
141
147 static constexpr FieldType strongDelta = 1;
148
154 static constexpr FieldType weakDelta = (one << StrongCountNumBits);
155
163
170
175
179 static constexpr FieldType valueMask = ~tagMask;
180
183 static constexpr FieldType strongMask = ((one << StrongCountNumBits) - 1) & valueMask;
184
187 static constexpr FieldType weakMask =
189
192 {
206 RefCountPair(FieldType v) noexcept;
207 RefCountPair(CountType s, CountType w) noexcept;
208
211 combinedValue() const noexcept;
212
213 static constexpr CountType maxStrongValue =
214 static_cast<CountType>((one << StrongCountNumBits) - 1);
215 static constexpr CountType maxWeakValue =
216 static_cast<CountType>((one << WeakCountNumBits) - 1);
222 static constexpr CountType checkWeakMaxValue = maxWeakValue - 32;
223 };
224};
225
226inline void
231
232inline void
237
240{
241 // Subtract `strongDelta` from refCounts. If this releases the last strong
242 // ref, set the `partialDestroyStarted` bit. It is important that the ref
243 // count and the `partialDestroyStartedBit` are changed atomically (hence
244 // the loop and `compare_exchange` op). If this didn't need to be done
245 // atomically, the loop could be replaced with a `fetch_sub` and a
246 // conditional `fetch_or`. This loop will almost always run once.
247
248 using enum ReleaseStrongRefAction;
249 auto prevIntVal = refCounts.load(std::memory_order_acquire);
250 while (1)
251 {
252 RefCountPair const prevVal{prevIntVal};
253 XRPL_ASSERT(
254 (prevVal.strong >= strongDelta),
255 "xrpl::IntrusiveRefCounts::releaseStrongRef : previous ref "
256 "higher than new");
257 auto nextIntVal = prevIntVal - strongDelta;
259 if (prevVal.strong == 1)
260 {
261 if (prevVal.weak == 0)
262 {
263 action = destroy;
264 }
265 else
266 {
267 nextIntVal |= partialDestroyStartedMask;
268 action = partialDestroy;
269 }
270 }
271
272 if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
273 {
274 // Can't be in partial destroy because only decrementing the strong
275 // count to zero can start a partial destroy, and that can't happen
276 // twice.
277 XRPL_ASSERT(
278 (action == noop) || !(prevIntVal & partialDestroyStartedMask),
279 "xrpl::IntrusiveRefCounts::releaseStrongRef : not in partial "
280 "destroy");
281 return action;
282 }
283 }
284}
285
288{
289 using enum ReleaseStrongRefAction;
290
291 static_assert(weakDelta > strongDelta);
292 auto constexpr delta = weakDelta - strongDelta;
293 auto prevIntVal = refCounts.load(std::memory_order_acquire);
294 // This loop will almost always run once. The loop is needed to atomically
295 // change the counts and flags (the count could be atomically changed, but
296 // the flags depend on the current value of the counts).
297 //
298 // Note: If this becomes a perf bottleneck, the `partialDestroyStartedMask`
299 // may be able to be set non-atomically. But it is easier to reason about
300 // the code if the flag is set atomically.
301 while (1)
302 {
303 RefCountPair const prevVal{prevIntVal};
304 // Converted the last strong pointer to a weak pointer.
305 //
306 // Can't be in partial destroy because only decrementing the
307 // strong count to zero can start a partial destroy, and that
308 // can't happen twice.
309 XRPL_ASSERT(
310 (!prevVal.partialDestroyStartedBit),
311 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not in "
312 "partial destroy");
313
314 auto nextIntVal = prevIntVal + delta;
316 if (prevVal.strong == 1)
317 {
318 if (prevVal.weak == 0)
319 {
320 action = noop;
321 }
322 else
323 {
324 nextIntVal |= partialDestroyStartedMask;
325 action = partialDestroy;
326 }
327 }
328 if (refCounts.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
329 {
330 XRPL_ASSERT(
331 (!(prevIntVal & partialDestroyStartedMask)),
332 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not "
333 "started partial destroy");
334 return action;
335 }
336 }
337}
338
341{
342 auto prevIntVal = refCounts.fetch_sub(weakDelta, std::memory_order_acq_rel);
343 RefCountPair prev = prevIntVal;
344 if (prev.weak == 1 && prev.strong == 0)
345 {
346 if (!prev.partialDestroyStartedBit)
347 {
348 // This case should only be hit if the partialDestroyStartedBit is
349 // set non-atomically (and even then very rarely). The code is kept
350 // in case we need to set the flag non-atomically for perf reasons.
351 refCounts.wait(prevIntVal, std::memory_order_acquire);
352 prevIntVal = refCounts.load(std::memory_order_acquire);
353 prev = RefCountPair{prevIntVal};
354 }
355 if (!prev.partialDestroyFinishedBit)
356 {
357 // partial destroy MUST finish before running a full destroy (when
358 // using weak pointers)
360 }
362 }
364}
365
366inline bool
368{
369 auto curValue = RefCountPair{1, 1}.combinedValue();
370 auto desiredValue = RefCountPair{2, 1}.combinedValue();
371
372 while (!refCounts.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel))
373 {
374 RefCountPair const prev{curValue};
375 if (!prev.strong)
376 return false;
377
378 desiredValue = curValue + strongDelta;
379 }
380 return true;
381}
382
383inline bool
385{
387 return val.strong == 0;
388}
389
390inline std::size_t
392{
394 return val.strong;
395}
396
398{
399#ifndef NDEBUG
401 XRPL_ASSERT(
402 (!(v & valueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
403 auto t = v & tagMask;
404 XRPL_ASSERT((!t || t == tagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
405#endif
406}
407
408//------------------------------------------------------------------------------
409
411 : strong{static_cast<CountType>(v & strongMask)}
412 , weak{static_cast<CountType>((v & weakMask) >> StrongCountNumBits)}
415{
416 XRPL_ASSERT(
418 "xrpl::IntrusiveRefCounts::RefCountPair(FieldType) : inputs inside "
419 "range");
420}
421
425 : strong{s}, weak{w}
426{
427 XRPL_ASSERT(
429 "xrpl::IntrusiveRefCounts::RefCountPair(CountType, CountType) : "
430 "inputs inside range");
431}
432
435{
436 XRPL_ASSERT(
438 "xrpl::IntrusiveRefCounts::RefCountPair::combinedValue : inputs "
439 "inside range");
440 return (static_cast<IntrusiveRefCounts::FieldType>(weak)
444}
445
446template <class T>
447inline void
449{
450 T& self = **o;
452 self.refCounts.fetch_or(IntrusiveRefCounts::partialDestroyFinishedMask);
453 XRPL_ASSERT(
455 "xrpl::partialDestructorFinished : not a weak ref");
456 if (!p.weak)
457 {
458 // There was a weak count before the partial destructor ran (or we would
459 // have run the full destructor) and now there isn't a weak count. Some
460 // thread is waiting to run the destructor.
461 self.refCounts.notify_one();
462 }
463 // Set the pointer to null to emphasize that the object shouldn't be used
464 // after calling this function as it may be destroyed in another thread.
465 *o = nullptr;
466}
467//------------------------------------------------------------------------------
468
469} // namespace xrpl
T is_same_v
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 checkWeakMaxValue
static constexpr CountType maxStrongValue
FieldType partialDestroyStartedBit
The partialDestroyStartedBit is set to on when the partial destroy function is started.
static constexpr CountType checkStrongMaxValue
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.
Implement the strong count, weak count, and bit flags for an intrusive pointer.
bool checkoutStrongRefFromWeak() const noexcept
static constexpr FieldType strongDelta
Amount to change the strong count when adding or releasing a reference.
static constexpr FieldType weakMask
Mask that will zero out everything except the weak count.
static constexpr FieldType tagMask
Mask that will zero out all the count bits and leave the tag bits unchanged.
ReleaseStrongRefAction addWeakReleaseStrongRef() const
void addWeakRef() const noexcept
static constexpr FieldType one
static constexpr size_t StrongCountNumBits
bool expired() const noexcept
friend void partialDestructorFinished(T **o)
virtual ~IntrusiveRefCounts() noexcept
std::size_t use_count() const noexcept
static constexpr FieldType partialDestroyFinishedMask
Flag that is set when the partialDestroy function has finished running.
static constexpr FieldType weakDelta
Amount to change the weak count when adding or releasing a reference.
static constexpr FieldType valueMask
Mask that will zero out the tag bits and leave the count bits unchanged.
ReleaseWeakRefAction releaseWeakRef() const
static constexpr FieldType strongMask
Mask that will zero out everything except the strong count.
ReleaseStrongRefAction releaseStrongRef() const
void addStrongRef() const noexcept
static constexpr size_t FieldTypeBits
static constexpr size_t WeakCountNumBits
static constexpr FieldType partialDestroyStartedMask
Flag that is set when the partialDestroy function has started running (or is about to start running).
std::atomic< FieldType > refCounts
refCounts consists of four fields that are treated atomically: