1#include <xrpl/basics/IntrusivePointer.h>
2#include <xrpl/basics/IntrusivePointer.ipp>
3#include <xrpl/basics/IntrusiveRefCounts.h>
5#include <gtest/gtest.h>
74 cv.wait(lock, [&] {
return generation != currentGeneration; });
83 PartiallyDeletedStarted,
89class TIBase :
public IntrusiveRefCounts
99 if (
id >= state.
size())
102 return state[id].load(std::memory_order_acquire);
106 resetStates(
bool resetCallback)
108 for (std::size_t i = 0; i < kMaxStates; ++i)
109 state[i].store(TrackedState::Uninitialized, std::memory_order_release);
110 nextId.store(0, std::memory_order_release);
112 TIBase::tracingCallback = [](TrackedState, std::optional<TrackedState>) {};
115 struct ResetStatesGuard
117 bool resetCallback{
false};
119 ResetStatesGuard(
bool resetCallback) : resetCallback{resetCallback}
121 TIBase::resetStates(resetCallback);
126 TIBase::resetStates(resetCallback);
130 TIBase() : id{checkoutID()}
132 state[id].store(TrackedState::Alive, std::memory_order_relaxed);
137 using enum TrackedState;
139 tracingCallback(state[
id].load(std::memory_order_relaxed), DeletedStarted);
144 state[id].store(DeletedStarted, std::memory_order_relaxed);
146 tracingCallback(DeletedStarted, Deleted);
148 state[id].store(TrackedState::Deleted, std::memory_order_relaxed);
150 tracingCallback(TrackedState::Deleted, std::nullopt);
154 partialDestructor()
const
156 using enum TrackedState;
158 tracingCallback(state[
id].load(std::memory_order_relaxed), PartiallyDeletedStarted);
160 state[id].store(PartiallyDeletedStarted, std::memory_order_relaxed);
162 tracingCallback(PartiallyDeletedStarted, PartiallyDeleted);
164 state[id].store(PartiallyDeleted, std::memory_order_relaxed);
166 tracingCallback(PartiallyDeleted, std::nullopt);
169 static std::function<void(TrackedState, std::optional<TrackedState>)> tracingCallback;
171 std::size_t
const id;
177 auto const id = nextId.fetch_add(1, std::memory_order_acq_rel);
178 if (
id >= state.size())
179 throw std::out_of_range(
"TIBase state capacity exceeded");
185std::array<std::atomic<TrackedState>, TIBase::kMaxStates> TIBase::state;
186std::atomic<std::size_t> TIBase::nextId{0};
188std::function<void(TrackedState, std::optional<TrackedState>)> TIBase::tracingCallback =
189 [](TrackedState, std::optional<TrackedState>) {};
193TEST(IntrusiveSharedTest, basics)
196 TIBase::ResetStatesGuard
const rsg{
true};
199 EXPECT_EQ(b.useCount(), 1);
201 EXPECT_EQ(b.useCount(), 1);
202 auto s = b.releaseStrongRef();
204 EXPECT_EQ(b.useCount(), 0);
205 TIBase
const* pb = &b;
208 auto w = b.releaseWeakRef();
215 TIBase::ResetStatesGuard
const rsg{
true};
217 using enum TrackedState;
220 EXPECT_EQ(TIBase::getState(
id), Alive);
221 EXPECT_EQ(b->useCount(), 1);
222 for (
auto i = 0uz; i < 10; ++i)
225 EXPECT_EQ(TIBase::getState(
id), Alive);
227 EXPECT_EQ(TIBase::getState(
id), Alive);
229 EXPECT_EQ(TIBase::getState(
id), Deleted);
233 EXPECT_EQ(TIBase::getState(
id), Alive);
234 EXPECT_EQ(b->useCount(), 1);
235 for (
auto i = 0uz; i < 10; ++i)
238 EXPECT_EQ(b->useCount(), 1);
240 EXPECT_EQ(TIBase::getState(
id), Alive);
242 EXPECT_EQ(TIBase::getState(
id), Alive);
244 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
245 while (!weak.
empty())
249 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
252 EXPECT_EQ(TIBase::getState(
id), Deleted);
255 TIBase::ResetStatesGuard
const rsg{
true};
257 using enum TrackedState;
260 EXPECT_EQ(TIBase::getState(
id), Alive);
262 EXPECT_EQ(TIBase::getState(
id), Alive);
264 EXPECT_TRUE(s && s->useCount() == 2);
266 EXPECT_TRUE(TIBase::getState(
id) == Alive);
267 EXPECT_TRUE(s && s->useCount() == 1);
269 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
276 EXPECT_EQ(TIBase::getState(
id), Deleted);
279 TIBase::ResetStatesGuard
const rsg{
true};
281 using enum TrackedState;
284 EXPECT_TRUE(b.isStrong() && b.useCount() == 1);
285 auto id = b.get()->id;
286 EXPECT_EQ(TIBase::getState(
id), Alive);
288 EXPECT_TRUE(TIBase::getState(
id) == Alive);
289 EXPECT_TRUE(w.isStrong() && b.useCount() == 2);
291 EXPECT_TRUE(w.isWeak() && b.useCount() == 1);
293 EXPECT_TRUE(s.isWeak() && b.useCount() == 1);
295 EXPECT_TRUE(s.isStrong() && b.useCount() == 2);
297 EXPECT_EQ(TIBase::getState(
id), Alive);
298 EXPECT_EQ(s.useCount(), 1);
299 EXPECT_FALSE(w.expired());
301 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
302 EXPECT_TRUE(w.expired());
306 EXPECT_TRUE(w.isWeak());
308 EXPECT_EQ(TIBase::getState(
id), Deleted);
313 TIBase::ResetStatesGuard
const rsg{
true};
318 auto id1 = strong1->id;
319 auto id2 = strong2->id;
328 EXPECT_EQ(union1.
get(), strong1.get());
329 EXPECT_EQ(union2.
get(), strong2.get());
335 EXPECT_EQ(union1.
get(), union2.
get());
336 EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
337 EXPECT_EQ(TIBase::getState(id2), TrackedState::Alive);
341 EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
342 int const initialRefCount = strong1->useCount();
343#pragma clang diagnostic push
344#pragma clang diagnostic ignored "-Wself-assign-overloaded"
346#pragma clang diagnostic pop
348 EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
349 EXPECT_EQ(strong1->useCount(), initialRefCount);
353 EXPECT_EQ(union1.
get(),
nullptr);
359 EXPECT_EQ(union1.
get(),
nullptr);
360 EXPECT_EQ(TIBase::getState(id2), TrackedState::Deleted);
364TEST(IntrusiveSharedTest, partial_delete)
373 using enum TrackedState;
375 TIBase::ResetStatesGuard
const rsg{
true};
384 using enum TrackedState;
396 EXPECT_EQ(cur, PartiallyDeleted);
399 case PartiallyDeletedStarted: {
401 using namespace std::chrono_literals;
409 case PartiallyDeleted:
410 EXPECT_FALSE(partialDeleteRan.
exchange(
true) || destructorRan.
load());
414 EXPECT_FALSE(destructorRan.
exchange(
true));
436 EXPECT_TRUE(destructorRan.
load() && partialDeleteRan.
load());
439TEST(IntrusiveSharedTest, convert_last_strong_to_weak)
441 using enum TrackedState;
443 TIBase::ResetStatesGuard
const rsg{
true};
446 auto const id = p.
get()->id;
447 EXPECT_EQ(TIBase::getState(
id), Alive);
452 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
455 EXPECT_EQ(TIBase::getState(
id), Deleted);
458TEST(IntrusiveSharedTest, multithreaded_convert_to_weak_partial_delete)
460 using enum TrackedState;
462 TIBase::ResetStatesGuard
const rsg{
true};
468 auto const id = converted.
get()->id;
482 EXPECT_EQ(cur, PartiallyDeleted);
485 case PartiallyDeletedStarted:
488 otherReleasedSyncPoint.
wait();
491 case PartiallyDeleted:
492 EXPECT_FALSE(partialDeleteRan.
exchange(
true) || destructorRan.
load());
496 EXPECT_FALSE(destructorRan.
exchange(
true));
506 partialDeleteStartedSyncPoint.
wait();
518 EXPECT_TRUE(partialDeleteRan.
load());
519 EXPECT_FALSE(destructorRan.
load());
520 EXPECT_TRUE(converted.
isWeak());
521 EXPECT_EQ(TIBase::getState(
id), PartiallyDeleted);
524 EXPECT_TRUE(destructorRan.
load());
525 EXPECT_EQ(TIBase::getState(
id), Deleted);
528TEST(IntrusiveSharedTest, destructor)
538 using enum TrackedState;
540 TIBase::ResetStatesGuard
const rsg{
true};
548 using enum TrackedState;
554 case PartiallyDeleted:
555 EXPECT_FALSE(partialDeleteRan.
exchange(
true) || destructorRan.
load());
559 EXPECT_FALSE(destructorRan.
exchange(
true));
564 case PartiallyDeletedStarted:
580 EXPECT_TRUE(destructorRan.
load() && !partialDeleteRan.
load());
583TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant)
590 using enum TrackedState;
591 TIBase::ResetStatesGuard
const rsg{
true};
596 int const s = destructionState.
load(std::memory_order_relaxed);
597 return {(s & 1) != 0, (s & 2) != 0};
599 auto setDestructorRan = [&]() ->
void {
600 destructionState.
fetch_or(1, std::memory_order_acq_rel);
602 auto setPartialDeleteRan = [&]() ->
void {
603 destructionState.
fetch_or(2, std::memory_order_acq_rel);
606 using enum TrackedState;
607 auto [destructorRan, partialDeleteRan] = getDestructorState();
613 case PartiallyDeleted:
614 EXPECT_FALSE(partialDeleteRan || destructorRan);
615 setPartialDeleteRan();
619 EXPECT_FALSE(destructorRan);
625 case PartiallyDeletedStarted:
635 auto numToCreate = toCreateDist(eng);
637 for (
auto i = 0uz; i < numToCreate; ++i)
639 if (isStrongDist(eng))
650 constexpr auto kLoopIters = 2uz * 1024;
651 constexpr auto kNumThreads = 16uz;
653 Barrier loopStartSyncPoint{kNumThreads};
654 Barrier postCreateToCloneSyncPoint{kNumThreads};
655 Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
660 for (
auto i = 0uz; i < kNumThreads; ++i)
669 for (
auto i = 0uz; i < kLoopIters; ++i)
684 auto [destructorRan, partialDeleteRan] = getDestructorState();
685 EXPECT_TRUE(i == 0 || destructorRan);
686 destructionState.
store(0, std::memory_order_release);
689 toClone.
resize(kNumThreads);
691 strong->tracingCallback = tracingCallback;
698 auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
699 toClone[threadId].reset();
709 for (
auto i = 0uz; i < kNumThreads; ++i)
713 for (
auto i = 0uz; i < kNumThreads; ++i)
719TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union)
731 using enum TrackedState;
733 TIBase::ResetStatesGuard
const rsg{
true};
738 int const s = destructionState.
load(std::memory_order_relaxed);
739 return {(s & 1) != 0, (s & 2) != 0};
741 auto setDestructorRan = [&]() ->
void {
742 destructionState.
fetch_or(1, std::memory_order_acq_rel);
744 auto setPartialDeleteRan = [&]() ->
void {
745 destructionState.
fetch_or(2, std::memory_order_acq_rel);
748 using enum TrackedState;
749 auto [destructorRan, partialDeleteRan] = getDestructorState();
755 case PartiallyDeleted:
756 EXPECT_FALSE(partialDeleteRan || destructorRan);
757 setPartialDeleteRan();
761 EXPECT_FALSE(destructorRan);
767 case PartiallyDeletedStarted:
772 auto createVecOfPointers =
773 [&](
auto const& toClone,
777 auto numToCreate = toCreateDist(eng);
779 for (
auto i = 0uz; i < numToCreate; ++i)
783 constexpr auto kLoopIters = 2uz * 1024;
784 constexpr auto kFlipPointersLoopIters = 256uz;
785 constexpr auto kNumThreads = 16uz;
787 Barrier loopStartSyncPoint{kNumThreads};
788 Barrier postCreateToCloneSyncPoint{kNumThreads};
789 Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
790 Barrier postFlipPointersLoopSyncPoint{kNumThreads};
795 for (
auto i = 0uz; i < kNumThreads; ++i)
805 for (
auto i = 0uz; i < kLoopIters; ++i)
819 auto [destructorRan, partialDeleteRan] = getDestructorState();
820 EXPECT_TRUE(i == 0 || destructorRan);
821 destructionState.
store(0, std::memory_order_release);
824 toClone.
resize(kNumThreads);
826 strong->tracingCallback = tracingCallback;
833 auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
834 toClone[threadId].reset();
840 for (
auto f = 0uz; f < kFlipPointersLoopIters; ++f)
844 if (isStrongDist(engines[threadId]))
863 for (
auto i = 0uz; i < kNumThreads; ++i)
867 for (
auto i = 0uz; i < kNumThreads; ++i)
873TEST(IntrusiveSharedTest, multithreaded_locking_weak)
880 using enum TrackedState;
882 TIBase::ResetStatesGuard
const rsg{
true};
887 int const s = destructionState.
load(std::memory_order_relaxed);
888 return {(s & 1) != 0, (s & 2) != 0};
890 auto setDestructorRan = [&]() ->
void {
891 destructionState.
fetch_or(1, std::memory_order_acq_rel);
893 auto setPartialDeleteRan = [&]() ->
void {
894 destructionState.
fetch_or(2, std::memory_order_acq_rel);
897 using enum TrackedState;
898 auto [destructorRan, partialDeleteRan] = getDestructorState();
904 case PartiallyDeleted:
905 EXPECT_FALSE(partialDeleteRan || destructorRan);
906 setPartialDeleteRan();
910 EXPECT_FALSE(destructorRan);
916 case PartiallyDeletedStarted:
922 constexpr auto kLoopIters = 2uz * 1024;
923 constexpr auto kLockWeakLoopIters = 256uz;
924 constexpr auto kNumThreads = 16uz;
926 Barrier loopStartSyncPoint{kNumThreads};
927 Barrier postCreateToLockSyncPoint{kNumThreads};
928 Barrier postLockWeakLoopSyncPoint{kNumThreads};
934 for (
auto i = 0uz; i < kLoopIters; ++i)
948 auto [destructorRan, partialDeleteRan] = getDestructorState();
949 EXPECT_TRUE(i == 0 || destructorRan);
950 destructionState.
store(0, std::memory_order_release);
953 toLock.
resize(kNumThreads);
955 strong->tracingCallback = tracingCallback;
965 for (
auto wi = 0uz; wi < kLockWeakLoopIters; ++wi)
968 auto strong = weak.
lock();
975 toLock[threadId].reset();
980 for (
auto i = 0uz; i < kNumThreads; ++i)
984 for (
auto i = 0uz; i < kNumThreads; ++i)
T arrive_and_wait(T... args)
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 expired() const
Return true if there is a non-zero strong count.
void reset()
Set the pointer to null, decrement the appropriate ref count, and run the appropriate release action.
T * get() const
If this is a strong pointer, return the raw pointer.
bool convertToWeak()
If this is a strong pointer, attempt to convert it to a weak pointer.
bool isStrong() const
Return true is this represents a strong pointer.
std::size_t useCount() const
If this is a strong pointer, return the strong count.
bool isWeak() const
Return true is this represents a weak pointer.
A weak intrusive pointer class for the SharedIntrusive pointer class.
SharedIntrusive< T > lock() const
Get a strong pointer from the weak pointer, if possible.
bool expired() const
Return true if the strong count is zero.
void reset()
Set the pointer to null and decrement the weak count.
T emplace_back(T... args)
TEST(IntrusiveSharedTest, basics)
void partialDestructorFinished(T **o)
SharedIntrusive< TT > makeSharedIntrusive(Args &&... args)
Create a shared intrusive pointer.
std::condition_variable cv
std::size_t const initial