// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/task_scheduler/tracked_ref.h" #include #include "base/bind.h" #include "base/macros.h" #include "base/synchronization/atomic_flag.h" #include "base/test/test_timeouts.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace internal { namespace { class ObjectWithTrackedRefs { public: ObjectWithTrackedRefs() : tracked_ref_factory_(this) {} ~ObjectWithTrackedRefs() { under_destruction_.Set(); } TrackedRef GetTrackedRef() { return tracked_ref_factory_.GetTrackedRef(); } bool under_destruction() const { return under_destruction_.IsSet(); } private: // True once ~ObjectWithTrackedRefs() has been initiated. AtomicFlag under_destruction_; TrackedRefFactory tracked_ref_factory_; DISALLOW_COPY_AND_ASSIGN(ObjectWithTrackedRefs); }; } // namespace // Test that an object with a TrackedRefFactory can be destroyed by a single // owner but that its destruction will be blocked on the TrackedRefs being // released. TEST(TrackedRefTest, TrackedRefObjectDeletion) { Thread thread("TrackedRefTestThread"); thread.Start(); std::unique_ptr obj = std::make_unique(); TimeTicks begin = TimeTicks::Now(); thread.task_runner()->PostDelayedTask( FROM_HERE, BindOnce( [](TrackedRef obj) { // By the time this kicks in, the object should already be under // destruction, but blocked on this TrackedRef being released. This // is technically racy (main thread has to run |obj.reset()| and // this thread has to observe the side-effects before this delayed // task fires). If this ever flakes this expectation could be turned // into a while(!obj->under_destruction()); but until that's proven // flaky in practice, this expectation is more readable and // diagnosable then a hang. EXPECT_TRUE(obj->under_destruction()); }, obj->GetTrackedRef()), TestTimeouts::tiny_timeout()); // This should kick off destruction but block until the above task resolves // and releases the TrackedRef. obj.reset(); EXPECT_GE(TimeTicks::Now() - begin, TestTimeouts::tiny_timeout()); } TEST(TrackedRefTest, ManyThreadsRacing) { constexpr int kNumThreads = 16; std::vector> threads; for (int i = 0; i < kNumThreads; ++i) { threads.push_back(std::make_unique("TrackedRefTestThread")); threads.back()->StartAndWaitForTesting(); } std::unique_ptr obj = std::make_unique(); // Send a TrackedRef to each thread. for (auto& thread : threads) { thread->task_runner()->PostTask( FROM_HERE, BindOnce( [](TrackedRef obj) { // Confirm it's still safe to // dereference |obj| (and, bonus, that // playing with TrackedRefs some more // isn't problematic). EXPECT_TRUE(obj->GetTrackedRef()); }, obj->GetTrackedRef())); } // Initiate destruction racily with the above tasks' execution (they will // crash if TrackedRefs aren't WAI). obj.reset(); } // Test that instantiating and deleting a TrackedRefFactory without ever taking // a TrackedRef on it is fine. TEST(TrackedRefTest, NoTrackedRefs) { ObjectWithTrackedRefs obj; } namespace { void ConsumesTrackedRef(TrackedRef obj) {} } // namespace // Test that destroying a TrackedRefFactory which had TrackedRefs in the past // that are already gone is WAI. TEST(TrackedRefTest, NoPendingTrackedRefs) { ObjectWithTrackedRefs obj; ConsumesTrackedRef(obj.GetTrackedRef()); } TEST(TrackedRefTest, CopyAndMoveSemantics) { struct Foo { Foo() : factory(this) {} TrackedRefFactory factory; }; Foo foo; EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); { TrackedRef plain = foo.factory.GetTrackedRef(); EXPECT_EQ(2, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); TrackedRef copy_constructed(plain); EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); TrackedRef moved_constructed(std::move(copy_constructed)); EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); } EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug()); } } // namespace internal } // namespace base