LCOV - code coverage report
Current view: top level - xpcom/threads - DeadlockDetector.h (source / functions) Hit Total Coverage
Test: output.info Lines: 52 96 54.2 %
Date: 2017-07-14 16:53:18 Functions: 9 14 64.3 %
Legend: Lines: hit not hit

          Line data    Source code
       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
       8             : 
       9             : #include "mozilla/Attributes.h"
      10             : 
      11             : #include <stdlib.h>
      12             : 
      13             : #include "prlock.h"
      14             : 
      15             : #include "nsClassHashtable.h"
      16             : #include "nsTArray.h"
      17             : 
      18             : namespace mozilla {
      19             : 
      20             : /**
      21             :  * DeadlockDetector
      22             :  *
      23             :  * The following is an approximate description of how the deadlock detector
      24             :  * works.
      25             :  *
      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.
      36             :  *
      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: { }
      44             :  *
      45             :  *   l2.lock()   // current chain: [ l1, l2 ]
      46             :  *               // order: { l1 <_P l2 }
      47             :  *
      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|)
      51             :  *
      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).
      56             :  *
      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).
      63             :  */
      64             : template<typename T>
      65             : class DeadlockDetector
      66             : {
      67             : public:
      68             :   typedef nsTArray<const T*> ResourceAcquisitionArray;
      69             : 
      70             : private:
      71             :   struct OrderingEntry;
      72             :   typedef nsTArray<OrderingEntry*> HashEntryArray;
      73             :   typedef typename HashEntryArray::index_type index_type;
      74             :   typedef typename HashEntryArray::size_type size_type;
      75             :   static const index_type NoIndex = HashEntryArray::NoIndex;
      76             : 
      77             :   /**
      78             :    * Value type for the ordering table.  Contains the other
      79             :    * resources on which an ordering constraint |key < other|
      80             :    * exists.  The catch is that we also store the calling context at
      81             :    * which the other resource was acquired; this improves the
      82             :    * quality of error messages when potential deadlock is detected.
      83             :    */
      84             :   struct OrderingEntry
      85             :   {
      86        1934 :     explicit OrderingEntry(const T* aResource)
      87             :       : mOrderedLT()        // FIXME bug 456272: set to empirical dep size?
      88             :       , mExternalRefs()
      89        1934 :       , mResource(aResource)
      90             :     {
      91        1934 :     }
      92         500 :     ~OrderingEntry()
      93             :     {
      94         500 :     }
      95             : 
      96             :     size_t
      97           0 :     SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
      98             :     {
      99           0 :       size_t n = aMallocSizeOf(this);
     100           0 :       n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf);
     101           0 :       n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf);
     102           0 :       return n;
     103             :     }
     104             : 
     105             :     HashEntryArray mOrderedLT; // this <_o Other
     106             :     HashEntryArray mExternalRefs; // hash entries that reference this
     107             :     const T* mResource;
     108             :   };
     109             : 
     110             :   // Throwaway RAII lock to make the following code safer.
     111             :   struct PRAutoLock
     112             :   {
     113       13270 :     explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); }
     114       13276 :     ~PRAutoLock() { PR_Unlock(mLock); }
     115             :     PRLock* mLock;
     116             :   };
     117             : 
     118             : public:
     119             :   static const uint32_t kDefaultNumBuckets;
     120             : 
     121             :   /**
     122             :    * DeadlockDetector
     123             :    * Create a new deadlock detector.
     124             :    *
     125             :    * @param aNumResourcesGuess Guess at approximate number of resources
     126             :    *        that will be checked.
     127             :    */
     128           3 :   explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets)
     129           3 :     : mOrdering(aNumResourcesGuess)
     130             :   {
     131           3 :     mLock = PR_NewLock();
     132           3 :     if (!mLock) {
     133           0 :       MOZ_CRASH("couldn't allocate deadlock detector lock");
     134             :     }
     135           3 :   }
     136             : 
     137             :   /**
     138             :    * ~DeadlockDetector
     139             :    *
     140             :    * *NOT* thread safe.
     141             :    */
     142           0 :   ~DeadlockDetector()
     143             :   {
     144           0 :     PR_DestroyLock(mLock);
     145           0 :   }
     146             : 
     147             :   size_t
     148           0 :   SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
     149             :   {
     150           0 :     size_t n = aMallocSizeOf(this);
     151             : 
     152             :     {
     153           0 :       PRAutoLock _(mLock);
     154           0 :       n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf);
     155           0 :       for (auto iter = mOrdering.ConstIter(); !iter.Done(); iter.Next()) {
     156             :         // NB: Key is accounted for in the entry.
     157           0 :         n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
     158             :       }
     159             :     }
     160             : 
     161           0 :     return n;
     162             :   }
     163             : 
     164             :   /**
     165             :    * Add
     166             :    * Make the deadlock detector aware of |aResource|.
     167             :    *
     168             :    * WARNING: The deadlock detector owns |aResource|.
     169             :    *
     170             :    * Thread safe.
     171             :    *
     172             :    * @param aResource Resource to make deadlock detector aware of.
     173             :    */
     174        1932 :   void Add(const T* aResource)
     175             :   {
     176        3866 :     PRAutoLock _(mLock);
     177        1934 :     mOrdering.Put(aResource, new OrderingEntry(aResource));
     178        1934 :   }
     179             : 
     180         500 :   void Remove(const T* aResource)
     181             :   {
     182        1000 :     PRAutoLock _(mLock);
     183             : 
     184         500 :     OrderingEntry* entry = mOrdering.Get(aResource);
     185             : 
     186             :     // Iterate the external refs and remove the entry from them.
     187         500 :     HashEntryArray& refs = entry->mExternalRefs;
     188         593 :     for (index_type i = 0; i < refs.Length(); i++) {
     189          93 :       refs[i]->mOrderedLT.RemoveElementSorted(entry);
     190             :     }
     191             : 
     192             :     // Iterate orders and remove this entry from their refs.
     193         500 :     HashEntryArray& orders = entry->mOrderedLT;
     194         985 :     for (index_type i = 0; i < orders.Length(); i++) {
     195         485 :       orders[i]->mExternalRefs.RemoveElementSorted(entry);
     196             :     }
     197             : 
     198             :     // Now the entry can be safely removed.
     199         500 :     mOrdering.Remove(aResource);
     200         500 :   }
     201             : 
     202             :   /**
     203             :    * CheckAcquisition This method is called after acquiring |aLast|,
     204             :    * but before trying to acquire |aProposed|.
     205             :    * It determines whether actually trying to acquire |aProposed|
     206             :    * will create problems.  It is OK if |aLast| is nullptr; this is
     207             :    * interpreted as |aProposed| being the thread's first acquisition
     208             :    * of its current chain.
     209             :    *
     210             :    * Iff acquiring |aProposed| may lead to deadlock for some thread
     211             :    * interleaving (including the current one!), the cyclical
     212             :    * dependency from which this was deduced is returned.  Otherwise,
     213             :    * 0 is returned.
     214             :    *
     215             :    * If a potential deadlock is detected and a resource cycle is
     216             :    * returned, it is the *caller's* responsibility to free it.
     217             :    *
     218             :    * Thread safe.
     219             :    *
     220             :    * @param aLast Last resource acquired by calling thread (or 0).
     221             :    * @param aProposed Resource calling thread proposes to acquire.
     222             :    */
     223       93722 :   ResourceAcquisitionArray* CheckAcquisition(const T* aLast,
     224             :                                              const T* aProposed)
     225             :   {
     226       93722 :     if (!aLast) {
     227             :       // don't check if |0 < aProposed|; just vamoose
     228       82884 :       return 0;
     229             :     }
     230             : 
     231       10838 :     NS_ASSERTION(aProposed, "null resource");
     232       21680 :     PRAutoLock _(mLock);
     233             : 
     234       10842 :     OrderingEntry* proposed = mOrdering.Get(aProposed);
     235       10842 :     NS_ASSERTION(proposed, "missing ordering entry");
     236             : 
     237       10842 :     OrderingEntry* current = mOrdering.Get(aLast);
     238       10842 :     NS_ASSERTION(current, "missing ordering entry");
     239             : 
     240             :     // this is the crux of the deadlock detector algorithm
     241             : 
     242       10842 :     if (current == proposed) {
     243             :       // reflexive deadlock.  fastpath b/c InTransitiveClosure is
     244             :       // not applicable here.
     245           0 :       ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray();
     246           0 :       if (!cycle) {
     247           0 :         MOZ_CRASH("can't allocate dep. cycle array");
     248             :       }
     249           0 :       cycle->AppendElement(current->mResource);
     250           0 :       cycle->AppendElement(aProposed);
     251           0 :       return cycle;
     252             :     }
     253       10842 :     if (InTransitiveClosure(current, proposed)) {
     254             :       // we've already established |aLast < aProposed|.  all is well.
     255        9641 :       return 0;
     256             :     }
     257        1201 :     if (InTransitiveClosure(proposed, current)) {
     258             :       // the order |aProposed < aLast| has been deduced, perhaps
     259             :       // transitively.  we're attempting to violate that
     260             :       // constraint by acquiring resources in the order
     261             :       // |aLast < aProposed|, and thus we may deadlock under the
     262             :       // right conditions.
     263           0 :       ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current);
     264             :       // show how acquiring |aProposed| would complete the cycle
     265           0 :       cycle->AppendElement(aProposed);
     266           0 :       return cycle;
     267             :     }
     268             :     // |aLast|, |aProposed| are unordered according to our
     269             :     // poset.  this is fine, but we now need to add this
     270             :     // ordering constraint.
     271        1201 :     current->mOrderedLT.InsertElementSorted(proposed);
     272        1201 :     proposed->mExternalRefs.InsertElementSorted(current);
     273        1201 :     return 0;
     274             :   }
     275             : 
     276             :   /**
     277             :    * Return true iff |aTarget| is in the transitive closure of |aStart|
     278             :    * over the ordering relation `<_this'.
     279             :    *
     280             :    * @precondition |aStart != aTarget|
     281             :    */
     282       25343 :   bool InTransitiveClosure(const OrderingEntry* aStart,
     283             :                            const OrderingEntry* aTarget) const
     284             :   {
     285             :     // NB: Using a static comparator rather than default constructing one shows
     286             :     //     a 9% improvement in scalability tests on some systems.
     287             :     static nsDefaultComparator<const OrderingEntry*, const OrderingEntry*> comp;
     288       25343 :     if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) {
     289        9641 :       return true;
     290             :     }
     291             : 
     292       15702 :     index_type i = 0;
     293       15702 :     size_type len = aStart->mOrderedLT.Length();
     294       28899 :     for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) {
     295       13300 :       if (InTransitiveClosure(*it, aTarget)) {
     296         103 :         return true;
     297             :       }
     298             :     }
     299       15599 :     return false;
     300             :   }
     301             : 
     302             :   /**
     303             :    * Return an array of all resource acquisitions
     304             :    *   aStart <_this r1 <_this r2 <_ ... <_ aTarget
     305             :    * from which |aStart <_this aTarget| was deduced, including
     306             :    * |aStart| and |aTarget|.
     307             :    *
     308             :    * Nb: there may be multiple deductions of |aStart <_this
     309             :    * aTarget|.  This function returns the first ordering found by
     310             :    * depth-first search.
     311             :    *
     312             :    * Nb: |InTransitiveClosure| could be replaced by this function.
     313             :    * However, this one is more expensive because we record the DFS
     314             :    * search stack on the heap whereas the other doesn't.
     315             :    *
     316             :    * @precondition |aStart != aTarget|
     317             :    */
     318           0 :   ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart,
     319             :                                               const OrderingEntry* aTarget)
     320             :   {
     321           0 :     ResourceAcquisitionArray* chain = new ResourceAcquisitionArray();
     322           0 :     if (!chain) {
     323           0 :       MOZ_CRASH("can't allocate dep. cycle array");
     324             :     }
     325           0 :     chain->AppendElement(aStart->mResource);
     326             : 
     327           0 :     NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain),
     328             :                  "GetDeductionChain called when there's no deadlock");
     329           0 :     return chain;
     330             :   }
     331             : 
     332             :   // precondition: |aStart != aTarget|
     333             :   // invariant: |aStart| is the last element in |aChain|
     334           0 :   bool GetDeductionChain_Helper(const OrderingEntry* aStart,
     335             :                                 const OrderingEntry* aTarget,
     336             :                                 ResourceAcquisitionArray* aChain)
     337             :   {
     338           0 :     if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) {
     339           0 :       aChain->AppendElement(aTarget->mResource);
     340           0 :       return true;
     341             :     }
     342             : 
     343           0 :     index_type i = 0;
     344           0 :     size_type len = aStart->mOrderedLT.Length();
     345           0 :     for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) {
     346           0 :       aChain->AppendElement((*it)->mResource);
     347           0 :       if (GetDeductionChain_Helper(*it, aTarget, aChain)) {
     348           0 :         return true;
     349             :       }
     350           0 :       aChain->RemoveElementAt(aChain->Length() - 1);
     351             :     }
     352           0 :     return false;
     353             :   }
     354             : 
     355             :   /**
     356             :    * The partial order on resource acquisitions used by the deadlock
     357             :    * detector.
     358             :    */
     359             :   nsClassHashtable<nsPtrHashKey<const T>, OrderingEntry> mOrdering;
     360             : 
     361             : 
     362             :   /**
     363             :    * Protects contentious methods.
     364             :    * Nb: can't use mozilla::Mutex since we are used as its deadlock
     365             :    * detector.
     366             :    */
     367             :   PRLock* mLock;
     368             : 
     369             : private:
     370             :   DeadlockDetector(const DeadlockDetector& aDD) = delete;
     371             :   DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete;
     372             : };
     373             : 
     374             : 
     375             : template<typename T>
     376             : // FIXME bug 456272: tune based on average workload
     377             : const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 32;
     378             : 
     379             : 
     380             : } // namespace mozilla
     381             : 
     382             : #endif // ifndef mozilla_DeadlockDetector_h

Generated by: LCOV version 1.13