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