359 lines
12 KiB
C++
359 lines
12 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/threading/thread_checker.h"
|
||
|
|
|
||
|
|
#include <memory>
|
||
|
|
|
||
|
|
#include "base/functional/bind.h"
|
||
|
|
#include "base/functional/callback_helpers.h"
|
||
|
|
#include "base/memory/ref_counted.h"
|
||
|
|
#include "base/sequence_token.h"
|
||
|
|
#include "base/task/single_thread_task_runner.h"
|
||
|
|
#include "base/test/bind.h"
|
||
|
|
#include "base/test/gtest_util.h"
|
||
|
|
#include "base/test/test_simple_task_runner.h"
|
||
|
|
#include "base/threading/simple_thread.h"
|
||
|
|
#include "base/threading/thread_local.h"
|
||
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// A thread that runs a callback.
|
||
|
|
class RunCallbackThread : public SimpleThread {
|
||
|
|
public:
|
||
|
|
explicit RunCallbackThread(OnceClosure callback)
|
||
|
|
: SimpleThread("RunCallbackThread"), callback_(std::move(callback)) {}
|
||
|
|
|
||
|
|
RunCallbackThread(const RunCallbackThread&) = delete;
|
||
|
|
RunCallbackThread& operator=(const RunCallbackThread&) = delete;
|
||
|
|
|
||
|
|
private:
|
||
|
|
// SimpleThread:
|
||
|
|
void Run() override { std::move(callback_).Run(); }
|
||
|
|
|
||
|
|
OnceClosure callback_;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Runs a callback on a new thread synchronously.
|
||
|
|
void RunCallbackOnNewThreadSynchronously(OnceClosure callback) {
|
||
|
|
RunCallbackThread run_callback_thread(std::move(callback));
|
||
|
|
run_callback_thread.Start();
|
||
|
|
run_callback_thread.Join();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ExpectCalledOnValidThread(ThreadCheckerImpl* thread_checker) {
|
||
|
|
ASSERT_TRUE(thread_checker);
|
||
|
|
|
||
|
|
// This should bind |thread_checker| to the current thread if it wasn't
|
||
|
|
// already bound to a thread.
|
||
|
|
EXPECT_TRUE(thread_checker->CalledOnValidThread());
|
||
|
|
|
||
|
|
// Since |thread_checker| is now bound to the current thread, another call to
|
||
|
|
// CalledOnValidThread() should return true.
|
||
|
|
EXPECT_TRUE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
void ExpectNotCalledOnValidThread(ThreadCheckerImpl* thread_checker) {
|
||
|
|
ASSERT_TRUE(thread_checker);
|
||
|
|
EXPECT_FALSE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
void ExpectNotCalledOnValidThreadWithSequenceTokenAndThreadTaskRunnerHandle(
|
||
|
|
ThreadCheckerImpl* thread_checker,
|
||
|
|
SequenceToken sequence_token) {
|
||
|
|
SingleThreadTaskRunner::CurrentDefaultHandle
|
||
|
|
single_thread_task_runner_current_default_handle(
|
||
|
|
MakeRefCounted<TestSimpleTaskRunner>());
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(sequence_token);
|
||
|
|
ExpectNotCalledOnValidThread(thread_checker);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, AllowedSameThreadNoSequenceToken) {
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
EXPECT_TRUE(thread_checker.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest,
|
||
|
|
AllowedSameThreadAndSequenceDifferentTasksWithThreadTaskRunnerHandle) {
|
||
|
|
SingleThreadTaskRunner::CurrentDefaultHandle
|
||
|
|
single_thread_task_runner_current_default_handle(
|
||
|
|
MakeRefCounted<TestSimpleTaskRunner>());
|
||
|
|
|
||
|
|
std::unique_ptr<ThreadCheckerImpl> thread_checker;
|
||
|
|
const SequenceToken sequence_token = SequenceToken::Create();
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(sequence_token);
|
||
|
|
thread_checker = std::make_unique<ThreadCheckerImpl>();
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(sequence_token);
|
||
|
|
EXPECT_TRUE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest,
|
||
|
|
AllowedSameThreadSequenceAndTaskNoThreadTaskRunnerHandle) {
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
EXPECT_TRUE(thread_checker.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest,
|
||
|
|
DisallowedSameThreadAndSequenceDifferentTasksNoThreadTaskRunnerHandle) {
|
||
|
|
std::unique_ptr<ThreadCheckerImpl> thread_checker;
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
thread_checker = std::make_unique<ThreadCheckerImpl>();
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
EXPECT_FALSE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, DisallowedDifferentThreadsNoSequenceToken) {
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectNotCalledOnValidThread, Unretained(&thread_checker)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, DisallowedDifferentThreadsSameSequence) {
|
||
|
|
SingleThreadTaskRunner::CurrentDefaultHandle
|
||
|
|
single_thread_task_runner_current_default_handle(
|
||
|
|
MakeRefCounted<TestSimpleTaskRunner>());
|
||
|
|
const SequenceToken sequence_token(SequenceToken::Create());
|
||
|
|
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(sequence_token);
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
EXPECT_TRUE(thread_checker.CalledOnValidThread());
|
||
|
|
|
||
|
|
RunCallbackOnNewThreadSynchronously(BindOnce(
|
||
|
|
&ExpectNotCalledOnValidThreadWithSequenceTokenAndThreadTaskRunnerHandle,
|
||
|
|
Unretained(&thread_checker), sequence_token));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, DisallowedSameThreadDifferentSequence) {
|
||
|
|
std::unique_ptr<ThreadCheckerImpl> thread_checker;
|
||
|
|
|
||
|
|
SingleThreadTaskRunner::CurrentDefaultHandle
|
||
|
|
single_thread_task_runner_current_default_handle(
|
||
|
|
MakeRefCounted<TestSimpleTaskRunner>());
|
||
|
|
|
||
|
|
{
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
thread_checker = std::make_unique<ThreadCheckerImpl>();
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
// Different SequenceToken.
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
EXPECT_FALSE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
// No SequenceToken.
|
||
|
|
EXPECT_FALSE(thread_checker->CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, DetachFromThread) {
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
thread_checker.DetachFromThread();
|
||
|
|
|
||
|
|
// Verify that CalledOnValidThread() returns true when called on a different
|
||
|
|
// thread after a call to DetachFromThread().
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
|
||
|
|
|
||
|
|
EXPECT_FALSE(thread_checker.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, DetachFromThreadWithSequenceToken) {
|
||
|
|
SingleThreadTaskRunner::CurrentDefaultHandle
|
||
|
|
single_thread_task_runner_current_default_handle(
|
||
|
|
MakeRefCounted<TestSimpleTaskRunner>());
|
||
|
|
ScopedSetSequenceTokenForCurrentThread
|
||
|
|
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
|
||
|
|
ThreadCheckerImpl thread_checker;
|
||
|
|
thread_checker.DetachFromThread();
|
||
|
|
|
||
|
|
// Verify that CalledOnValidThread() returns true when called on a different
|
||
|
|
// thread after a call to DetachFromThread().
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
|
||
|
|
|
||
|
|
EXPECT_FALSE(thread_checker.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Owns a ThreadCheckerImpl and asserts that CalledOnValidThread() is valid
|
||
|
|
// in ~ThreadCheckerOwner.
|
||
|
|
class ThreadCheckerOwner {
|
||
|
|
public:
|
||
|
|
explicit ThreadCheckerOwner(bool detach_from_thread) {
|
||
|
|
if (detach_from_thread)
|
||
|
|
checker_.DetachFromThread();
|
||
|
|
}
|
||
|
|
|
||
|
|
ThreadCheckerOwner(const ThreadCheckerOwner&) = delete;
|
||
|
|
ThreadCheckerOwner& operator=(const ThreadCheckerOwner&) = delete;
|
||
|
|
|
||
|
|
~ThreadCheckerOwner() { EXPECT_TRUE(checker_.CalledOnValidThread()); }
|
||
|
|
|
||
|
|
private:
|
||
|
|
ThreadCheckerImpl checker_;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Verifies ThreadCheckerImpl::CalledOnValidThread() returns true if called
|
||
|
|
// during thread destruction.
|
||
|
|
TEST(ThreadCheckerTest, CalledOnValidThreadFromThreadDestruction) {
|
||
|
|
ThreadLocalOwnedPointer<ThreadCheckerOwner> thread_local_owner;
|
||
|
|
RunCallbackOnNewThreadSynchronously(BindLambdaForTesting([&]() {
|
||
|
|
thread_local_owner.Set(std::make_unique<ThreadCheckerOwner>(false));
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Variant of CalledOnValidThreadFromThreadDestruction that calls
|
||
|
|
// ThreadCheckerImpl::DetachFromThread().
|
||
|
|
TEST(ThreadCheckerTest, CalledOnValidThreadFromThreadDestructionDetached) {
|
||
|
|
ThreadLocalOwnedPointer<ThreadCheckerOwner> thread_local_owner;
|
||
|
|
RunCallbackOnNewThreadSynchronously(BindLambdaForTesting([&]() {
|
||
|
|
thread_local_owner.Set(std::make_unique<ThreadCheckerOwner>(true));
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, Move) {
|
||
|
|
ThreadCheckerImpl initial;
|
||
|
|
EXPECT_TRUE(initial.CalledOnValidThread());
|
||
|
|
|
||
|
|
ThreadCheckerImpl move_constructed(std::move(initial));
|
||
|
|
EXPECT_TRUE(move_constructed.CalledOnValidThread());
|
||
|
|
|
||
|
|
ThreadCheckerImpl move_assigned;
|
||
|
|
move_assigned = std::move(move_constructed);
|
||
|
|
EXPECT_TRUE(move_assigned.CalledOnValidThread());
|
||
|
|
|
||
|
|
// The two ThreadCheckerImpls moved from should be able to rebind to another
|
||
|
|
// thread.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&move_constructed)));
|
||
|
|
|
||
|
|
// But the latest one shouldn't be able to run on another thread.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectNotCalledOnValidThread, Unretained(&move_assigned)));
|
||
|
|
|
||
|
|
EXPECT_TRUE(move_assigned.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, MoveAssignIntoDetached) {
|
||
|
|
ThreadCheckerImpl initial;
|
||
|
|
|
||
|
|
ThreadCheckerImpl move_assigned;
|
||
|
|
move_assigned.DetachFromThread();
|
||
|
|
move_assigned = std::move(initial);
|
||
|
|
|
||
|
|
// |initial| is detached after move.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
|
||
|
|
|
||
|
|
// |move_assigned| should be associated with the main thread.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectNotCalledOnValidThread, Unretained(&move_assigned)));
|
||
|
|
|
||
|
|
EXPECT_TRUE(move_assigned.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, MoveFromDetachedRebinds) {
|
||
|
|
ThreadCheckerImpl initial;
|
||
|
|
initial.DetachFromThread();
|
||
|
|
|
||
|
|
ThreadCheckerImpl moved_into(std::move(initial));
|
||
|
|
|
||
|
|
// |initial| is still detached after move.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
|
||
|
|
|
||
|
|
// |moved_into| is bound to the current thread as part of the move.
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectNotCalledOnValidThread, Unretained(&moved_into)));
|
||
|
|
EXPECT_TRUE(moved_into.CalledOnValidThread());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ThreadCheckerTest, MoveOffThreadBanned) {
|
||
|
|
testing::GTEST_FLAG(death_test_style) = "threadsafe";
|
||
|
|
|
||
|
|
ThreadCheckerImpl other_thread;
|
||
|
|
other_thread.DetachFromThread();
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ExpectCalledOnValidThread, Unretained(&other_thread)));
|
||
|
|
|
||
|
|
EXPECT_DCHECK_DEATH(ThreadCheckerImpl main_thread(std::move(other_thread)));
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// This fixture is a helper for unit testing the thread checker macros as it is
|
||
|
|
// not possible to inline ExpectDeathOnOtherThread() and
|
||
|
|
// ExpectNoDeathOnOtherThreadAfterDetach() as lambdas since binding
|
||
|
|
// |Unretained(&my_sequence_checker)| wouldn't compile on non-dcheck builds
|
||
|
|
// where it won't be defined.
|
||
|
|
class ThreadCheckerMacroTest : public testing::Test {
|
||
|
|
public:
|
||
|
|
ThreadCheckerMacroTest() = default;
|
||
|
|
|
||
|
|
ThreadCheckerMacroTest(const ThreadCheckerMacroTest&) = delete;
|
||
|
|
ThreadCheckerMacroTest& operator=(const ThreadCheckerMacroTest&) = delete;
|
||
|
|
|
||
|
|
void ExpectDeathOnOtherThread() {
|
||
|
|
#if DCHECK_IS_ON()
|
||
|
|
EXPECT_DCHECK_DEATH({ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); });
|
||
|
|
#else
|
||
|
|
// Happily no-ops on non-dcheck builds.
|
||
|
|
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
void ExpectNoDeathOnOtherThreadAfterDetach() {
|
||
|
|
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected:
|
||
|
|
THREAD_CHECKER(thread_checker_);
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST_F(ThreadCheckerMacroTest, Macros) {
|
||
|
|
testing::GTEST_FLAG(death_test_style) = "threadsafe";
|
||
|
|
|
||
|
|
THREAD_CHECKER(my_thread_checker);
|
||
|
|
|
||
|
|
RunCallbackOnNewThreadSynchronously(BindOnce(
|
||
|
|
&ThreadCheckerMacroTest::ExpectDeathOnOtherThread, Unretained(this)));
|
||
|
|
|
||
|
|
DETACH_FROM_THREAD(thread_checker_);
|
||
|
|
|
||
|
|
RunCallbackOnNewThreadSynchronously(
|
||
|
|
BindOnce(&ThreadCheckerMacroTest::ExpectNoDeathOnOtherThreadAfterDetach,
|
||
|
|
Unretained(this)));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|