1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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
9 #include "mozilla/Attributes.h"
15 #include "nsClassHashtable.h"
23 * The following is an approximate description of how the deadlock detector
26 * The deadlock detector ensures that all blocking resources are
27 * acquired according to a partial order P. One type of blocking
28 * resource is a lock. If a lock l1 is acquired (locked) before l2,
29 * then we say that |l1 <_P l2|. The detector flags an error if two
30 * locks l1 and l2 have an inconsistent ordering in P; that is, if
31 * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error
32 * because a thread acquiring l1,l2 according to the first order might
33 * race with a thread acquiring them according to the second order.
34 * If this happens under the right conditions, then the acquisitions
37 * This deadlock detector doesn't know at compile-time what P is. So,
38 * it tries to discover the order at run time. More precisely, it
39 * finds <i>some</i> order P, then tries to find chains of resource
40 * acquisitions that violate P. An example acquisition sequence, and
41 * the orders they impose, is
42 * l1.lock() // current chain: [ l1 ]
45 * l2.lock() // current chain: [ l1, l2 ]
46 * // order: { l1 <_P l2 }
48 * l3.lock() // current chain: [ l1, l2, l3 ]
49 * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
50 * // (note: <_P is transitive, so also |l1 <_P l3|)
52 * l2.unlock() // current chain: [ l1, l3 ]
53 * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
54 * // (note: it's OK, but weird, that l2 was unlocked out
55 * // of order. we still have l1 <_P l3).
57 * l2.lock() // current chain: [ l1, l3, l2 ]
58 * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3,
60 * BEEP BEEP! Here the detector will flag a potential error, since
61 * l2 and l3 were used inconsistently (and potentially in ways that
65 class DeadlockDetector
{
67 typedef nsTArray
<const T
*> ResourceAcquisitionArray
;
71 typedef nsTArray
<OrderingEntry
*> HashEntryArray
;
72 typedef typename
HashEntryArray::index_type index_type
;
73 typedef typename
HashEntryArray::size_type size_type
;
74 static const index_type NoIndex
= HashEntryArray::NoIndex
;
77 * Value type for the ordering table. Contains the other
78 * resources on which an ordering constraint |key < other|
79 * exists. The catch is that we also store the calling context at
80 * which the other resource was acquired; this improves the
81 * quality of error messages when potential deadlock is detected.
83 struct OrderingEntry
{
84 explicit OrderingEntry(const T
* aResource
)
85 : mOrderedLT() // FIXME bug 456272: set to empirical dep size?
88 mResource(aResource
) {}
91 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
92 size_t n
= aMallocSizeOf(this);
93 n
+= mOrderedLT
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
94 n
+= mExternalRefs
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
98 HashEntryArray mOrderedLT
; // this <_o Other
99 HashEntryArray mExternalRefs
; // hash entries that reference this
103 // Throwaway RAII lock to make the following code safer.
105 explicit PRAutoLock(PRLock
* aLock
) : mLock(aLock
) { PR_Lock(mLock
); }
106 ~PRAutoLock() { PR_Unlock(mLock
); }
111 static const uint32_t kDefaultNumBuckets
;
115 * Create a new deadlock detector.
117 * @param aNumResourcesGuess Guess at approximate number of resources
118 * that will be checked.
120 explicit DeadlockDetector(uint32_t aNumResourcesGuess
= kDefaultNumBuckets
)
121 : mOrdering(aNumResourcesGuess
) {
122 mLock
= PR_NewLock();
124 MOZ_CRASH("couldn't allocate deadlock detector lock");
133 ~DeadlockDetector() { PR_DestroyLock(mLock
); }
135 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
136 size_t n
= aMallocSizeOf(this);
140 n
+= mOrdering
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
141 for (const auto& data
: mOrdering
.Values()) {
142 // NB: Key is accounted for in the entry.
143 n
+= data
->SizeOfIncludingThis(aMallocSizeOf
);
152 * Make the deadlock detector aware of |aResource|.
154 * WARNING: The deadlock detector owns |aResource|.
158 * @param aResource Resource to make deadlock detector aware of.
160 void Add(const T
* aResource
) {
162 mOrdering
.InsertOrUpdate(aResource
, MakeUnique
<OrderingEntry
>(aResource
));
165 void Remove(const T
* aResource
) {
168 OrderingEntry
* entry
= mOrdering
.Get(aResource
);
170 // Iterate the external refs and remove the entry from them.
171 HashEntryArray
& refs
= entry
->mExternalRefs
;
172 for (index_type i
= 0; i
< refs
.Length(); i
++) {
173 refs
[i
]->mOrderedLT
.RemoveElementSorted(entry
);
176 // Iterate orders and remove this entry from their refs.
177 HashEntryArray
& orders
= entry
->mOrderedLT
;
178 for (index_type i
= 0; i
< orders
.Length(); i
++) {
179 orders
[i
]->mExternalRefs
.RemoveElementSorted(entry
);
182 // Now the entry can be safely removed.
183 mOrdering
.Remove(aResource
);
187 * CheckAcquisition This method is called after acquiring |aLast|,
188 * but before trying to acquire |aProposed|.
189 * It determines whether actually trying to acquire |aProposed|
190 * will create problems. It is OK if |aLast| is nullptr; this is
191 * interpreted as |aProposed| being the thread's first acquisition
192 * of its current chain.
194 * Iff acquiring |aProposed| may lead to deadlock for some thread
195 * interleaving (including the current one!), the cyclical
196 * dependency from which this was deduced is returned. Otherwise,
199 * If a potential deadlock is detected and a resource cycle is
200 * returned, it is the *caller's* responsibility to free it.
204 * @param aLast Last resource acquired by calling thread (or 0).
205 * @param aProposed Resource calling thread proposes to acquire.
207 ResourceAcquisitionArray
* CheckAcquisition(const T
* aLast
,
208 const T
* aProposed
) {
210 // don't check if |0 < aProposed|; just vamoose
214 NS_ASSERTION(aProposed
, "null resource");
217 OrderingEntry
* proposed
= mOrdering
.Get(aProposed
);
218 NS_ASSERTION(proposed
, "missing ordering entry");
220 OrderingEntry
* current
= mOrdering
.Get(aLast
);
221 NS_ASSERTION(current
, "missing ordering entry");
223 // this is the crux of the deadlock detector algorithm
225 if (current
== proposed
) {
226 // reflexive deadlock. fastpath b/c InTransitiveClosure is
227 // not applicable here.
228 ResourceAcquisitionArray
* cycle
= new ResourceAcquisitionArray();
230 MOZ_CRASH("can't allocate dep. cycle array");
232 cycle
->AppendElement(current
->mResource
);
233 cycle
->AppendElement(aProposed
);
236 if (InTransitiveClosure(current
, proposed
)) {
237 // we've already established |aLast < aProposed|. all is well.
240 if (InTransitiveClosure(proposed
, current
)) {
241 // the order |aProposed < aLast| has been deduced, perhaps
242 // transitively. we're attempting to violate that
243 // constraint by acquiring resources in the order
244 // |aLast < aProposed|, and thus we may deadlock under the
246 ResourceAcquisitionArray
* cycle
= GetDeductionChain(proposed
, current
);
247 // show how acquiring |aProposed| would complete the cycle
248 cycle
->AppendElement(aProposed
);
251 // |aLast|, |aProposed| are unordered according to our
252 // poset. this is fine, but we now need to add this
253 // ordering constraint.
254 current
->mOrderedLT
.InsertElementSorted(proposed
);
255 proposed
->mExternalRefs
.InsertElementSorted(current
);
260 * Return true iff |aTarget| is in the transitive closure of |aStart|
261 * over the ordering relation `<_this'.
263 * @precondition |aStart != aTarget|
265 bool InTransitiveClosure(const OrderingEntry
* aStart
,
266 const OrderingEntry
* aTarget
) const {
267 // NB: Using a static comparator rather than default constructing one shows
268 // a 9% improvement in scalability tests on some systems.
269 static nsDefaultComparator
<const OrderingEntry
*, const OrderingEntry
*> comp
;
270 if (aStart
->mOrderedLT
.BinaryIndexOf(aTarget
, comp
) != NoIndex
) {
275 size_type len
= aStart
->mOrderedLT
.Length();
276 for (auto it
= aStart
->mOrderedLT
.Elements(); i
< len
; ++i
, ++it
) {
277 if (InTransitiveClosure(*it
, aTarget
)) {
285 * Return an array of all resource acquisitions
286 * aStart <_this r1 <_this r2 <_ ... <_ aTarget
287 * from which |aStart <_this aTarget| was deduced, including
288 * |aStart| and |aTarget|.
290 * Nb: there may be multiple deductions of |aStart <_this
291 * aTarget|. This function returns the first ordering found by
292 * depth-first search.
294 * Nb: |InTransitiveClosure| could be replaced by this function.
295 * However, this one is more expensive because we record the DFS
296 * search stack on the heap whereas the other doesn't.
298 * @precondition |aStart != aTarget|
300 ResourceAcquisitionArray
* GetDeductionChain(const OrderingEntry
* aStart
,
301 const OrderingEntry
* aTarget
) {
302 ResourceAcquisitionArray
* chain
= new ResourceAcquisitionArray();
304 MOZ_CRASH("can't allocate dep. cycle array");
306 chain
->AppendElement(aStart
->mResource
);
308 NS_ASSERTION(GetDeductionChain_Helper(aStart
, aTarget
, chain
),
309 "GetDeductionChain called when there's no deadlock");
313 // precondition: |aStart != aTarget|
314 // invariant: |aStart| is the last element in |aChain|
315 bool GetDeductionChain_Helper(const OrderingEntry
* aStart
,
316 const OrderingEntry
* aTarget
,
317 ResourceAcquisitionArray
* aChain
) {
318 if (aStart
->mOrderedLT
.BinaryIndexOf(aTarget
) != NoIndex
) {
319 aChain
->AppendElement(aTarget
->mResource
);
324 size_type len
= aStart
->mOrderedLT
.Length();
325 for (auto it
= aStart
->mOrderedLT
.Elements(); i
< len
; ++i
, ++it
) {
326 aChain
->AppendElement((*it
)->mResource
);
327 if (GetDeductionChain_Helper(*it
, aTarget
, aChain
)) {
330 aChain
->RemoveLastElement();
336 * The partial order on resource acquisitions used by the deadlock
339 nsClassHashtable
<nsPtrHashKey
<const T
>, OrderingEntry
> mOrdering
;
342 * Protects contentious methods.
343 * Nb: can't use mozilla::Mutex since we are used as its deadlock
349 DeadlockDetector(const DeadlockDetector
& aDD
) = delete;
350 DeadlockDetector
& operator=(const DeadlockDetector
& aDD
) = delete;
353 template <typename T
>
354 // FIXME bug 456272: tune based on average workload
355 const uint32_t DeadlockDetector
<T
>::kDefaultNumBuckets
= 32;
357 } // namespace mozilla
359 #endif // ifndef mozilla_DeadlockDetector_h