You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
4.8 KiB
151 lines
4.8 KiB
4 months ago
|
// 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
|