Panda3D
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 
20 using std::ostream;
21 using std::ostringstream;
22 
23 int MutexDebug::_pstats_count = 0;
24 MutexTrueImpl *MutexDebug::_global_lock;
25 
26 /**
27  *
28  */
29 MutexDebug::
30 MutexDebug(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  */
49 MutexDebug::
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  */
72 void MutexDebug::
73 output(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  */
88 void MutexDebug::
89 output_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  */
103 void MutexDebug::
104 increment_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  */
114 void MutexDebug::
115 decrement_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  */
124 void MutexDebug::
125 do_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  */
240 bool MutexDebug::
241 do_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  */
306 void MutexDebug::
307 do_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  */
384 bool MutexDebug::
385 do_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  */
405 void MutexDebug::
406 report_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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
A base class for all things which can have a name.
Definition: namable.h:26
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A thread; that is, a lightweight process.
Definition: thread.h:46
A fake mutex implementation for single-threaded applications that don't need any synchronization cont...