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 // MUST be called after `partialDestructor` returns. Another thread may
81 // then delete the object, so `*o` is nulled and must not be used after
82 // (unless the caller holds its own weak ref, e.g.
83 // SharedWeakUnion::convertToWeak). Called by the smart pointers, not
84 // `partialDestructor`, so custom partial destructors can't forget it.
85 // The two-star API signals that `*o` may be deleted. Templated to
86 // support incomplete types.
87 template <class T>
88 friend void
90
91private:
92 // TODO: We may need to use a uint64_t for both counts. This will reduce the
93 // memory savings. We need to audit the code to make sure 16 bit counts are
94 // enough for strong pointers and 14 bit counts are enough for weak
95 // pointers. Use type aliases to make it easy to switch types.
97 static constexpr size_t kStrongCountNumBits = sizeof(CountType) * 8;
98 static constexpr size_t kWeakCountNumBits = kStrongCountNumBits - 2;
100 static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
101 static constexpr FieldType kOne = 1;
102
135
137
144 static constexpr FieldType kStrongDelta = 1;
145
153
162
170
176
181 static constexpr FieldType kValueMask = ~kTagMask;
182
186 static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
187
191 static constexpr FieldType kWeakMask =
193
198 {
214 RefCountPair(FieldType v) noexcept;
215 RefCountPair(CountType s, CountType w) noexcept;
216
220 [[nodiscard]] FieldType
221 combinedValue() const noexcept;
222
223 static constexpr CountType kMaxStrongValue =
224 static_cast<CountType>((kOne << kStrongCountNumBits) - 1);
225 static constexpr CountType kMaxWeakValue =
226 static_cast<CountType>((kOne << kWeakCountNumBits) - 1);
234 };
235};
236
237inline void
239{
240 refCounts_.fetch_add(kStrongDelta, std::memory_order_acq_rel);
241}
242
243inline void
245{
246 refCounts_.fetch_add(kWeakDelta, std::memory_order_acq_rel);
247}
248
251{
252 // Subtract `strongDelta` from refCounts. If this releases the last strong
253 // ref, set the `partialDestroyStarted` bit. It is important that the ref
254 // count and the `partialDestroyStartedBit` are changed atomically (hence
255 // the loop and `compare_exchange` op). If this didn't need to be done
256 // atomically, the loop could be replaced with a `fetch_sub` and a
257 // conditional `fetch_or`. This loop will almost always run once.
258
259 using enum ReleaseStrongRefAction;
260 auto prevIntVal = refCounts_.load(std::memory_order_acquire);
261 while (true)
262 {
263 RefCountPair const prevVal{prevIntVal};
264 XRPL_ASSERT(
265 (prevVal.strong >= kStrongDelta),
266 "xrpl::IntrusiveRefCounts::releaseStrongRef : previous ref "
267 "higher than new");
268 auto nextIntVal = prevIntVal - kStrongDelta;
270 if (prevVal.strong == 1)
271 {
272 if (prevVal.weak == 0)
273 {
274 action = Destroy;
275 }
276 else
277 {
278 nextIntVal |= kPartialDestroyStartedMask;
279 action = PartialDestroy;
280 }
281 }
282
283 if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
284 {
285 // Can't be in partial destroy because only decrementing the strong
286 // count to zero can start a partial destroy, and that can't happen
287 // twice.
288 XRPL_ASSERT(
289 (action == NoOp) || !(prevIntVal & kPartialDestroyStartedMask),
290 "xrpl::IntrusiveRefCounts::releaseStrongRef : not in partial "
291 "destroy");
292 return action;
293 }
294 }
295}
296
299{
300 using enum ReleaseStrongRefAction;
301
302 static_assert(kWeakDelta > kStrongDelta);
303 static constexpr auto kDelta = kWeakDelta - kStrongDelta;
304 auto prevIntVal = refCounts_.load(std::memory_order_acquire);
305 // This loop will almost always run once. The loop is needed to atomically
306 // change the counts and flags (the count could be atomically changed, but
307 // the flags depend on the current value of the counts).
308 //
309 // Note: If this becomes a perf bottleneck, the `partialDestroyStartedMask`
310 // may be able to be set non-atomically. But it is easier to reason about
311 // the code if the flag is set atomically.
312 while (true)
313 {
314 RefCountPair const prevVal{prevIntVal};
315 // Converted the last strong pointer to a weak pointer.
316 //
317 // Can't be in partial destroy because only decrementing the
318 // strong count to zero can start a partial destroy, and that
319 // can't happen twice.
320 XRPL_ASSERT(
321 (!prevVal.partialDestroyStartedBit),
322 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not in "
323 "partial destroy");
324
325 auto nextIntVal = prevIntVal + kDelta;
327 if (prevVal.strong == 1)
328 {
329 // The weak ref added here keeps the weak count non-zero, so
330 // releasing the last strong ref always starts a partial destroy,
331 // regardless of the previous weak count.
332 nextIntVal |= kPartialDestroyStartedMask;
333 action = PartialDestroy;
334 }
335 if (refCounts_.compare_exchange_weak(prevIntVal, nextIntVal, std::memory_order_acq_rel))
336 {
337 XRPL_ASSERT(
338 (!(prevIntVal & kPartialDestroyStartedMask)),
339 "xrpl::IntrusiveRefCounts::addWeakReleaseStrongRef : not "
340 "started partial destroy");
341 return action;
342 }
343 }
344}
345
348{
349 auto const prevIntVal = refCounts_.fetch_sub(kWeakDelta, std::memory_order_acq_rel);
350 RefCountPair const prev = prevIntVal;
351 if (prev.weak == 1 && prev.strong == 0)
352 {
353 // `wait` blocks while the value equals its argument, so it must be
354 // given the value as it is after the decrement above.
355 auto curIntVal = prevIntVal - kWeakDelta;
356 if (prev.partialDestroyStartedBit == 0u)
357 {
358 // This case should only be hit if the partialDestroyStartedBit is
359 // set non-atomically (and even then very rarely). The code is kept
360 // in case we need to set the flag non-atomically for perf reasons.
361 refCounts_.wait(curIntVal, std::memory_order_acquire);
362 curIntVal = refCounts_.load(std::memory_order_acquire);
363 }
364 if (RefCountPair{curIntVal}.partialDestroyFinishedBit == 0u)
365 {
366 // partial destroy MUST finish before running a full destroy (when
367 // using weak pointers)
368 refCounts_.wait(curIntVal, std::memory_order_acquire);
369 }
371 }
373}
374
375inline bool
377{
378 auto curValue = RefCountPair{1, 1}.combinedValue();
379 auto desiredValue = RefCountPair{2, 1}.combinedValue();
380
381 while (!refCounts_.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel))
382 {
383 RefCountPair const prev{curValue};
384 if (prev.strong == 0u)
385 return false;
386
387 desiredValue = curValue + kStrongDelta;
388 }
389 return true;
390}
391
392inline bool
394{
395 RefCountPair const val = refCounts_.load(std::memory_order_acquire);
396 return val.strong == 0;
397}
398
399inline std::size_t
401{
402 RefCountPair const val = refCounts_.load(std::memory_order_acquire);
403 return val.strong;
404}
405
407{
408#ifndef NDEBUG
409 auto v = refCounts_.load(std::memory_order_acquire);
410 XRPL_ASSERT(
411 (!(v & kValueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
412 auto t = v & kTagMask;
413 XRPL_ASSERT((!t || t == kTagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
414#endif
415}
416
417//------------------------------------------------------------------------------
418
420 : strong{static_cast<CountType>(v & kStrongMask)}
421 , weak{static_cast<CountType>((v & kWeakMask) >> kStrongCountNumBits)}
424{
425 XRPL_ASSERT(
427 "xrpl::IntrusiveRefCounts::RefCountPair(FieldType) : inputs inside "
428 "range");
429}
430
434 : strong{s}, weak{w}
435{
436 XRPL_ASSERT(
438 "xrpl::IntrusiveRefCounts::RefCountPair(CountType, CountType) : "
439 "inputs inside range");
440}
441
444{
445 XRPL_ASSERT(
447 "xrpl::IntrusiveRefCounts::RefCountPair::combinedValue : inputs "
448 "inside range");
449 return (static_cast<IntrusiveRefCounts::FieldType>(weak)
453}
454
455template <class T>
456inline void
458{
459 T& self = **o;
461 self.refCounts_.fetch_or(IntrusiveRefCounts::kPartialDestroyFinishedMask);
462 XRPL_ASSERT(
464 "xrpl::partialDestructorFinished : not a weak ref");
465 if (!p.weak)
466 {
467 // There was a weak count before the partial destructor ran (or we would
468 // have run the full destructor) and now there isn't a weak count. Some
469 // thread is waiting to run the destructor.
470 self.refCounts_.notify_one();
471 }
472 // Set the pointer to null to emphasize that the object shouldn't be used
473 // after calling this function as it may be destroyed in another thread.
474 *o = nullptr;
475}
476//------------------------------------------------------------------------------
477
478} // 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