1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#ifndef BASE_THREADING_THREAD_COLLISION_WARNER_H_ 6#define BASE_THREADING_THREAD_COLLISION_WARNER_H_ 7 8#include <memory> 9 10#include "base/atomicops.h" 11#include "base/base_export.h" 12#include "base/compiler_specific.h" 13#include "base/macros.h" 14 15// A helper class alongside macros to be used to verify assumptions about thread 16// safety of a class. 17// 18// Example: Queue implementation non thread-safe but still usable if clients 19// are synchronized somehow. 20// 21// In this case the macro DFAKE_SCOPED_LOCK has to be 22// used, it checks that if a thread is inside the push/pop then 23// noone else is still inside the pop/push 24// 25// class NonThreadSafeQueue { 26// public: 27// ... 28// void push(int) { DFAKE_SCOPED_LOCK(push_pop_); ... } 29// int pop() { DFAKE_SCOPED_LOCK(push_pop_); ... } 30// ... 31// private: 32// DFAKE_MUTEX(push_pop_); 33// }; 34// 35// 36// Example: Queue implementation non thread-safe but still usable if clients 37// are synchronized somehow, it calls a method to "protect" from 38// a "protected" method 39// 40// In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK 41// has to be used, it checks that if a thread is inside the push/pop 42// then noone else is still inside the pop/push 43// 44// class NonThreadSafeQueue { 45// public: 46// void push(int) { 47// DFAKE_SCOPED_LOCK(push_pop_); 48// ... 49// } 50// int pop() { 51// DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); 52// bar(); 53// ... 54// } 55// void bar() { DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); ... } 56// ... 57// private: 58// DFAKE_MUTEX(push_pop_); 59// }; 60// 61// 62// Example: Queue implementation not usable even if clients are synchronized, 63// so only one thread in the class life cycle can use the two members 64// push/pop. 65// 66// In this case the macro DFAKE_SCOPED_LOCK_THREAD_LOCKED pins the 67// specified 68// critical section the first time a thread enters push or pop, from 69// that time on only that thread is allowed to execute push or pop. 70// 71// class NonThreadSafeQueue { 72// public: 73// ... 74// void push(int) { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } 75// int pop() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } 76// ... 77// private: 78// DFAKE_MUTEX(push_pop_); 79// }; 80// 81// 82// Example: Class that has to be contructed/destroyed on same thread, it has 83// a "shareable" method (with external synchronization) and a not 84// shareable method (even with external synchronization). 85// 86// In this case 3 Critical sections have to be defined 87// 88// class ExoticClass { 89// public: 90// ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } 91// ~ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } 92// 93// void Shareable() { DFAKE_SCOPED_LOCK(shareable_section_); ... } 94// void NotShareable() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } 95// ... 96// private: 97// DFAKE_MUTEX(ctor_dtor_); 98// DFAKE_MUTEX(shareable_section_); 99// }; 100 101 102#if !defined(NDEBUG) 103 104// Defines a class member that acts like a mutex. It is used only as a 105// verification tool. 106#define DFAKE_MUTEX(obj) \ 107 mutable base::ThreadCollisionWarner obj 108// Asserts the call is never called simultaneously in two threads. Used at 109// member function scope. 110#define DFAKE_SCOPED_LOCK(obj) \ 111 base::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj) 112// Asserts the call is never called simultaneously in two threads. Used at 113// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks. 114#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \ 115 base::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj) 116// Asserts the code is always executed in the same thread. 117#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \ 118 base::ThreadCollisionWarner::Check check_##obj(&obj) 119 120#else 121 122#define DFAKE_MUTEX(obj) typedef void InternalFakeMutexType##obj 123#define DFAKE_SCOPED_LOCK(obj) ((void)0) 124#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) ((void)0) 125#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) ((void)0) 126 127#endif 128 129namespace base { 130 131// The class ThreadCollisionWarner uses an Asserter to notify the collision 132// AsserterBase is the interfaces and DCheckAsserter is the default asserter 133// used. During the unit tests is used another class that doesn't "DCHECK" 134// in case of collision (check thread_collision_warner_unittests.cc) 135struct BASE_EXPORT AsserterBase { 136 virtual ~AsserterBase() {} 137 virtual void warn() = 0; 138}; 139 140struct BASE_EXPORT DCheckAsserter : public AsserterBase { 141 ~DCheckAsserter() override {} 142 void warn() override; 143}; 144 145class BASE_EXPORT ThreadCollisionWarner { 146 public: 147 // The parameter asserter is there only for test purpose 148 explicit ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter()) 149 : valid_thread_id_(0), 150 counter_(0), 151 asserter_(asserter) {} 152 153 ~ThreadCollisionWarner() { 154 delete asserter_; 155 } 156 157 // This class is meant to be used through the macro 158 // DFAKE_SCOPED_LOCK_THREAD_LOCKED 159 // it doesn't leave the critical section, as opposed to ScopedCheck, 160 // because the critical section being pinned is allowed to be used only 161 // from one thread 162 class BASE_EXPORT Check { 163 public: 164 explicit Check(ThreadCollisionWarner* warner) 165 : warner_(warner) { 166 warner_->EnterSelf(); 167 } 168 169 ~Check() {} 170 171 private: 172 ThreadCollisionWarner* warner_; 173 174 DISALLOW_COPY_AND_ASSIGN(Check); 175 }; 176 177 // This class is meant to be used through the macro 178 // DFAKE_SCOPED_LOCK 179 class BASE_EXPORT ScopedCheck { 180 public: 181 explicit ScopedCheck(ThreadCollisionWarner* warner) 182 : warner_(warner) { 183 warner_->Enter(); 184 } 185 186 ~ScopedCheck() { 187 warner_->Leave(); 188 } 189 190 private: 191 ThreadCollisionWarner* warner_; 192 193 DISALLOW_COPY_AND_ASSIGN(ScopedCheck); 194 }; 195 196 // This class is meant to be used through the macro 197 // DFAKE_SCOPED_RECURSIVE_LOCK 198 class BASE_EXPORT ScopedRecursiveCheck { 199 public: 200 explicit ScopedRecursiveCheck(ThreadCollisionWarner* warner) 201 : warner_(warner) { 202 warner_->EnterSelf(); 203 } 204 205 ~ScopedRecursiveCheck() { 206 warner_->Leave(); 207 } 208 209 private: 210 ThreadCollisionWarner* warner_; 211 212 DISALLOW_COPY_AND_ASSIGN(ScopedRecursiveCheck); 213 }; 214 215 private: 216 // This method stores the current thread identifier and does a DCHECK 217 // if a another thread has already done it, it is safe if same thread 218 // calls this multiple time (recursion allowed). 219 void EnterSelf(); 220 221 // Same as EnterSelf but recursion is not allowed. 222 void Enter(); 223 224 // Removes the thread_id stored in order to allow other threads to 225 // call EnterSelf or Enter. 226 void Leave(); 227 228 // This stores the thread id that is inside the critical section, if the 229 // value is 0 then no thread is inside. 230 volatile subtle::Atomic32 valid_thread_id_; 231 232 // Counter to trace how many time a critical section was "pinned" 233 // (when allowed) in order to unpin it when counter_ reaches 0. 234 volatile subtle::Atomic32 counter_; 235 236 // Here only for class unit tests purpose, during the test I need to not 237 // DCHECK but notify the collision with something else. 238 AsserterBase* asserter_; 239 240 DISALLOW_COPY_AND_ASSIGN(ThreadCollisionWarner); 241}; 242 243} // namespace base 244 245#endif // BASE_THREADING_THREAD_COLLISION_WARNER_H_ 246