1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
// Copyright 2011 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_impl.h"
#include "base/check.h"
#include "base/debug/stack_trace.h"
#include "base/sequence_token.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_local.h"
namespace {
bool g_log_stack = false;
}
namespace base {
// static
void ThreadCheckerImpl::EnableStackLogging() {
g_log_stack = true;
}
ThreadCheckerImpl::ThreadCheckerImpl() {
AutoLock auto_lock(lock_);
EnsureAssigned();
}
ThreadCheckerImpl::~ThreadCheckerImpl() = default;
ThreadCheckerImpl::ThreadCheckerImpl(ThreadCheckerImpl&& other) {
// Verify that `other` is called on the correct thread.
// Note: This binds `other` if not already bound.
CHECK(other.CalledOnValidThread());
// Not using `other.lock_` to let TSAN catch racy construct from `other`.
bound_at_ = std::move(other.bound_at_);
thread_ref_ = other.thread_ref_;
task_token_ = other.task_token_;
sequence_token_ = other.sequence_token_;
// `other.bound_at_` was moved from so it's null.
other.thread_ref_ = PlatformThreadRef();
other.task_token_ = internal::TaskToken();
other.sequence_token_ = internal::SequenceToken();
}
ThreadCheckerImpl& ThreadCheckerImpl::operator=(ThreadCheckerImpl&& other) {
CHECK(CalledOnValidThread());
// Verify that `other` is called on the correct thread.
// Note: This binds `other` if not already bound.
CHECK(other.CalledOnValidThread());
// Intentionally not using either |lock_| to let TSAN catch racy assign.
TS_UNCHECKED_READ(thread_ref_) = TS_UNCHECKED_READ(other.thread_ref_);
TS_UNCHECKED_READ(task_token_) = TS_UNCHECKED_READ(other.task_token_);
TS_UNCHECKED_READ(sequence_token_) = TS_UNCHECKED_READ(other.sequence_token_);
TS_UNCHECKED_READ(other.thread_ref_) = PlatformThreadRef();
TS_UNCHECKED_READ(other.task_token_) = internal::TaskToken();
TS_UNCHECKED_READ(other.sequence_token_) = internal::SequenceToken();
return *this;
}
bool ThreadCheckerImpl::CalledOnValidThread(
std::unique_ptr<debug::StackTrace>* out_bound_at) const {
AutoLock auto_lock(lock_);
// If we're detached, bind to current state.
EnsureAssigned();
DCHECK(sequence_token_.IsValid());
// Cases to handle:
//
// 1. Bound outside a task and used on the same thread: return true.
// 2. Used on the same thread, TLS destroyed: return true.
// Note: This case exists for historical reasons and should be
// removed. See details in `SequenceCheckerImpl`.
// 3. Same sequence as when this was bound:
// 3a. Sequence is associated with a thread: return true.
// 3b. Sequence may run on any thread: return false.
// Note: Return false even if this happens on the same thread as when
// this was bound, because that would be fortuitous.
// 4. Different sequence than when this was bound: return false.
if (thread_ref_ == PlatformThread::CurrentRef()) {
// If this runs on the bound thread:
// Return true if the checker was bound outside of a `TaskScope`.
if (!task_token_.IsValid()) {
return true;
}
// Return true if the checker was bound in the same `TaskScope`.
if (task_token_ == internal::TaskToken::GetForCurrentThread()) {
return true;
}
// Return true if TLS has been destroyed.
//
// This exists for historical reasons and can probably be removed. See
// details in `SequenceCheckerImpl::CalledOnValidSequence()`.
if (ThreadLocalStorage::HasBeenDestroyed()) {
return true;
}
// Return true if the checker was bound in the same thread-bound sequence.
// `CurrentTaskIsThreadBound()` avoids returning true when non-thread-bound
// tasks from the same sequence run on the same thread by chance.
if (sequence_token_ == internal::SequenceToken::GetForCurrentThread() &&
internal::CurrentTaskIsThreadBound()) {
return true;
}
}
// On failure, set the `out_bound_at` argument.
if (out_bound_at && bound_at_) {
*out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
}
return false;
}
void ThreadCheckerImpl::DetachFromThread() {
AutoLock auto_lock(lock_);
bound_at_ = nullptr;
thread_ref_ = PlatformThreadRef();
task_token_ = internal::TaskToken();
sequence_token_ = internal::SequenceToken();
}
void ThreadCheckerImpl::EnsureAssigned() const {
if (!thread_ref_.is_null()) {
return;
}
if (g_log_stack) {
bound_at_ = std::make_unique<debug::StackTrace>(size_t{10});
}
thread_ref_ = PlatformThread::CurrentRef();
task_token_ = internal::TaskToken::GetForCurrentThread();
sequence_token_ = internal::SequenceToken::GetForCurrentThread();
}
} // namespace base
|