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