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, destructor)
449 using enum TrackedState;
451 TIBase::ResetStatesGuard
const rsg{
true};
459 using enum TrackedState;
465 case PartiallyDeleted:
466 EXPECT_FALSE(partialDeleteRan.
exchange(
true) || destructorRan.
load());
470 EXPECT_FALSE(destructorRan.
exchange(
true));
475 case PartiallyDeletedStarted:
491 EXPECT_TRUE(destructorRan.
load() && !partialDeleteRan.
load());
494TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant)
501 using enum TrackedState;
502 TIBase::ResetStatesGuard
const rsg{
true};
507 int const s = destructionState.
load(std::memory_order_relaxed);
508 return {(s & 1) != 0, (s & 2) != 0};
510 auto setDestructorRan = [&]() ->
void {
511 destructionState.
fetch_or(1, std::memory_order_acq_rel);
513 auto setPartialDeleteRan = [&]() ->
void {
514 destructionState.
fetch_or(2, std::memory_order_acq_rel);
517 using enum TrackedState;
518 auto [destructorRan, partialDeleteRan] = getDestructorState();
524 case PartiallyDeleted:
525 EXPECT_FALSE(partialDeleteRan || destructorRan);
526 setPartialDeleteRan();
530 EXPECT_FALSE(destructorRan);
536 case PartiallyDeletedStarted:
546 auto numToCreate = toCreateDist(eng);
548 for (
auto i = 0uz; i < numToCreate; ++i)
550 if (isStrongDist(eng))
561 constexpr auto kLoopIters = 2uz * 1024;
562 constexpr auto kNumThreads = 16uz;
564 Barrier loopStartSyncPoint{kNumThreads};
565 Barrier postCreateToCloneSyncPoint{kNumThreads};
566 Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
571 for (
auto i = 0uz; i < kNumThreads; ++i)
580 for (
auto i = 0uz; i < kLoopIters; ++i)
595 auto [destructorRan, partialDeleteRan] = getDestructorState();
596 EXPECT_TRUE(i == 0 || destructorRan);
597 destructionState.
store(0, std::memory_order_release);
600 toClone.
resize(kNumThreads);
602 strong->tracingCallback = tracingCallback;
609 auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
610 toClone[threadId].reset();
620 for (
auto i = 0uz; i < kNumThreads; ++i)
624 for (
auto i = 0uz; i < kNumThreads; ++i)
630TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union)
642 using enum TrackedState;
644 TIBase::ResetStatesGuard
const rsg{
true};
649 int const s = destructionState.
load(std::memory_order_relaxed);
650 return {(s & 1) != 0, (s & 2) != 0};
652 auto setDestructorRan = [&]() ->
void {
653 destructionState.
fetch_or(1, std::memory_order_acq_rel);
655 auto setPartialDeleteRan = [&]() ->
void {
656 destructionState.
fetch_or(2, std::memory_order_acq_rel);
659 using enum TrackedState;
660 auto [destructorRan, partialDeleteRan] = getDestructorState();
666 case PartiallyDeleted:
667 EXPECT_FALSE(partialDeleteRan || destructorRan);
668 setPartialDeleteRan();
672 EXPECT_FALSE(destructorRan);
678 case PartiallyDeletedStarted:
683 auto createVecOfPointers =
684 [&](
auto const& toClone,
688 auto numToCreate = toCreateDist(eng);
690 for (
auto i = 0uz; i < numToCreate; ++i)
694 constexpr auto kLoopIters = 2uz * 1024;
695 constexpr auto kFlipPointersLoopIters = 256uz;
696 constexpr auto kNumThreads = 16uz;
698 Barrier loopStartSyncPoint{kNumThreads};
699 Barrier postCreateToCloneSyncPoint{kNumThreads};
700 Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
701 Barrier postFlipPointersLoopSyncPoint{kNumThreads};
706 for (
auto i = 0uz; i < kNumThreads; ++i)
716 for (
auto i = 0uz; i < kLoopIters; ++i)
730 auto [destructorRan, partialDeleteRan] = getDestructorState();
731 EXPECT_TRUE(i == 0 || destructorRan);
732 destructionState.
store(0, std::memory_order_release);
735 toClone.
resize(kNumThreads);
737 strong->tracingCallback = tracingCallback;
744 auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
745 toClone[threadId].reset();
751 for (
auto f = 0uz; f < kFlipPointersLoopIters; ++f)
755 if (isStrongDist(engines[threadId]))
774 for (
auto i = 0uz; i < kNumThreads; ++i)
778 for (
auto i = 0uz; i < kNumThreads; ++i)
784TEST(IntrusiveSharedTest, multithreaded_locking_weak)
791 using enum TrackedState;
793 TIBase::ResetStatesGuard
const rsg{
true};
798 int const s = destructionState.
load(std::memory_order_relaxed);
799 return {(s & 1) != 0, (s & 2) != 0};
801 auto setDestructorRan = [&]() ->
void {
802 destructionState.
fetch_or(1, std::memory_order_acq_rel);
804 auto setPartialDeleteRan = [&]() ->
void {
805 destructionState.
fetch_or(2, std::memory_order_acq_rel);
808 using enum TrackedState;
809 auto [destructorRan, partialDeleteRan] = getDestructorState();
815 case PartiallyDeleted:
816 EXPECT_FALSE(partialDeleteRan || destructorRan);
817 setPartialDeleteRan();
821 EXPECT_FALSE(destructorRan);
827 case PartiallyDeletedStarted:
833 constexpr auto kLoopIters = 2uz * 1024;
834 constexpr auto kLockWeakLoopIters = 256uz;
835 constexpr auto kNumThreads = 16uz;
837 Barrier loopStartSyncPoint{kNumThreads};
838 Barrier postCreateToLockSyncPoint{kNumThreads};
839 Barrier postLockWeakLoopSyncPoint{kNumThreads};
845 for (
auto i = 0uz; i < kLoopIters; ++i)
859 auto [destructorRan, partialDeleteRan] = getDestructorState();
860 EXPECT_TRUE(i == 0 || destructorRan);
861 destructionState.
store(0, std::memory_order_release);
864 toLock.
resize(kNumThreads);
866 strong->tracingCallback = tracingCallback;
876 for (
auto wi = 0uz; wi < kLockWeakLoopIters; ++wi)
879 auto strong = weak.
lock();
886 toLock[threadId].reset();
891 for (
auto i = 0uz; i < kNumThreads; ++i)
895 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.
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 isStrong() const
Return true is this represents a strong 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