| // 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 |