
* Ran clang-tidy with -fix. * Ran git cl format. * Reverted any fixes which caused local problems (mostly because of making constructors explicit and breaking unittests that depended on them being implicit). Other minor hand-edits applied during rebasing. This does not make base/ fully clang-tidy clean, but it should greatly reduce the number of existing errors. Bug: none Change-Id: I93a046f2838e6494812fa038b776b22b8dea93a7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6119194 Reviewed-by: Lei Zhang <thestig@chromium.org> Owners-Override: Lei Zhang <thestig@chromium.org> Commit-Queue: Peter Kasting <pkasting@chromium.org> Auto-Submit: Peter Kasting <pkasting@chromium.org> Cr-Commit-Position: refs/heads/main@{#1403975}
323 lines
10 KiB
C++
323 lines
10 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/lazy_instance.h"
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/at_exit.h"
|
|
#include "base/atomic_sequence_num.h"
|
|
#include "base/barrier_closure.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/aligned_memory.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/system/sys_info.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/threading/simple_thread.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace {
|
|
|
|
base::AtomicSequenceNumber constructed_seq_;
|
|
base::AtomicSequenceNumber destructed_seq_;
|
|
|
|
class ConstructAndDestructLogger {
|
|
public:
|
|
ConstructAndDestructLogger() { constructed_seq_.GetNext(); }
|
|
ConstructAndDestructLogger(const ConstructAndDestructLogger&) = delete;
|
|
ConstructAndDestructLogger& operator=(const ConstructAndDestructLogger&) =
|
|
delete;
|
|
~ConstructAndDestructLogger() { destructed_seq_.GetNext(); }
|
|
};
|
|
|
|
class SlowConstructor {
|
|
public:
|
|
SlowConstructor() {
|
|
// Sleep for 1 second to try to cause a race.
|
|
base::PlatformThread::Sleep(base::Seconds(1));
|
|
++constructed;
|
|
some_int_ = 12;
|
|
}
|
|
SlowConstructor(const SlowConstructor&) = delete;
|
|
SlowConstructor& operator=(const SlowConstructor&) = delete;
|
|
int some_int() const { return some_int_; }
|
|
|
|
static int constructed;
|
|
|
|
private:
|
|
int some_int_ = 0;
|
|
};
|
|
|
|
// static
|
|
int SlowConstructor::constructed = 0;
|
|
|
|
class SlowDelegate : public base::DelegateSimpleThread::Delegate {
|
|
public:
|
|
explicit SlowDelegate(
|
|
base::LazyInstance<SlowConstructor>::DestructorAtExit* lazy)
|
|
: lazy_(lazy) {}
|
|
SlowDelegate(const SlowDelegate&) = delete;
|
|
SlowDelegate& operator=(const SlowDelegate&) = delete;
|
|
|
|
void Run() override {
|
|
EXPECT_EQ(12, lazy_->Get().some_int());
|
|
EXPECT_EQ(12, lazy_->Pointer()->some_int());
|
|
}
|
|
|
|
private:
|
|
raw_ptr<base::LazyInstance<SlowConstructor>::DestructorAtExit> lazy_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
base::LazyInstance<ConstructAndDestructLogger>::DestructorAtExit lazy_logger =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
TEST(LazyInstanceTest, Basic) {
|
|
{
|
|
base::ShadowingAtExitManager shadow;
|
|
|
|
EXPECT_FALSE(lazy_logger.IsCreated());
|
|
EXPECT_EQ(0, constructed_seq_.GetNext());
|
|
EXPECT_EQ(0, destructed_seq_.GetNext());
|
|
|
|
lazy_logger.Get();
|
|
EXPECT_TRUE(lazy_logger.IsCreated());
|
|
EXPECT_EQ(2, constructed_seq_.GetNext());
|
|
EXPECT_EQ(1, destructed_seq_.GetNext());
|
|
|
|
lazy_logger.Pointer();
|
|
EXPECT_TRUE(lazy_logger.IsCreated());
|
|
EXPECT_EQ(3, constructed_seq_.GetNext());
|
|
EXPECT_EQ(2, destructed_seq_.GetNext());
|
|
}
|
|
EXPECT_FALSE(lazy_logger.IsCreated());
|
|
EXPECT_EQ(4, constructed_seq_.GetNext());
|
|
EXPECT_EQ(4, destructed_seq_.GetNext());
|
|
}
|
|
|
|
base::LazyInstance<SlowConstructor>::DestructorAtExit lazy_slow =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
TEST(LazyInstanceTest, ConstructorThreadSafety) {
|
|
{
|
|
base::ShadowingAtExitManager shadow;
|
|
|
|
SlowDelegate delegate(&lazy_slow);
|
|
EXPECT_EQ(0, SlowConstructor::constructed);
|
|
|
|
base::DelegateSimpleThreadPool pool("lazy_instance_cons", 5);
|
|
pool.AddWork(&delegate, 20);
|
|
EXPECT_EQ(0, SlowConstructor::constructed);
|
|
|
|
pool.Start();
|
|
pool.JoinAll();
|
|
EXPECT_EQ(1, SlowConstructor::constructed);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
// DeleteLogger is an object which sets a flag when it's destroyed.
|
|
// It accepts a bool* and sets the bool to true when the dtor runs.
|
|
class DeleteLogger {
|
|
public:
|
|
DeleteLogger() : deleted_(nullptr) {}
|
|
~DeleteLogger() { *deleted_ = true; }
|
|
|
|
void SetDeletedPtr(bool* deleted) { deleted_ = deleted; }
|
|
|
|
private:
|
|
raw_ptr<bool> deleted_;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
TEST(LazyInstanceTest, LeakyLazyInstance) {
|
|
// Check that using a plain LazyInstance causes the dtor to run
|
|
// when the AtExitManager finishes.
|
|
bool deleted1 = false;
|
|
{
|
|
base::ShadowingAtExitManager shadow;
|
|
static base::LazyInstance<DeleteLogger>::DestructorAtExit test =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
test.Get().SetDeletedPtr(&deleted1);
|
|
}
|
|
EXPECT_TRUE(deleted1);
|
|
|
|
// Check that using a *leaky* LazyInstance makes the dtor not run
|
|
// when the AtExitManager finishes.
|
|
bool deleted2 = false;
|
|
{
|
|
base::ShadowingAtExitManager shadow;
|
|
static base::LazyInstance<DeleteLogger>::Leaky test =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
test.Get().SetDeletedPtr(&deleted2);
|
|
}
|
|
EXPECT_FALSE(deleted2);
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <size_t alignment>
|
|
class AlignedData {
|
|
public:
|
|
AlignedData() = default;
|
|
~AlignedData() = default;
|
|
alignas(alignment) char data_[alignment];
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(LazyInstanceTest, Alignment) {
|
|
using base::LazyInstance;
|
|
|
|
// Create some static instances with increasing sizes and alignment
|
|
// requirements. By ordering this way, the linker will need to do some work to
|
|
// ensure proper alignment of the static data.
|
|
static LazyInstance<AlignedData<4>>::DestructorAtExit align4 =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
static LazyInstance<AlignedData<32>>::DestructorAtExit align32 =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
static LazyInstance<AlignedData<4096>>::DestructorAtExit align4096 =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
EXPECT_TRUE(base::IsAligned(align4.Pointer(), 4));
|
|
EXPECT_TRUE(base::IsAligned(align32.Pointer(), 32));
|
|
EXPECT_TRUE(base::IsAligned(align4096.Pointer(), 4096));
|
|
}
|
|
|
|
namespace {
|
|
|
|
// A class whose constructor busy-loops until it is told to complete
|
|
// construction.
|
|
class BlockingConstructor {
|
|
public:
|
|
BlockingConstructor() {
|
|
EXPECT_FALSE(WasConstructorCalled());
|
|
constructor_called_.store(true, std::memory_order_relaxed);
|
|
EXPECT_TRUE(WasConstructorCalled());
|
|
while (!complete_construction_.load(std::memory_order_relaxed)) {
|
|
base::PlatformThread::YieldCurrentThread();
|
|
}
|
|
done_construction_ = true;
|
|
}
|
|
BlockingConstructor(const BlockingConstructor&) = delete;
|
|
BlockingConstructor& operator=(const BlockingConstructor&) = delete;
|
|
~BlockingConstructor() {
|
|
// Restore static state for the next test.
|
|
constructor_called_.store(false, std::memory_order_relaxed);
|
|
complete_construction_.store(false, std::memory_order_relaxed);
|
|
}
|
|
|
|
// Returns true if BlockingConstructor() was entered.
|
|
static bool WasConstructorCalled() {
|
|
return constructor_called_.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
// Instructs BlockingConstructor() that it may now unblock its construction.
|
|
static void CompleteConstructionNow() {
|
|
complete_construction_.store(true, std::memory_order_relaxed);
|
|
}
|
|
|
|
bool done_construction() const { return done_construction_; }
|
|
|
|
private:
|
|
// Use Atomic32 instead of AtomicFlag for them to be trivially initialized.
|
|
static std::atomic<bool> constructor_called_;
|
|
static std::atomic<bool> complete_construction_;
|
|
|
|
bool done_construction_ = false;
|
|
};
|
|
|
|
// A SimpleThread running at |thread_type| which invokes |before_get| (optional)
|
|
// and then invokes Get() on the LazyInstance it's assigned.
|
|
class BlockingConstructorThread : public base::SimpleThread {
|
|
public:
|
|
BlockingConstructorThread(
|
|
base::ThreadType thread_type,
|
|
base::LazyInstance<BlockingConstructor>::DestructorAtExit* lazy,
|
|
base::OnceClosure before_get)
|
|
: SimpleThread("BlockingConstructorThread", Options(thread_type)),
|
|
lazy_(lazy),
|
|
before_get_(std::move(before_get)) {}
|
|
BlockingConstructorThread(const BlockingConstructorThread&) = delete;
|
|
BlockingConstructorThread& operator=(const BlockingConstructorThread&) =
|
|
delete;
|
|
|
|
void Run() override {
|
|
if (before_get_) {
|
|
std::move(before_get_).Run();
|
|
}
|
|
EXPECT_TRUE(lazy_->Get().done_construction());
|
|
}
|
|
|
|
private:
|
|
raw_ptr<base::LazyInstance<BlockingConstructor>::DestructorAtExit> lazy_;
|
|
base::OnceClosure before_get_;
|
|
};
|
|
|
|
// static
|
|
std::atomic<bool> BlockingConstructor::constructor_called_ = false;
|
|
// static
|
|
std::atomic<bool> BlockingConstructor::complete_construction_ = false;
|
|
|
|
base::LazyInstance<BlockingConstructor>::DestructorAtExit lazy_blocking =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
} // namespace
|
|
|
|
// Tests that if the thread assigned to construct the LazyInstance runs at
|
|
// background priority : the foreground threads will yield to it enough for it
|
|
// to eventually complete construction.
|
|
// This is a regression test for https://crbug.com/797129.
|
|
TEST(LazyInstanceTest, PriorityInversionAtInitializationResolves) {
|
|
base::TimeTicks test_begin = base::TimeTicks::Now();
|
|
|
|
// Construct BlockingConstructor from a background thread.
|
|
BlockingConstructorThread background_getter(
|
|
base::ThreadType::kBackground, &lazy_blocking, base::OnceClosure());
|
|
background_getter.Start();
|
|
|
|
while (!BlockingConstructor::WasConstructorCalled()) {
|
|
base::PlatformThread::Sleep(base::Milliseconds(1));
|
|
}
|
|
|
|
// Spin 4 foreground thread per core contending to get the already under
|
|
// construction LazyInstance. When they are all running and poking at it :
|
|
// allow the background thread to complete its work.
|
|
const int kNumForegroundThreads = 4 * base::SysInfo::NumberOfProcessors();
|
|
std::vector<std::unique_ptr<base::SimpleThread>> foreground_threads;
|
|
base::RepeatingClosure foreground_thread_ready_callback =
|
|
base::BarrierClosure(
|
|
kNumForegroundThreads,
|
|
base::BindOnce(&BlockingConstructor::CompleteConstructionNow));
|
|
for (int i = 0; i < kNumForegroundThreads; ++i) {
|
|
foreground_threads.push_back(std::make_unique<BlockingConstructorThread>(
|
|
base::ThreadType::kDefault, &lazy_blocking,
|
|
foreground_thread_ready_callback));
|
|
foreground_threads.back()->Start();
|
|
}
|
|
|
|
// This test will hang if the foreground threads become stuck in
|
|
// LazyInstance::Get() per the background thread never being scheduled to
|
|
// complete construction.
|
|
for (auto& foreground_thread : foreground_threads) {
|
|
foreground_thread->Join();
|
|
}
|
|
background_getter.Join();
|
|
|
|
// Fail if this test takes more than 5 seconds (it takes 5-10 seconds on a
|
|
// Z840 without r527445 but is expected to be fast (~30ms) with the fix).
|
|
EXPECT_LT(base::TimeTicks::Now() - test_begin, base::Seconds(5));
|
|
}
|