Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Gabriel Charette | 52fa3ae | 2019-04-15 21:44:37 | [diff] [blame] | 5 | #include "base/task/thread_pool/tracked_ref.h" |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 6 | |
| 7 | #include <memory> |
David Bienvenu | 85cf749b | 2020-10-30 15:10:59 | [diff] [blame] | 8 | #include <utility> |
| 9 | #include <vector> |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 10 | |
Avi Drissman | 63e1f99 | 2023-01-13 18:54:43 | [diff] [blame^] | 11 | #include "base/functional/bind.h" |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 12 | #include "base/synchronization/atomic_flag.h" |
| 13 | #include "base/test/test_timeouts.h" |
| 14 | #include "base/threading/thread.h" |
| 15 | #include "base/time/time.h" |
| 16 | #include "testing/gtest/include/gtest/gtest.h" |
| 17 | |
| 18 | namespace base { |
| 19 | namespace internal { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | class ObjectWithTrackedRefs { |
| 24 | public: |
| 25 | ObjectWithTrackedRefs() : tracked_ref_factory_(this) {} |
David Bienvenu | 85cf749b | 2020-10-30 15:10:59 | [diff] [blame] | 26 | ObjectWithTrackedRefs(const ObjectWithTrackedRefs&) = delete; |
| 27 | ObjectWithTrackedRefs& operator=(const ObjectWithTrackedRefs&) = delete; |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 28 | ~ObjectWithTrackedRefs() { under_destruction_.Set(); } |
| 29 | |
| 30 | TrackedRef<ObjectWithTrackedRefs> GetTrackedRef() { |
| 31 | return tracked_ref_factory_.GetTrackedRef(); |
| 32 | } |
| 33 | |
| 34 | bool under_destruction() const { return under_destruction_.IsSet(); } |
| 35 | |
| 36 | private: |
| 37 | // True once ~ObjectWithTrackedRefs() has been initiated. |
| 38 | AtomicFlag under_destruction_; |
| 39 | |
| 40 | TrackedRefFactory<ObjectWithTrackedRefs> tracked_ref_factory_; |
Gabriel Charette | c88dae7b | 2018-04-19 19:02:16 | [diff] [blame] | 41 | }; |
| 42 | |
| 43 | } // namespace |
| 44 | |
| 45 | // Test that an object with a TrackedRefFactory can be destroyed by a single |
| 46 | // owner but that its destruction will be blocked on the TrackedRefs being |
| 47 | // released. |
| 48 | TEST(TrackedRefTest, TrackedRefObjectDeletion) { |
| 49 | Thread thread("TrackedRefTestThread"); |
| 50 | thread.Start(); |
| 51 | |
| 52 | std::unique_ptr<ObjectWithTrackedRefs> obj = |
| 53 | std::make_unique<ObjectWithTrackedRefs>(); |
| 54 | |
| 55 | TimeTicks begin = TimeTicks::Now(); |
| 56 | |
| 57 | thread.task_runner()->PostDelayedTask( |
| 58 | FROM_HERE, |
| 59 | BindOnce( |
| 60 | [](TrackedRef<ObjectWithTrackedRefs> obj) { |
| 61 | // By the time this kicks in, the object should already be under |
| 62 | // destruction, but blocked on this TrackedRef being released. This |
| 63 | // is technically racy (main thread has to run |obj.reset()| and |
| 64 | // this thread has to observe the side-effects before this delayed |
| 65 | // task fires). If this ever flakes this expectation could be turned |
| 66 | // into a while(!obj->under_destruction()); but until that's proven |
| 67 | // flaky in practice, this expectation is more readable and |
| 68 | // diagnosable then a hang. |
| 69 | EXPECT_TRUE(obj->under_destruction()); |
| 70 | }, |
| 71 | obj->GetTrackedRef()), |
| 72 | TestTimeouts::tiny_timeout()); |
| 73 | |
| 74 | // This should kick off destruction but block until the above task resolves |
| 75 | // and releases the TrackedRef. |
| 76 | obj.reset(); |
| 77 | EXPECT_GE(TimeTicks::Now() - begin, TestTimeouts::tiny_timeout()); |
| 78 | } |
| 79 | |
| 80 | TEST(TrackedRefTest, ManyThreadsRacing) { |
| 81 | constexpr int kNumThreads = 16; |
| 82 | std::vector<std::unique_ptr<Thread>> threads; |
| 83 | for (int i = 0; i < kNumThreads; ++i) { |
| 84 | threads.push_back(std::make_unique<Thread>("TrackedRefTestThread")); |
| 85 | threads.back()->StartAndWaitForTesting(); |
| 86 | } |
| 87 | |
| 88 | std::unique_ptr<ObjectWithTrackedRefs> obj = |
| 89 | std::make_unique<ObjectWithTrackedRefs>(); |
| 90 | |
| 91 | // Send a TrackedRef to each thread. |
| 92 | for (auto& thread : threads) { |
| 93 | thread->task_runner()->PostTask( |
| 94 | FROM_HERE, BindOnce( |
| 95 | [](TrackedRef<ObjectWithTrackedRefs> obj) { |
| 96 | // Confirm it's still safe to |
| 97 | // dereference |obj| (and, bonus, that |
| 98 | // playing with TrackedRefs some more |
| 99 | // isn't problematic). |
| 100 | EXPECT_TRUE(obj->GetTrackedRef()); |
| 101 | }, |
| 102 | obj->GetTrackedRef())); |
| 103 | } |
| 104 | |
| 105 | // Initiate destruction racily with the above tasks' execution (they will |
| 106 | // crash if TrackedRefs aren't WAI). |
| 107 | obj.reset(); |
| 108 | } |
| 109 | |
| 110 | // Test that instantiating and deleting a TrackedRefFactory without ever taking |
| 111 | // a TrackedRef on it is fine. |
| 112 | TEST(TrackedRefTest, NoTrackedRefs) { |
| 113 | ObjectWithTrackedRefs obj; |
| 114 | } |
| 115 | |
| 116 | namespace { |
| 117 | void ConsumesTrackedRef(TrackedRef<ObjectWithTrackedRefs> obj) {} |
| 118 | } // namespace |
| 119 | |
| 120 | // Test that destroying a TrackedRefFactory which had TrackedRefs in the past |
| 121 | // that are already gone is WAI. |
| 122 | TEST(TrackedRefTest, NoPendingTrackedRefs) { |
| 123 | ObjectWithTrackedRefs obj; |
| 124 | ConsumesTrackedRef(obj.GetTrackedRef()); |
| 125 | } |
| 126 | |
| 127 | TEST(TrackedRefTest, CopyAndMoveSemantics) { |
| 128 | struct Foo { |
| 129 | Foo() : factory(this) {} |
| 130 | TrackedRefFactory<Foo> factory; |
| 131 | }; |
| 132 | Foo foo; |
| 133 | |
| 134 | EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); |
| 135 | |
| 136 | { |
| 137 | TrackedRef<Foo> plain = foo.factory.GetTrackedRef(); |
| 138 | EXPECT_EQ(2, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); |
| 139 | |
| 140 | TrackedRef<Foo> copy_constructed(plain); |
| 141 | EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); |
| 142 | |
| 143 | TrackedRef<Foo> moved_constructed(std::move(copy_constructed)); |
| 144 | EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); |
| 145 | } |
| 146 | |
| 147 | EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); |
| 148 | } |
| 149 | |
| 150 | } // namespace internal |
| 151 | } // namespace base |