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

base / threading / thread_checker_impl.cc [blame]

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