Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / threads / DeadlockDetector.h
blob5c40941328dc7e4e6dde391248c549288431c947
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"
11 #include <stdlib.h>
13 #include "prlock.h"
15 #include "nsClassHashtable.h"
16 #include "nsTArray.h"
18 namespace mozilla {
20 /**
21 * DeadlockDetector
23 * The following is an approximate description of how the deadlock detector
24 * works.
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
35 * will deadlock.
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 ]
43 * // order: { }
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,
59 * l3 <_P l2 (!!!) }
60 * BEEP BEEP! Here the detector will flag a potential error, since
61 * l2 and l3 were used inconsistently (and potentially in ways that
62 * would deadlock).
64 template <typename T>
65 class DeadlockDetector {
66 public:
67 typedef nsTArray<const T*> ResourceAcquisitionArray;
69 private:
70 struct OrderingEntry;
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;
76 /**
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?
87 mExternalRefs(),
88 mResource(aResource) {}
89 ~OrderingEntry() {}
91 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
92 size_t n = aMallocSizeOf(this);
93 n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf);
94 n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf);
95 return n;
98 HashEntryArray mOrderedLT; // this <_o Other
99 HashEntryArray mExternalRefs; // hash entries that reference this
100 const T* mResource;
103 // Throwaway RAII lock to make the following code safer.
104 struct PRAutoLock {
105 explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); }
106 ~PRAutoLock() { PR_Unlock(mLock); }
107 PRLock* mLock;
110 public:
111 static const uint32_t kDefaultNumBuckets;
114 * DeadlockDetector
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();
123 if (!mLock) {
124 MOZ_CRASH("couldn't allocate deadlock detector lock");
129 * ~DeadlockDetector
131 * *NOT* thread safe.
133 ~DeadlockDetector() { PR_DestroyLock(mLock); }
135 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
136 size_t n = aMallocSizeOf(this);
139 PRAutoLock _(mLock);
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);
147 return n;
151 * Add
152 * Make the deadlock detector aware of |aResource|.
154 * WARNING: The deadlock detector owns |aResource|.
156 * Thread safe.
158 * @param aResource Resource to make deadlock detector aware of.
160 void Add(const T* aResource) {
161 PRAutoLock _(mLock);
162 mOrdering.InsertOrUpdate(aResource, MakeUnique<OrderingEntry>(aResource));
165 void Remove(const T* aResource) {
166 PRAutoLock _(mLock);
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,
197 * 0 is returned.
199 * If a potential deadlock is detected and a resource cycle is
200 * returned, it is the *caller's* responsibility to free it.
202 * Thread safe.
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) {
209 if (!aLast) {
210 // don't check if |0 < aProposed|; just vamoose
211 return 0;
214 NS_ASSERTION(aProposed, "null resource");
215 PRAutoLock _(mLock);
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();
229 if (!cycle) {
230 MOZ_CRASH("can't allocate dep. cycle array");
232 cycle->AppendElement(current->mResource);
233 cycle->AppendElement(aProposed);
234 return cycle;
236 if (InTransitiveClosure(current, proposed)) {
237 // we've already established |aLast < aProposed|. all is well.
238 return 0;
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
245 // right conditions.
246 ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current);
247 // show how acquiring |aProposed| would complete the cycle
248 cycle->AppendElement(aProposed);
249 return cycle;
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);
256 return 0;
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) {
271 return true;
274 index_type i = 0;
275 size_type len = aStart->mOrderedLT.Length();
276 for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) {
277 if (InTransitiveClosure(*it, aTarget)) {
278 return true;
281 return false;
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();
303 if (!chain) {
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");
310 return chain;
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);
320 return true;
323 index_type i = 0;
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)) {
328 return true;
330 aChain->RemoveLastElement();
332 return false;
336 * The partial order on resource acquisitions used by the deadlock
337 * detector.
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
344 * detector.
346 PRLock* mLock;
348 private:
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