1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: sw=4 ts=4 et :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Chris Jones <jones.chris.g@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 : #ifndef mozilla_DeadlockDetector_h
40 : #define mozilla_DeadlockDetector_h
41 :
42 : #include "mozilla/Attributes.h"
43 :
44 : #include <stdlib.h>
45 :
46 : #include "plhash.h"
47 : #include "prlock.h"
48 :
49 : #include "nsTArray.h"
50 :
51 : #ifdef NS_TRACE_MALLOC
52 : # include "nsTraceMalloc.h"
53 : #endif // ifdef NS_TRACE_MALLOC
54 :
55 : namespace mozilla {
56 :
57 :
58 : // FIXME bug 456272: split this off into a convenience API on top of
59 : // nsStackWalk?
60 : class NS_COM_GLUE CallStack
61 : {
62 : private:
63 : #ifdef NS_TRACE_MALLOC
64 : typedef nsTMStackTraceID callstack_id;
65 : // needs to be a macro to avoid disturbing the backtrace
66 : # define NS_GET_BACKTRACE() NS_TraceMallocGetStackTrace()
67 : #else
68 : typedef void* callstack_id;
69 : # define NS_GET_BACKTRACE() 0
70 : #endif // ifdef NS_TRACE_MALLOC
71 :
72 : callstack_id mCallStack;
73 :
74 : public:
75 : /**
76 : * CallStack
77 : * *ALWAYS* *ALWAYS* *ALWAYS* call this with no arguments. This
78 : * constructor takes an argument *ONLY* so that |GET_BACKTRACE()|
79 : * can be evaluated in the stack frame of the caller, rather than
80 : * that of the constructor.
81 : *
82 : * *BEWARE*: this means that calling this constructor with no
83 : * arguments is not the same as a "default, do-nothing"
84 : * constructor: it *will* construct a backtrace. This can cause
85 : * unexpected performance issues.
86 : */
87 26693230 : CallStack(const callstack_id aCallStack = NS_GET_BACKTRACE()) :
88 26693230 : mCallStack(aCallStack)
89 : {
90 26693230 : }
91 701454 : CallStack(const CallStack& aFrom) :
92 701454 : mCallStack(aFrom.mCallStack)
93 : {
94 701454 : }
95 53756191 : CallStack& operator=(const CallStack& aFrom)
96 : {
97 53756191 : mCallStack = aFrom.mCallStack;
98 53756191 : return *this;
99 : }
100 53382528 : bool operator==(const CallStack& aOther) const
101 : {
102 53382528 : return mCallStack == aOther.mCallStack;
103 : }
104 26691749 : bool operator!=(const CallStack& aOther) const
105 : {
106 26691749 : return mCallStack != aOther.mCallStack;
107 : }
108 :
109 : // FIXME bug 456272: if this is split off,
110 : // NS_TraceMallocPrintStackTrace should be modified to print into
111 : // an nsACString
112 0 : void Print(FILE* f) const
113 : {
114 : #ifdef NS_TRACE_MALLOC
115 : if (this != &kNone && mCallStack) {
116 : NS_TraceMallocPrintStackTrace(f, mCallStack);
117 : return;
118 : }
119 : #endif
120 0 : fputs(" [stack trace unavailable]\n", f);
121 0 : }
122 :
123 : /** The "null" callstack. */
124 : static const CallStack kNone;
125 : };
126 :
127 :
128 : /**
129 : * DeadlockDetector
130 : *
131 : * The following is an approximate description of how the deadlock detector
132 : * works.
133 : *
134 : * The deadlock detector ensures that all blocking resources are
135 : * acquired according to a partial order P. One type of blocking
136 : * resource is a lock. If a lock l1 is acquired (locked) before l2,
137 : * then we say that |l1 <_P l2|. The detector flags an error if two
138 : * locks l1 and l2 have an inconsistent ordering in P; that is, if
139 : * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error
140 : * because a thread acquiring l1,l2 according to the first order might
141 : * race with a thread acquiring them according to the second order.
142 : * If this happens under the right conditions, then the acquisitions
143 : * will deadlock.
144 : *
145 : * This deadlock detector doesn't know at compile-time what P is. So,
146 : * it tries to discover the order at run time. More precisely, it
147 : * finds <i>some</i> order P, then tries to find chains of resource
148 : * acquisitions that violate P. An example acquisition sequence, and
149 : * the orders they impose, is
150 : * l1.lock() // current chain: [ l1 ]
151 : * // order: { }
152 : *
153 : * l2.lock() // current chain: [ l1, l2 ]
154 : * // order: { l1 <_P l2 }
155 : *
156 : * l3.lock() // current chain: [ l1, l2, l3 ]
157 : * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
158 : * // (note: <_P is transitive, so also |l1 <_P l3|)
159 : *
160 : * l2.unlock() // current chain: [ l1, l3 ]
161 : * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
162 : * // (note: it's OK, but weird, that l2 was unlocked out
163 : * // of order. we still have l1 <_P l3).
164 : *
165 : * l2.lock() // current chain: [ l1, l3, l2 ]
166 : * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3,
167 : * l3 <_P l2 (!!!) }
168 : * BEEP BEEP! Here the detector will flag a potential error, since
169 : * l2 and l3 were used inconsistently (and potentially in ways that
170 : * would deadlock).
171 : */
172 : template <typename T>
173 : class DeadlockDetector
174 : {
175 : public:
176 : /**
177 : * ResourceAcquisition
178 : * Consists simply of a resource and the calling context from
179 : * which it was acquired. We pack this information together so
180 : * that it can be returned back to the caller when a potential
181 : * deadlock has been found.
182 : */
183 : struct ResourceAcquisition
184 0 : {
185 : const T* mResource;
186 : CallStack mCallContext;
187 :
188 0 : ResourceAcquisition(
189 : const T* aResource,
190 : const CallStack aCallContext=CallStack::kNone) :
191 : mResource(aResource),
192 0 : mCallContext(aCallContext)
193 : {
194 0 : }
195 0 : ResourceAcquisition(const ResourceAcquisition& aFrom) :
196 : mResource(aFrom.mResource),
197 0 : mCallContext(aFrom.mCallContext)
198 : {
199 0 : }
200 : ResourceAcquisition& operator=(const ResourceAcquisition& aFrom)
201 : {
202 : mResource = aFrom.mResource;
203 : mCallContext = aFrom.mCallContext;
204 : return *this;
205 : }
206 : };
207 : typedef nsTArray<ResourceAcquisition> ResourceAcquisitionArray;
208 :
209 : private:
210 : typedef nsTArray<PLHashEntry*> HashEntryArray;
211 : typedef typename HashEntryArray::index_type index_type;
212 : typedef typename HashEntryArray::size_type size_type;
213 : enum {
214 : NoIndex = HashEntryArray::NoIndex
215 : };
216 :
217 : /**
218 : * Value type for the ordering table. Contains the other
219 : * resources on which an ordering constraint |key < other|
220 : * exists. The catch is that we also store the calling context at
221 : * which the other resource was acquired; this improves the
222 : * quality of error messages when potential deadlock is detected.
223 : */
224 : struct OrderingEntry
225 : {
226 182916 : OrderingEntry() :
227 : mFirstSeen(CallStack::kNone),
228 182916 : mOrderedLT() // FIXME bug 456272: set to empirical
229 : { // dep size?
230 182916 : }
231 182900 : ~OrderingEntry()
232 : {
233 182900 : }
234 :
235 : CallStack mFirstSeen; // first site from which the resource appeared
236 : HashEntryArray mOrderedLT; // this <_o Other
237 : };
238 :
239 4789 : static void* TableAlloc(void* /*pool*/, PRSize size)
240 : {
241 4789 : return operator new(size);
242 : }
243 4779 : static void TableFree(void* /*pool*/, void* item)
244 : {
245 : operator delete(item);
246 4779 : }
247 182916 : static PLHashEntry* EntryAlloc(void* /*pool*/, const void* key)
248 : {
249 182916 : return new PLHashEntry;
250 : }
251 182900 : static void EntryFree(void* /*pool*/, PLHashEntry* entry, PRUintn flag)
252 : {
253 182900 : delete static_cast<T*>(const_cast<void*>(entry->key));
254 182900 : delete static_cast<OrderingEntry*>(entry->value);
255 182900 : entry->value = 0;
256 182900 : if (HT_FREE_ENTRY == flag)
257 : delete entry;
258 182900 : }
259 29731839 : static PLHashNumber HashKey(const void* aKey)
260 : {
261 29731839 : return NS_PTR_TO_INT32(aKey) >> 2;
262 : }
263 : static const PLHashAllocOps kAllocOps;
264 :
265 : // Hash table "interface" the rest of the code should use
266 :
267 29548923 : PLHashEntry** GetEntry(const T* aKey)
268 : {
269 29548923 : return PL_HashTableRawLookup(mOrdering, HashKey(aKey), aKey);
270 : }
271 :
272 182916 : void PutEntry(T* aKey)
273 : {
274 182916 : PL_HashTableAdd(mOrdering, aKey, new OrderingEntry());
275 182916 : }
276 :
277 : // XXX need these helper methods because OrderingEntry doesn't have
278 : // XXX access to underlying PLHashEntry
279 :
280 : /**
281 : * Add the order |aFirst <_o aSecond|.
282 : *
283 : * WARNING: this does not check whether it's sane to add this
284 : * order. In the "best" bad case, when this order already exists,
285 : * adding it anyway may unnecessarily result in O(n^2) space. In
286 : * the "worst" bad case, adding it anyway will cause
287 : * |InTransitiveClosure()| to diverge.
288 : */
289 90755 : void AddOrder(PLHashEntry* aLT, PLHashEntry* aGT)
290 : {
291 90755 : static_cast<OrderingEntry*>(aLT->value)->mOrderedLT
292 : .InsertElementSorted(aGT);
293 90755 : }
294 :
295 : /**
296 : * Return true iff the order |aFirst < aSecond| has been
297 : * *explicitly* added.
298 : *
299 : * Does not consider transitivity.
300 : */
301 3949905 : bool IsOrdered(const PLHashEntry* aFirst, const PLHashEntry* aSecond)
302 : const
303 : {
304 : return NoIndex !=
305 : static_cast<const OrderingEntry*>(aFirst->value)->mOrderedLT
306 3949905 : .BinaryIndexOf(aSecond);
307 : }
308 :
309 : /**
310 : * Return a pointer to the array of all elements "that" for
311 : * which the order |this < that| has been explicitly added.
312 : *
313 : * NOTE: this does *not* consider transitive orderings.
314 : */
315 1184154 : PLHashEntry* const* GetOrders(const PLHashEntry* aEntry) const
316 : {
317 : return static_cast<const OrderingEntry*>(aEntry->value)->mOrderedLT
318 1184154 : .Elements();
319 : }
320 :
321 : /**
322 : * Return the number of elements "that" for which the order
323 : * |this < that| has been explicitly added.
324 : *
325 : * NOTE: this does *not* consider transitive orderings.
326 : */
327 1184154 : size_type NumOrders(const PLHashEntry* aEntry) const
328 : {
329 : return static_cast<const OrderingEntry*>(aEntry->value)->mOrderedLT
330 1184154 : .Length();
331 : }
332 :
333 : /** Make a ResourceAcquisition out of |aEntry|. */
334 0 : ResourceAcquisition MakeResourceAcquisition(const PLHashEntry* aEntry)
335 : const
336 : {
337 : return ResourceAcquisition(
338 : static_cast<const T*>(aEntry->key),
339 0 : static_cast<const OrderingEntry*>(aEntry->value)->mFirstSeen);
340 : }
341 :
342 : // Throwaway RAII lock to make the following code safer.
343 : struct PRAutoLock
344 : {
345 26874275 : PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); }
346 26875333 : ~PRAutoLock() { PR_Unlock(mLock); }
347 : PRLock* mLock;
348 : };
349 :
350 : public:
351 : static const PRUint32 kDefaultNumBuckets;
352 :
353 : /**
354 : * DeadlockDetector
355 : * Create a new deadlock detector.
356 : *
357 : * @param aNumResourcesGuess Guess at approximate number of resources
358 : * that will be checked.
359 : */
360 1446 : DeadlockDetector(PRUint32 aNumResourcesGuess = kDefaultNumBuckets)
361 : {
362 1446 : mOrdering = PL_NewHashTable(aNumResourcesGuess,
363 : HashKey,
364 : PL_CompareValues, PL_CompareValues,
365 : &kAllocOps, 0);
366 1446 : if (!mOrdering)
367 0 : NS_RUNTIMEABORT("couldn't initialize resource ordering table");
368 :
369 1446 : mLock = PR_NewLock();
370 1446 : if (!mLock)
371 0 : NS_RUNTIMEABORT("couldn't allocate deadlock detector lock");
372 1446 : }
373 :
374 : /**
375 : * ~DeadlockDetector
376 : *
377 : * *NOT* thread safe.
378 : */
379 1441 : ~DeadlockDetector()
380 : {
381 1441 : PL_HashTableDestroy(mOrdering);
382 1441 : PR_DestroyLock(mLock);
383 1441 : }
384 :
385 : /**
386 : * Add
387 : * Make the deadlock detector aware of |aResource|.
388 : *
389 : * WARNING: The deadlock detector owns |aResource|.
390 : *
391 : * Thread safe.
392 : *
393 : * @param aResource Resource to make deadlock detector aware of.
394 : */
395 182916 : void Add(T* aResource)
396 : {
397 365832 : PRAutoLock _(mLock);
398 182916 : PutEntry(aResource);
399 182916 : }
400 :
401 : // Nb: implementing a Remove() method makes the detector "more
402 : // unsound." By removing a resource from the orderings, deadlocks
403 : // may be missed that would otherwise have been found. However,
404 : // removing resources possibly reduces the # of false positives,
405 : // and additionally saves space. So it's a trade off; we have
406 : // chosen to err on the side of caution and not implement Remove().
407 :
408 : /**
409 : * CheckAcquisition This method is called after acquiring |aLast|,
410 : * but before trying to acquire |aProposed| from |aCallContext|.
411 : * It determines whether actually trying to acquire |aProposed|
412 : * will create problems. It is OK if |aLast| is NULL; this is
413 : * interpreted as |aProposed| being the thread's first acquisition
414 : * of its current chain.
415 : *
416 : * Iff acquiring |aProposed| may lead to deadlock for some thread
417 : * interleaving (including the current one!), the cyclical
418 : * dependency from which this was deduced is returned. Otherwise,
419 : * 0 is returned.
420 : *
421 : * If a potential deadlock is detected and a resource cycle is
422 : * returned, it is the *caller's* responsibility to free it.
423 : *
424 : * Thread safe.
425 : *
426 : * @param aLast Last resource acquired by calling thread (or 0).
427 : * @param aProposed Resource calling thread proposes to acquire.
428 : * @param aCallContext Calling context whence acquisiton request came.
429 : */
430 26691271 : ResourceAcquisitionArray* CheckAcquisition(const T* aLast,
431 : const T* aProposed,
432 : const CallStack& aCallContext)
433 : {
434 26691271 : NS_ASSERTION(aProposed, "null resource");
435 53383688 : PRAutoLock _(mLock);
436 :
437 26692417 : PLHashEntry* second = *GetEntry(aProposed);
438 26692417 : OrderingEntry* e = static_cast<OrderingEntry*>(second->value);
439 26692417 : if (CallStack::kNone == e->mFirstSeen)
440 151942 : e->mFirstSeen = aCallContext;
441 :
442 26692417 : if (!aLast)
443 : // don't check if |0 < proposed|; just vamoose
444 23835911 : return 0;
445 :
446 2856506 : PLHashEntry* first = *GetEntry(aLast);
447 :
448 : // this is the crux of the deadlock detector algorithm
449 :
450 2856506 : if (first == second) {
451 : // reflexive deadlock. fastpath b/c InTransitiveClosure is
452 : // not applicable here.
453 0 : ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray();
454 0 : if (!cycle)
455 0 : NS_RUNTIMEABORT("can't allocate dep. cycle array");
456 0 : cycle->AppendElement(MakeResourceAcquisition(first));
457 0 : cycle->AppendElement(ResourceAcquisition(aProposed,
458 : aCallContext));
459 0 : return cycle;
460 : }
461 2856506 : if (InTransitiveClosure(first, second)) {
462 : // we've already established |last < proposed|. all is well.
463 2765751 : return 0;
464 : }
465 90755 : if (InTransitiveClosure(second, first)) {
466 : // the order |proposed < last| has been deduced, perhaps
467 : // transitively. we're attempting to violate that
468 : // constraint by acquiring resources in the order
469 : // |last < proposed|, and thus we may deadlock under the
470 : // right conditions.
471 0 : ResourceAcquisitionArray* cycle = GetDeductionChain(second, first);
472 : // show how acquiring |proposed| would complete the cycle
473 0 : cycle->AppendElement(ResourceAcquisition(aProposed,
474 : aCallContext));
475 0 : return cycle;
476 : }
477 : // |last|, |proposed| are unordered according to our
478 : // poset. this is fine, but we now need to add this
479 : // ordering constraint.
480 90755 : AddOrder(first, second);
481 90755 : return 0;
482 : }
483 :
484 : /**
485 : * Return true iff |aTarget| is in the transitive closure of |aStart|
486 : * over the ordering relation `<_this'.
487 : *
488 : * @precondition |aStart != aTarget|
489 : */
490 3949905 : bool InTransitiveClosure(const PLHashEntry* aStart,
491 : const PLHashEntry* aTarget) const
492 : {
493 3949905 : if (IsOrdered(aStart, aTarget))
494 2765751 : return true;
495 :
496 1184154 : index_type i = 0;
497 1184154 : size_type len = NumOrders(aStart);
498 2181533 : for (const PLHashEntry* const* it = GetOrders(aStart);
499 : i < len; ++i, ++it)
500 1002644 : if (InTransitiveClosure(*it, aTarget))
501 5265 : return true;
502 1178889 : return false;
503 : }
504 :
505 : /**
506 : * Return an array of all resource acquisitions
507 : * aStart <_this r1 <_this r2 <_ ... <_ aTarget
508 : * from which |aStart <_this aTarget| was deduced, including
509 : * |aStart| and |aTarget|.
510 : *
511 : * Nb: there may be multiple deductions of |aStart <_this
512 : * aTarget|. This function returns the first ordering found by
513 : * depth-first search.
514 : *
515 : * Nb: |InTransitiveClosure| could be replaced by this function.
516 : * However, this one is more expensive because we record the DFS
517 : * search stack on the heap whereas the other doesn't.
518 : *
519 : * @precondition |aStart != aTarget|
520 : */
521 0 : ResourceAcquisitionArray* GetDeductionChain(
522 : const PLHashEntry* aStart,
523 : const PLHashEntry* aTarget)
524 : {
525 0 : ResourceAcquisitionArray* chain = new ResourceAcquisitionArray();
526 0 : if (!chain)
527 0 : NS_RUNTIMEABORT("can't allocate dep. cycle array");
528 0 : chain->AppendElement(MakeResourceAcquisition(aStart));
529 :
530 0 : NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain),
531 : "GetDeductionChain called when there's no deadlock");
532 0 : return chain;
533 : }
534 :
535 : // precondition: |aStart != aTarget|
536 : // invariant: |aStart| is the last element in |aChain|
537 0 : bool GetDeductionChain_Helper(const PLHashEntry* aStart,
538 : const PLHashEntry* aTarget,
539 : ResourceAcquisitionArray* aChain)
540 : {
541 0 : if (IsOrdered(aStart, aTarget)) {
542 0 : aChain->AppendElement(MakeResourceAcquisition(aTarget));
543 0 : return true;
544 : }
545 :
546 0 : index_type i = 0;
547 0 : size_type len = NumOrders(aStart);
548 0 : for (const PLHashEntry* const* it = GetOrders(aStart);
549 : i < len; ++i, ++it) {
550 0 : aChain->AppendElement(MakeResourceAcquisition(*it));
551 0 : if (GetDeductionChain_Helper(*it, aTarget, aChain))
552 0 : return true;
553 0 : aChain->RemoveElementAt(aChain->Length() - 1);
554 : }
555 0 : return false;
556 : }
557 :
558 : /**
559 : * The partial order on resource acquisitions used by the deadlock
560 : * detector.
561 : */
562 : PLHashTable* mOrdering; // T* -> PLHashEntry<OrderingEntry>
563 :
564 : /**
565 : * Protects contentious methods.
566 : * Nb: can't use mozilla::Mutex since we are used as its deadlock
567 : * detector.
568 : */
569 : PRLock* mLock;
570 :
571 : private:
572 : DeadlockDetector(const DeadlockDetector& aDD) MOZ_DELETE;
573 : DeadlockDetector& operator=(const DeadlockDetector& aDD) MOZ_DELETE;
574 : };
575 :
576 :
577 : template<typename T>
578 : const PLHashAllocOps DeadlockDetector<T>::kAllocOps = {
579 : DeadlockDetector<T>::TableAlloc, DeadlockDetector<T>::TableFree,
580 : DeadlockDetector<T>::EntryAlloc, DeadlockDetector<T>::EntryFree
581 : };
582 :
583 :
584 : template<typename T>
585 : // FIXME bug 456272: tune based on average workload
586 : const PRUint32 DeadlockDetector<T>::kDefaultNumBuckets = 64;
587 :
588 :
589 : } // namespace mozilla
590 :
591 : #endif // ifndef mozilla_DeadlockDetector_h
|