• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/sequence_checker_impl.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/check.h"
11 #include "base/compiler_specific.h"
12 #include "base/containers/contains.h"
13 #include "base/debug/stack_trace.h"
14 #include "base/ranges/algorithm.h"
15 #include "base/sequence_token.h"
16 #include "base/synchronization/lock_subtle.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/threading/platform_thread_ref.h"
19 #include "base/threading/thread_checker.h"
20 #include "base/threading/thread_checker_impl.h"
21 #include "base/threading/thread_local_storage.h"
22 
23 namespace base {
24 
25 namespace {
26 bool g_log_stack = false;
27 }
28 
29 // static
EnableStackLogging()30 void SequenceCheckerImpl::EnableStackLogging() {
31   g_log_stack = true;
32   ThreadChecker::EnableStackLogging();
33 }
34 
SequenceCheckerImpl()35 SequenceCheckerImpl::SequenceCheckerImpl() {
36   AutoLock auto_lock(lock_);
37   EnsureAssigned();
38 }
39 
40 SequenceCheckerImpl::~SequenceCheckerImpl() = default;
41 
SequenceCheckerImpl(SequenceCheckerImpl && other)42 SequenceCheckerImpl::SequenceCheckerImpl(SequenceCheckerImpl&& other) {
43   // Verify that `other` is called on the correct thread.
44   // Note: This binds `other` if not already bound.
45   CHECK(other.CalledOnValidSequence());
46 
47   bound_at_ = std::move(other.bound_at_);
48   sequence_token_ = other.sequence_token_;
49 #if DCHECK_IS_ON()
50   locks_ = std::move(other.locks_);
51 #endif  // DCHECK_IS_ON()
52   thread_ref_ = other.thread_ref_;
53 
54   // `other.bound_at_` and `other.locks_` were moved so they're null.
55   DCHECK(!other.bound_at_);
56 #if DCHECK_IS_ON()
57   DCHECK(other.locks_.empty());
58 #endif  // DCHECK_IS_ON()
59   other.sequence_token_ = internal::SequenceToken();
60   other.thread_ref_ = PlatformThreadRef();
61 }
62 
operator =(SequenceCheckerImpl && other)63 SequenceCheckerImpl& SequenceCheckerImpl::operator=(
64     SequenceCheckerImpl&& other) {
65   // Verify that `other` is called on the correct thread.
66   // Note: This binds `other` if not already bound.
67   CHECK(other.CalledOnValidSequence());
68 
69   TS_UNCHECKED_READ(bound_at_) = std::move(TS_UNCHECKED_READ(other.bound_at_));
70   TS_UNCHECKED_READ(sequence_token_) = TS_UNCHECKED_READ(other.sequence_token_);
71 #if DCHECK_IS_ON()
72   TS_UNCHECKED_READ(locks_) = std::move(TS_UNCHECKED_READ(other.locks_));
73 #endif  // DCHECK_IS_ON()
74   TS_UNCHECKED_READ(thread_ref_) = TS_UNCHECKED_READ(other.thread_ref_);
75 
76   // `other.bound_at_` and `other.locks_` were moved so they're null.
77   DCHECK(!TS_UNCHECKED_READ(other.bound_at_));
78 #if DCHECK_IS_ON()
79   DCHECK(TS_UNCHECKED_READ(other.locks_).empty());
80 #endif  // DCHECK_IS_ON()
81   TS_UNCHECKED_READ(other.sequence_token_) = internal::SequenceToken();
82   TS_UNCHECKED_READ(other.thread_ref_) = PlatformThreadRef();
83 
84   return *this;
85 }
86 
CalledOnValidSequence(std::unique_ptr<debug::StackTrace> * out_bound_at) const87 bool SequenceCheckerImpl::CalledOnValidSequence(
88     std::unique_ptr<debug::StackTrace>* out_bound_at) const {
89   AutoLock auto_lock(lock_);
90   EnsureAssigned();
91   CHECK(!thread_ref_.is_null());
92 
93   // Valid if current sequence is the bound sequence.
94   bool is_valid =
95       (sequence_token_ == internal::SequenceToken::GetForCurrentThread());
96 
97   // Valid if holding a bound lock.
98   if (!is_valid) {
99 #if DCHECK_IS_ON()
100     for (uintptr_t lock : subtle::GetTrackedLocksHeldByCurrentThread()) {
101       if (Contains(locks_, lock)) {
102         is_valid = true;
103         break;
104       }
105     }
106 #endif  // DCHECK_IS_ON()
107   }
108 
109   // Valid if called from the bound thread after TLS destruction.
110   //
111   // TODO(pbos): This preserves existing behavior that `sequence_token_` is
112   // ignored after TLS shutdown. It should either be documented here why that is
113   // necessary (shouldn't this destroy on sequence?) or
114   // SequenceCheckerTest.FromThreadDestruction should be updated to reflect the
115   // expected behavior.
116   //
117   // crrev.com/682023 added this TLS-check to solve an edge case but that edge
118   // case was probably only a problem before TLS-destruction order was fixed in
119   // crrev.com/1119244. crrev.com/1117059 further improved TLS-destruction order
120   // of tokens by using `thread_local` and making it deterministic.
121   //
122   // See https://timsong-cpp.github.io/cppwp/n4140/basic.start.term: "If the
123   // completion of the constructor or dynamic initialization of an object with
124   // thread storage duration is sequenced before that of another, the completion
125   // of the destructor of the second is sequenced before the initiation of the
126   // destructor of the first."
127   if (!is_valid) {
128     is_valid = ThreadLocalStorage::HasBeenDestroyed() &&
129                thread_ref_ == PlatformThread::CurrentRef();
130   }
131 
132   if (!is_valid) {
133     // Return false without modifying the state if this call is not guaranteed
134     // to be mutually exclusive with others that returned true. Not modifying
135     // the state allows future calls to return true if they are mutually
136     // exclusive with other calls that returned true.
137 
138     // On failure, set the `out_bound_at` argument.
139     if (out_bound_at && bound_at_) {
140       *out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
141     }
142 
143     return false;
144   }
145 
146   // Before returning true, modify the state so future calls only return true if
147   // they are guaranteed to be mutually exclusive with this one.
148 
149 #if DCHECK_IS_ON()
150   // `locks_` must contain locks held at binding time and for all calls to
151   // `CalledOnValidSequence` that returned true afterwards.
152   std::erase_if(locks_, [](uintptr_t lock_ptr) {
153     return !Contains(subtle::GetTrackedLocksHeldByCurrentThread(), lock_ptr);
154   });
155 #endif  // DCHECK_IS_ON()
156 
157   // `sequence_token_` is reset if this returns true from a different sequence.
158   if (sequence_token_ != internal::SequenceToken::GetForCurrentThread()) {
159     sequence_token_ = internal::SequenceToken();
160   }
161 
162   return true;
163 }
164 
DetachFromSequence()165 void SequenceCheckerImpl::DetachFromSequence() {
166   AutoLock auto_lock(lock_);
167   bound_at_.reset();
168   sequence_token_ = internal::SequenceToken();
169 #if DCHECK_IS_ON()
170   locks_.clear();
171 #endif  // DCHECK_IS_ON()
172   thread_ref_ = PlatformThreadRef();
173 }
174 
EnsureAssigned() const175 void SequenceCheckerImpl::EnsureAssigned() const {
176   // Use `thread_ref_` to determine if this checker is already bound, as it is
177   // always set when bound (unlike `sequence_token_` and `locks_` which may be
178   // cleared by `CalledOnValidSequence()` while this checker is still bound).
179   if (!thread_ref_.is_null()) {
180     return;
181   }
182 
183   if (g_log_stack) {
184     bound_at_ = std::make_unique<debug::StackTrace>(size_t{10});
185   }
186 
187   sequence_token_ = internal::SequenceToken::GetForCurrentThread();
188 
189 #if DCHECK_IS_ON()
190   // Copy all held locks to `locks_`, except `&lock_` (this is an implementation
191   // detail of `SequenceCheckerImpl` and doesn't provide mutual exclusion
192   // guarantees to the caller).
193   DCHECK(locks_.empty());
194   ranges::remove_copy(subtle::GetTrackedLocksHeldByCurrentThread(),
195                       std::back_inserter(locks_),
196                       reinterpret_cast<uintptr_t>(&lock_));
197 #endif  // DCHECK_IS_ON()
198 
199   DCHECK(sequence_token_.IsValid());
200   thread_ref_ = PlatformThread::CurrentRef();
201   DCHECK(!thread_ref_.is_null());
202 }
203 
204 }  // namespace base
205