xpcom/glue/DeadlockDetector.h

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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
     9 #include "mozilla/Attributes.h"
    11 #include <stdlib.h>
    13 #include "plhash.h"
    14 #include "prlock.h"
    16 #include "nsTArray.h"
    18 #ifdef NS_TRACE_MALLOC
    19 #  include "nsTraceMalloc.h"
    20 #endif  // ifdef NS_TRACE_MALLOC
    22 namespace mozilla {
    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
    41     callstack_id mCallStack;
    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     }
    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     }
    94     /** The "null" callstack. */
    95     static const CallStack kNone;
    96 };
    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;
   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;
   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     };
   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         }
   206         CallStack mFirstSeen; // first site from which the resource appeared
   207         HashEntryArray mOrderedLT; // this <_o Other
   208     };
   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;
   236     // Hash table "interface" the rest of the code should use
   238     PLHashEntry** GetEntry(const T* aKey)
   239     {
   240         return PL_HashTableRawLookup(mOrdering, HashKey(aKey), aKey);
   241     }
   243     void PutEntry(T* aKey)
   244     {
   245         PL_HashTableAdd(mOrdering, aKey, new OrderingEntry());
   246     }
   248     // XXX need these helper methods because OrderingEntry doesn't have
   249     // XXX access to underlying PLHashEntry
   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     }
   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     }
   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     }
   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     }
   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     }
   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     };
   321 public:
   322     static const uint32_t kDefaultNumBuckets;
   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");
   340         mLock = PR_NewLock();
   341         if (!mLock)
   342             NS_RUNTIMEABORT("couldn't allocate deadlock detector lock");
   343     }
   345     /**
   346      * ~DeadlockDetector
   347      *
   348      * *NOT* thread safe.
   349      */
   350     ~DeadlockDetector()
   351     {
   352         PL_HashTableDestroy(mOrdering);
   353         PR_DestroyLock(mLock);
   354     }
   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     }
   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().
   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);
   408         PLHashEntry* second = *GetEntry(aProposed);
   409         OrderingEntry* e = static_cast<OrderingEntry*>(second->value);
   410         if (CallStack::kNone == e->mFirstSeen)
   411             e->mFirstSeen = aCallContext;
   413         if (!aLast)
   414             // don't check if |0 < proposed|; just vamoose
   415             return 0;
   417         PLHashEntry* first = *GetEntry(aLast);
   419         // this is the crux of the deadlock detector algorithm
   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     }
   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;
   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     }
   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));
   501         NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain),
   502                      "GetDeductionChain called when there's no deadlock");
   503         return chain;
   504     }
   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         }
   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     }
   529     /**
   530      * The partial order on resource acquisitions used by the deadlock
   531      * detector.
   532      */
   533     PLHashTable* mOrdering;     // T* -> PLHashEntry<OrderingEntry>
   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;
   542 private:
   543     DeadlockDetector(const DeadlockDetector& aDD) MOZ_DELETE;
   544     DeadlockDetector& operator=(const DeadlockDetector& aDD) MOZ_DELETE;
   545 };
   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 };
   555 template<typename T>
   556 // FIXME bug 456272: tune based on average workload
   557 const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 64;
   560 } // namespace mozilla
   562 #endif // ifndef mozilla_DeadlockDetector_h

mercurial