1#include <test/unit_test/SuiteJournal.h>
3#include <xrpl/basics/IntrusivePointer.ipp>
4#include <xrpl/basics/IntrusiveRefCounts.h>
5#include <xrpl/beast/unit_test.h>
6#include <xrpl/beast/utility/Journal.h>
76 partiallyDeletedStarted,
82class TIBase :
public IntrusiveRefCounts
91 assert(
id < state.size());
92 return state[id].load(std::memory_order_acquire);
95 resetStates(
bool resetCallback)
97 for (
int i = 0; i < maxStates; ++i)
100 TrackedState::uninitialized, std::memory_order_release);
102 nextId.
store(0, std::memory_order_release);
104 TIBase::tracingCallback_ = [](TrackedState,
108 struct ResetStatesGuard
110 bool resetCallback_{
false};
112 ResetStatesGuard(
bool resetCallback) : resetCallback_{resetCallback}
114 TIBase::resetStates(resetCallback_);
118 TIBase::resetStates(resetCallback_);
122 TIBase() : id_{checkoutID()}
124 assert(state.size() > id_);
125 state[id_].store(TrackedState::alive, std::memory_order_relaxed);
129 using enum TrackedState;
131 assert(state.size() > id_);
133 state[id_].load(std::memory_order_relaxed), deletedStarted);
135 assert(state.size() > id_);
139 state[id_].store(deletedStarted, std::memory_order_relaxed);
141 tracingCallback_(deletedStarted, deleted);
143 assert(state.size() > id_);
144 state[id_].store(TrackedState::deleted, std::memory_order_relaxed);
146 tracingCallback_(TrackedState::deleted, std::nullopt);
152 using enum TrackedState;
154 assert(state.size() > id_);
156 state[id_].load(std::memory_order_relaxed),
157 partiallyDeletedStarted);
159 assert(state.size() > id_);
160 state[id_].store(partiallyDeletedStarted, std::memory_order_relaxed);
162 tracingCallback_(partiallyDeletedStarted, partiallyDeleted);
164 assert(state.size() > id_);
165 state[id_].store(partiallyDeleted, std::memory_order_relaxed);
167 tracingCallback_(partiallyDeleted, std::nullopt);
179 return nextId.
fetch_add(1, std::memory_order_acq_rel);
200 TIBase::ResetStatesGuard rsg{
true};
203 BEAST_EXPECT(b.use_count() == 1);
205 BEAST_EXPECT(b.use_count() == 1);
206 auto s = b.releaseStrongRef();
208 BEAST_EXPECT(b.use_count() == 0);
212 auto w = b.releaseWeakRef();
219 TIBase::ResetStatesGuard rsg{
true};
221 using enum TrackedState;
222 auto b = make_SharedIntrusive<TIBase>();
224 BEAST_EXPECT(TIBase::getState(
id) == alive);
225 BEAST_EXPECT(b->use_count() == 1);
226 for (
int i = 0; i < 10; ++i)
231 BEAST_EXPECT(TIBase::getState(
id) == alive);
233 BEAST_EXPECT(TIBase::getState(
id) == alive);
235 BEAST_EXPECT(TIBase::getState(
id) == deleted);
237 b = make_SharedIntrusive<TIBase>();
239 BEAST_EXPECT(TIBase::getState(
id) == alive);
240 BEAST_EXPECT(b->use_count() == 1);
241 for (
int i = 0; i < 10; ++i)
244 BEAST_EXPECT(b->use_count() == 1);
246 BEAST_EXPECT(TIBase::getState(
id) == alive);
248 BEAST_EXPECT(TIBase::getState(
id) == alive);
250 BEAST_EXPECT(TIBase::getState(
id) == partiallyDeleted);
251 while (!weak.
empty())
255 BEAST_EXPECT(TIBase::getState(
id) == partiallyDeleted);
257 BEAST_EXPECT(TIBase::getState(
id) == deleted);
260 TIBase::ResetStatesGuard rsg{
true};
262 using enum TrackedState;
263 auto b = make_SharedIntrusive<TIBase>();
265 BEAST_EXPECT(TIBase::getState(
id) == alive);
267 BEAST_EXPECT(TIBase::getState(
id) == alive);
269 BEAST_EXPECT(s && s->use_count() == 2);
271 BEAST_EXPECT(TIBase::getState(
id) == alive);
272 BEAST_EXPECT(s && s->use_count() == 1);
274 BEAST_EXPECT(TIBase::getState(
id) == partiallyDeleted);
275 BEAST_EXPECT(w.expired());
281 BEAST_EXPECT(TIBase::getState(
id) == deleted);
284 TIBase::ResetStatesGuard rsg{
true};
286 using enum TrackedState;
288 swu b = make_SharedIntrusive<TIBase>();
289 BEAST_EXPECT(b.isStrong() && b.use_count() == 1);
290 auto id = b.get()->id_;
291 BEAST_EXPECT(TIBase::getState(
id) == alive);
293 BEAST_EXPECT(TIBase::getState(
id) == alive);
294 BEAST_EXPECT(w.isStrong() && b.use_count() == 2);
296 BEAST_EXPECT(w.isWeak() && b.use_count() == 1);
298 BEAST_EXPECT(s.isWeak() && b.use_count() == 1);
300 BEAST_EXPECT(s.isStrong() && b.use_count() == 2);
302 BEAST_EXPECT(TIBase::getState(
id) == alive);
303 BEAST_EXPECT(s.use_count() == 1);
304 BEAST_EXPECT(!w.expired());
306 BEAST_EXPECT(TIBase::getState(
id) == partiallyDeleted);
307 BEAST_EXPECT(w.expired());
311 BEAST_EXPECT(w.isWeak());
313 BEAST_EXPECT(TIBase::getState(
id) == deleted);
318 TIBase::ResetStatesGuard rsg{
true};
320 auto strong1 = make_SharedIntrusive<TIBase>();
321 auto strong2 = make_SharedIntrusive<TIBase>();
323 auto id1 = strong1->id_;
324 auto id2 = strong2->id_;
326 BEAST_EXPECT(id1 != id2);
333 BEAST_EXPECT(union1.
get() == strong1.get());
334 BEAST_EXPECT(union2.
get() == strong2.get());
340 BEAST_EXPECT(union1.
get() == union2.
get());
341 BEAST_EXPECT(TIBase::getState(id1) == TrackedState::alive);
342 BEAST_EXPECT(TIBase::getState(id2) == TrackedState::alive);
346 BEAST_EXPECT(TIBase::getState(id1) == TrackedState::alive);
347 int initialRefCount = strong1->use_count();
348#pragma clang diagnostic push
349#pragma clang diagnostic ignored "-Wself-assign-overloaded"
351#pragma clang diagnostic pop
353 BEAST_EXPECT(TIBase::getState(id1) == TrackedState::alive);
354 BEAST_EXPECT(strong1->use_count() == initialRefCount);
358 BEAST_EXPECT(union1.
get() ==
nullptr);
364 BEAST_EXPECT(union1.
get() ==
nullptr);
365 BEAST_EXPECT(TIBase::getState(id2) == TrackedState::deleted);
381 using enum TrackedState;
383 TIBase::ResetStatesGuard rsg{
true};
385 auto strong = make_SharedIntrusive<TIBase>();
387 bool destructorRan =
false;
388 bool partialDeleteRan =
false;
390 strong->tracingCallback_ = [&](TrackedState cur,
392 using enum TrackedState;
393 if (next == deletedStarted)
400 BEAST_EXPECT(cur == partiallyDeleted);
402 if (next == partiallyDeletedStarted)
404 partialDeleteStartedSyncPoint.arrive_and_wait();
405 using namespace std::chrono_literals;
411 if (next == partiallyDeleted)
413 BEAST_EXPECT(!partialDeleteRan && !destructorRan);
414 partialDeleteRan =
true;
418 BEAST_EXPECT(!destructorRan);
419 destructorRan =
true;
423 partialDeleteStartedSyncPoint.arrive_and_wait();
433 BEAST_EXPECT(destructorRan && partialDeleteRan);
449 using enum TrackedState;
451 TIBase::ResetStatesGuard rsg{
true};
453 auto strong = make_SharedIntrusive<TIBase>();
455 bool destructorRan =
false;
456 bool partialDeleteRan =
false;
458 strong->tracingCallback_ = [&](TrackedState cur,
460 using enum TrackedState;
461 if (next == partiallyDeleted)
463 BEAST_EXPECT(!partialDeleteRan && !destructorRan);
464 partialDeleteRan =
true;
468 BEAST_EXPECT(!destructorRan);
469 destructorRan =
true;
474 weakResetSyncPoint.arrive_and_wait();
477 weakResetSyncPoint.arrive_and_wait();
483 BEAST_EXPECT(destructorRan && !partialDeleteRan);
489 testcase(
"Multithreaded Clear Mixed Variant");
496 using enum TrackedState;
497 TIBase::ResetStatesGuard rsg{
true};
502 int s = destructionState.load(std::memory_order_relaxed);
503 return {(s & 1) != 0, (s & 2) != 0};
505 auto setDestructorRan = [&]() ->
void {
506 destructionState.fetch_or(1, std::memory_order_acq_rel);
508 auto setPartialDeleteRan = [&]() ->
void {
509 destructionState.fetch_or(2, std::memory_order_acq_rel);
511 auto tracingCallback = [&](TrackedState cur,
513 using enum TrackedState;
514 auto [destructorRan, partialDeleteRan] = getDestructorState();
515 if (next == partiallyDeleted)
517 BEAST_EXPECT(!partialDeleteRan && !destructorRan);
518 setPartialDeleteRan();
522 BEAST_EXPECT(!destructorRan);
526 auto createVecOfPointers = [&](
auto const& toClone,
535 auto numToCreate = toCreateDist(eng);
536 result.reserve(numToCreate);
537 for (
int i = 0; i < numToCreate; ++i)
539 if (isStrongDist(eng))
550 constexpr int loopIters = 2 * 1024;
551 constexpr int numThreads = 16;
553 Barrier loopStartSyncPoint{numThreads};
554 Barrier postCreateToCloneSyncPoint{numThreads};
555 Barrier postCreateVecOfPointersSyncPoint{numThreads};
560 for (
int i = 0; i < numThreads; ++i)
568 auto cloneAndDestroy = [&](
int threadId) {
569 for (
int i = 0; i < loopIters; ++i)
572 loopStartSyncPoint.arrive_and_wait();
584 auto [destructorRan, partialDeleteRan] =
585 getDestructorState();
586 BEAST_EXPECT(!i || destructorRan);
587 destructionState.store(0, std::memory_order_release);
590 toClone.
resize(numThreads);
591 auto strong = make_SharedIntrusive<TIBase>();
592 strong->tracingCallback_ = tracingCallback;
597 postCreateToCloneSyncPoint.arrive_and_wait();
600 createVecOfPointers(toClone[threadId], engines[threadId]);
601 toClone[threadId].reset();
604 postCreateVecOfPointersSyncPoint.arrive_and_wait();
610 for (
int i = 0; i < numThreads; ++i)
614 for (
int i = 0; i < numThreads; ++i)
623 testcase(
"Multithreaded Clear Mixed Union");
635 using enum TrackedState;
637 TIBase::ResetStatesGuard rsg{
true};
642 int s = destructionState.load(std::memory_order_relaxed);
643 return {(s & 1) != 0, (s & 2) != 0};
645 auto setDestructorRan = [&]() ->
void {
646 destructionState.fetch_or(1, std::memory_order_acq_rel);
648 auto setPartialDeleteRan = [&]() ->
void {
649 destructionState.fetch_or(2, std::memory_order_acq_rel);
651 auto tracingCallback = [&](TrackedState cur,
653 using enum TrackedState;
654 auto [destructorRan, partialDeleteRan] = getDestructorState();
655 if (next == partiallyDeleted)
657 BEAST_EXPECT(!partialDeleteRan && !destructorRan);
658 setPartialDeleteRan();
662 BEAST_EXPECT(!destructorRan);
666 auto createVecOfPointers = [&](
auto const& toClone,
671 auto numToCreate = toCreateDist(eng);
673 for (
int i = 0; i < numToCreate; ++i)
677 constexpr int loopIters = 2 * 1024;
678 constexpr int flipPointersLoopIters = 256;
679 constexpr int numThreads = 16;
681 Barrier loopStartSyncPoint{numThreads};
682 Barrier postCreateToCloneSyncPoint{numThreads};
683 Barrier postCreateVecOfPointersSyncPoint{numThreads};
684 Barrier postFlipPointersLoopSyncPoint{numThreads};
689 for (
int i = 0; i < numThreads; ++i)
698 auto cloneAndDestroy = [&](
int threadId) {
699 for (
int i = 0; i < loopIters; ++i)
702 loopStartSyncPoint.arrive_and_wait();
713 auto [destructorRan, partialDeleteRan] =
714 getDestructorState();
715 BEAST_EXPECT(!i || destructorRan);
716 destructionState.store(0, std::memory_order_release);
719 toClone.
resize(numThreads);
720 auto strong = make_SharedIntrusive<TIBase>();
721 strong->tracingCallback_ = tracingCallback;
726 postCreateToCloneSyncPoint.arrive_and_wait();
729 createVecOfPointers(toClone[threadId], engines[threadId]);
730 toClone[threadId].reset();
733 postCreateVecOfPointersSyncPoint.arrive_and_wait();
736 for (
int f = 0; f < flipPointersLoopIters; ++f)
740 if (isStrongDist(engines[threadId]))
752 postFlipPointersLoopSyncPoint.arrive_and_wait();
758 for (
int i = 0; i < numThreads; ++i)
762 for (
int i = 0; i < numThreads; ++i)
771 testcase(
"Multithreaded Locking Weak");
778 using enum TrackedState;
780 TIBase::ResetStatesGuard rsg{
true};
785 int s = destructionState.load(std::memory_order_relaxed);
786 return {(s & 1) != 0, (s & 2) != 0};
788 auto setDestructorRan = [&]() ->
void {
789 destructionState.fetch_or(1, std::memory_order_acq_rel);
791 auto setPartialDeleteRan = [&]() ->
void {
792 destructionState.fetch_or(2, std::memory_order_acq_rel);
794 auto tracingCallback = [&](TrackedState cur,
796 using enum TrackedState;
797 auto [destructorRan, partialDeleteRan] = getDestructorState();
798 if (next == partiallyDeleted)
800 BEAST_EXPECT(!partialDeleteRan && !destructorRan);
801 setPartialDeleteRan();
805 BEAST_EXPECT(!destructorRan);
810 constexpr int loopIters = 2 * 1024;
811 constexpr int lockWeakLoopIters = 256;
812 constexpr int numThreads = 16;
814 Barrier loopStartSyncPoint{numThreads};
815 Barrier postCreateToLockSyncPoint{numThreads};
816 Barrier postLockWeakLoopSyncPoint{numThreads};
821 auto lockAndDestroy = [&](
int threadId) {
822 for (
int i = 0; i < loopIters; ++i)
836 auto [destructorRan, partialDeleteRan] =
837 getDestructorState();
838 BEAST_EXPECT(!i || destructorRan);
839 destructionState.store(0, std::memory_order_release);
842 toLock.
resize(numThreads);
843 auto strong = make_SharedIntrusive<TIBase>();
844 strong->tracingCallback_ = tracingCallback;
849 postCreateToLockSyncPoint.arrive_and_wait();
854 for (
int wi = 0; wi < lockWeakLoopIters; ++wi)
856 BEAST_EXPECT(!weak.expired());
857 auto strong = weak.lock();
858 BEAST_EXPECT(strong);
862 postLockWeakLoopSyncPoint.arrive_and_wait();
864 toLock[threadId].reset();
868 for (
int i = 0; i < numThreads; ++i)
872 for (
int i = 0; i < numThreads; ++i)
890BEAST_DEFINE_TESTSUITE(IntrusiveShared, ripple_basics,
ripple);
testcase_t testcase
Memberspace for declaring test cases.
A shared intrusive pointer class that supports weak pointers.
A combination of a strong and a weak intrusive pointer stored in the space of a single pointer.
bool isStrong() const
Return true is this represents a strong pointer.
T * get() const
If this is a strong pointer, return the raw pointer.
void reset()
Set the pointer to null, decrement the appropriate ref count, and run the appropriate release action.
A weak intrusive pointer class for the SharedIntrusive pointer class.
void testMultithreadedClearMixedVariant()
void testMultithreadedClearMixedUnion()
void testMultithreadedLockingWeak()
void run() override
Runs the suite.
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
void partialDestructorFinished(T **o)
Experimentally, we discovered that using std::barrier performs extremely poorly (~1 hour vs ~1 minute...
std::condition_variable cv