Panda3D
Loading...
Searching...
No Matches
mutexDebug.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file mutexDebug.cxx
10 * @author drose
11 * @date 2006-02-13
12 */
13
14#include "mutexDebug.h"
15#include "thread.h"
16#include "config_pipeline.h"
17
18#ifdef DEBUG_THREADS
19
20using std::ostream;
21using std::ostringstream;
22
23int MutexDebug::_pstats_count = 0;
24MutexTrueImpl *MutexDebug::_global_lock;
25
26/**
27 *
28 */
29MutexDebug::
30MutexDebug(const std::string &name, bool allow_recursion, bool lightweight) :
31 Namable(name),
32 _allow_recursion(allow_recursion),
33 _lightweight(lightweight),
34 _locking_thread(nullptr),
35 _lock_count(0),
36 _deleted_name(nullptr),
37 _cvar_impl(*get_global_lock())
38{
39#ifndef SIMPLE_THREADS
40 // If we're using real threads, there's no such thing as a lightweight
41 // mutex.
42 _lightweight = false;
43#endif
44}
45
46/**
47 *
48 */
49MutexDebug::
50~MutexDebug() {
51 nassertv(_locking_thread == nullptr && _lock_count == 0);
52
53 // If the config variable says to, allocate (and leak) a string name for the
54 // mutex, so we can report which mutex it is that has destructed after the
55 // fact.
56 if (name_deleted_mutexes) {
57 ostringstream strm;
58 strm << *this;
59 std::string name = strm.str();
60 _deleted_name = strdup((char *)name.c_str());
61 }
62
63 // Put a distinctive, bogus lock count in upon destruction, so we'll be more
64 // likely to notice a floating pointer.
65 _lock_count = -100;
66}
67
68/**
69 * This method is declared virtual in MutexDebug, but non-virtual in
70 * MutexDirect.
71 */
72void MutexDebug::
73output(ostream &out) const {
74 if (_lightweight) {
75 out << "Light";
76 }
77 if (_allow_recursion) {
78 out << "ReMutex " << get_name() << " " << (void *)this;
79 } else {
80 out << "Mutex " << get_name() << " " << (void *)this;
81 }
82}
83
84/**
85 * Reports the mutex as well as the thread that is currently holding it, if
86 * any.
87 */
88void MutexDebug::
89output_with_holder(ostream &out) const {
90 _global_lock->lock();
91 output(out);
92 if (_locking_thread != nullptr) {
93 out << " (held by " << *_locking_thread << ")\n";
94 }
95 _global_lock->unlock();
96}
97
98/**
99 * Intended to be called only by PStatClientImpl::client_connect(), this tells
100 * the global mutex system that PStats is active. Once PStats is active, all
101 * "light" mutexes are treated the same as full mutexes.
102 */
103void MutexDebug::
104increment_pstats() {
105 _global_lock->lock();
106 ++_pstats_count;
107 _global_lock->unlock();
108}
109
110/**
111 * Intended to be called only by PStatClientImpl::client_disconnect(), this
112 * tells the global mutex system that PStats is no longer active.
113 */
114void MutexDebug::
115decrement_pstats() {
116 _global_lock->lock();
117 --_pstats_count;
118 _global_lock->unlock();
119}
120
121/**
122 * The private implementation of acquire() assumes that _lock_impl is held.
123 */
124void MutexDebug::
125do_lock(Thread *current_thread) {
126 // If this assertion is triggered, you tried to lock a recently-destructed
127 // mutex.
128 nassertd(_lock_count != -100) {
129 pipeline_cat.error()
130 << "Destructed mutex: " << (void *)this << "\n";
131 if (name_deleted_mutexes && _deleted_name != nullptr) {
132 pipeline_cat.error()
133 << _deleted_name << "\n";
134 } else {
135 pipeline_cat.error()
136 << "Configure name-deleted-mutexes 1 to see the mutex name.\n";
137 }
138 return;
139 }
140
141 if (_locking_thread == nullptr) {
142 // The mutex is not already locked by anyone. Lock it.
143 _locking_thread = current_thread;
144 ++_lock_count;
145 nassertv(_lock_count == 1);
146
147 } else if (_locking_thread == current_thread) {
148 // The mutex is already locked by this thread. Increment the lock count.
149 nassertv(_lock_count > 0);
150 if (!_allow_recursion) {
151 ostringstream ostr;
152 ostr << *current_thread << " attempted to double-lock non-reentrant "
153 << *this;
154 nassert_raise(ostr.str());
155 }
156 ++_lock_count;
157
158 } else {
159 // The mutex is locked by some other thread.
160
161 if (_lightweight && _pstats_count == 0) {
162 // In this case, it's not a real mutex. Just watch it go by.
163 MissedThreads::iterator mi = _missed_threads.insert(MissedThreads::value_type(current_thread, 0)).first;
164 if ((*mi).second == 0) {
165 ostringstream ostr;
166 ostr << *current_thread << " not stopped by " << *this << " (held by "
167 << *_locking_thread << ")\n";
168 nassert_raise(ostr.str());
169 } else {
170 if (!_allow_recursion) {
171 ostringstream ostr;
172 ostr << *current_thread << " attempted to double-lock non-reentrant "
173 << *this;
174 nassert_raise(ostr.str());
175 }
176 }
177 ++((*mi).second);
178
179 } else {
180 // This is the real case. It's a real mutex, so block if necessary.
181
182 // Check for deadlock.
183 MutexDebug *next_mutex = this;
184 while (next_mutex != nullptr) {
185 if (next_mutex->_locking_thread == current_thread) {
186 // Whoops, the thread is blocked on me! Deadlock!
187 report_deadlock(current_thread);
188 nassert_raise("Deadlock");
189 return;
190 }
191 Thread *next_thread = next_mutex->_locking_thread;
192 if (next_thread == nullptr) {
193 // Looks like this mutex isn't actually locked, which means the last
194 // thread isn't really blocked--it just hasn't woken up yet to
195 // discover that. In any case, no deadlock.
196 break;
197 }
198
199 // The last thread is blocked on this "next thread"'s mutex, but what
200 // mutex is the next thread blocked on?
201 next_mutex = next_thread->_blocked_on_mutex;
202 }
203
204 // OK, no deadlock detected. Carry on.
205 current_thread->_blocked_on_mutex = this;
206
207 // Go to sleep on the condition variable until it's unlocked.
208
209 if (thread_cat->is_debug()) {
210 thread_cat.debug()
211 << *current_thread << " blocking on " << *this << " (held by "
212 << *_locking_thread << ")\n";
213 }
214
215 while (_locking_thread != nullptr) {
216 thread_cat.debug()
217 << *current_thread << " still blocking on " << *this << " (held by "
218 << *_locking_thread << ")\n";
219 _cvar_impl.wait();
220 }
221
222 if (thread_cat.is_debug()) {
223 thread_cat.debug()
224 << *current_thread << " acquired " << *this << "\n";
225 }
226
227 current_thread->_blocked_on_mutex = nullptr;
228
229 _locking_thread = current_thread;
230 ++_lock_count;
231 nassertv(_lock_count == 1);
232 }
233 }
234}
235
236/**
237 * The private implementation of acquire(false) assumes that _lock_impl is
238 * held.
239 */
240bool MutexDebug::
241do_try_lock(Thread *current_thread) {
242 // If this assertion is triggered, you tried to lock a recently-destructed
243 // mutex.
244 nassertd(_lock_count != -100) {
245 pipeline_cat.error()
246 << "Destructed mutex: " << (void *)this << "\n";
247 if (name_deleted_mutexes && _deleted_name != nullptr) {
248 pipeline_cat.error()
249 << _deleted_name << "\n";
250 } else {
251 pipeline_cat.error()
252 << "Configure name-deleted-mutexes 1 to see the mutex name.\n";
253 }
254 return false;
255 }
256
257 bool acquired = true;
258 if (_locking_thread == nullptr) {
259 // The mutex is not already locked by anyone. Lock it.
260 _locking_thread = current_thread;
261 ++_lock_count;
262 nassertr(_lock_count == 1, false);
263
264 } else if (_locking_thread == current_thread) {
265 // The mutex is already locked by this thread. Increment the lock count.
266 nassertr(_lock_count > 0, false);
267 if (!_allow_recursion) {
268 // Non-recursive lock; return false.
269 acquired = false;
270 } else {
271 ++_lock_count;
272 }
273
274 } else {
275 // The mutex is locked by some other thread. Return false.
276
277 if (_lightweight && _pstats_count == 0) {
278 // In this case, it's not a real mutex. Just watch it go by.
279 MissedThreads::iterator mi = _missed_threads.insert(MissedThreads::value_type(current_thread, 0)).first;
280 if ((*mi).second == 0) {
281 thread_cat.info()
282 << *current_thread << " not stopped by " << *this << " (held by "
283 << *_locking_thread << ")\n";
284 } else {
285 if (!_allow_recursion) {
286 ostringstream ostr;
287 ostr << *current_thread << " attempted to double-lock non-reentrant "
288 << *this;
289 nassert_raise(ostr.str());
290 }
291 }
292 ++((*mi).second);
293
294 } else {
295 // This is the real case.
296 acquired = false;
297 }
298 }
299
300 return acquired;
301}
302
303/**
304 * The private implementation of acquire() assumes that _lock_impl is held.
305 */
306void MutexDebug::
307do_unlock() {
308 // If this assertion is triggered, you tried to release a recently-
309 // destructed mutex.
310 nassertd(_lock_count != -100) {
311 pipeline_cat.error()
312 << "Destructed mutex: " << (void *)this << "\n";
313 if (name_deleted_mutexes && _deleted_name != nullptr) {
314 pipeline_cat.error()
315 << _deleted_name << "\n";
316 } else {
317 pipeline_cat.error()
318 << "Configure name-deleted-mutexes 1 to see the mutex name.\n";
319 }
320 return;
321 }
322
323 Thread *current_thread = Thread::get_current_thread();
324
325 if (_locking_thread != current_thread) {
326 // We're not holding this mutex.
327
328 if (_lightweight) {
329 // Not a real mutex. This just means we blew past a mutex without
330 // locking it, above.
331
332 MissedThreads::iterator mi = _missed_threads.find(current_thread);
333 nassertv(mi != _missed_threads.end());
334 nassertv((*mi).second > 0);
335 --((*mi).second);
336
337 if ((*mi).second == 0) {
338 _missed_threads.erase(mi);
339 }
340
341 } else {
342 // In the real-mutex case, this is an error condition.
343 ostringstream ostr;
344 ostr << *current_thread << " attempted to release "
345 << *this << " which it does not own";
346 nassert_raise(ostr.str());
347 }
348 return;
349 }
350
351 nassertv(_lock_count > 0);
352
353 --_lock_count;
354 if (_lock_count == 0) {
355 // That was the last lock held by this thread. Release the lock.
356 _locking_thread = nullptr;
357
358 if (_lightweight) {
359 if (!_missed_threads.empty()) {
360 // Promote some other thread to be the honorary lock holder.
361 MissedThreads::iterator mi = _missed_threads.begin();
362 _locking_thread = (*mi).first;
363 _lock_count = (*mi).second;
364 _missed_threads.erase(mi);
365 nassertv(_lock_count > 0);
366 }
367 } else {
368
369 /*
370 if (thread_cat.is_debug()) {
371 thread_cat.debug()
372 << *current_thread << " releasing " << *this << "\n";
373 }
374 */
375 _cvar_impl.notify();
376 }
377 }
378}
379
380/**
381 * The private implementation of debug_is_locked() assumes that _lock_impl is
382 * held.
383 */
384bool MutexDebug::
385do_debug_is_locked() const {
386 Thread *current_thread = Thread::get_current_thread();
387 if (_locking_thread == current_thread) {
388 return true;
389 }
390
391 if (_lightweight) {
392 MissedThreads::const_iterator mi = _missed_threads.find(current_thread);
393 if (mi != _missed_threads.end()) {
394 nassertr((*mi).second > 0, false);
395 return true;
396 }
397 }
398
399 return false;
400}
401
402/**
403 * Reports a detected deadlock situation. _lock_impl should be already held.
404 */
405void MutexDebug::
406report_deadlock(Thread *current_thread) {
407 thread_cat->error()
408 << "\n\n"
409 << "****************************************************************\n"
410 << "***** Deadlock detected! *****\n"
411 << "****************************************************************\n"
412 << "\n";
413
414 thread_cat.error()
415 << *current_thread << " attempted to lock " << *this
416 << " which is held by " << *_locking_thread << "\n";
417
418 MutexDebug *next_mutex = this;
419 Thread *next_thread = next_mutex->_locking_thread;
420 next_mutex = next_thread->_blocked_on_mutex;
421 while (next_mutex != nullptr) {
422 thread_cat.error()
423 << *next_thread << " is blocked waiting on "
424 << *next_mutex << " which is held by "
425 << *next_mutex->_locking_thread << "\n";
426 next_thread = next_mutex->_locking_thread;
427 next_mutex = next_thread->_blocked_on_mutex;
428 }
429
430 thread_cat.error()
431 << "Deadlock!\n";
432}
433
434#endif // DEBUG_THREADS
A fake mutex implementation for single-threaded applications that don't need any synchronization cont...
A base class for all things which can have a name.
Definition namable.h:26
A thread; that is, a lightweight process.
Definition thread.h:46
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition thread.h:109
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.