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