blob: b793c07b6b2ed61b43a6170700adef3dd63130a8 [file] [log] [blame]
// 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 <memory>
#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<ObjectWithTrackedRefs> 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<ObjectWithTrackedRefs> 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<ObjectWithTrackedRefs> obj =
std::make_unique<ObjectWithTrackedRefs>();
TimeTicks begin = TimeTicks::Now();
thread.task_runner()->PostDelayedTask(
FROM_HERE,
BindOnce(
[](TrackedRef<ObjectWithTrackedRefs> 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<std::unique_ptr<Thread>> threads;
for (int i = 0; i < kNumThreads; ++i) {
threads.push_back(std::make_unique<Thread>("TrackedRefTestThread"));
threads.back()->StartAndWaitForTesting();
}
std::unique_ptr<ObjectWithTrackedRefs> obj =
std::make_unique<ObjectWithTrackedRefs>();
// Send a TrackedRef to each thread.
for (auto& thread : threads) {
thread->task_runner()->PostTask(
FROM_HERE, BindOnce(
[](TrackedRef<ObjectWithTrackedRefs> 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<ObjectWithTrackedRefs> 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<Foo> factory;
};
Foo foo;
EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
{
TrackedRef<Foo> plain = foo.factory.GetTrackedRef();
EXPECT_EQ(2, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
TrackedRef<Foo> copy_constructed(plain);
EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
TrackedRef<Foo> 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