/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_DeadlockDetector_h
#define mozilla_DeadlockDetector_h

#include "mozilla/Attributes.h"

#include <stdlib.h>

#include "plhash.h"
#include "prlock.h"

#include "nsTArray.h"

#ifdef NS_TRACE_MALLOC
#  include "nsTraceMalloc.h"
#endif  // ifdef NS_TRACE_MALLOC

namespace mozilla {


// FIXME bug 456272: split this off into a convenience API on top of
// nsStackWalk?
class NS_COM_GLUE CallStack
{
private:
#ifdef NS_TRACE_MALLOC
  typedef nsTMStackTraceID callstack_id;
  // needs to be a macro to avoid disturbing the backtrace
#   define NS_GET_BACKTRACE() NS_TraceMallocGetStackTrace()
#   define NS_DEADLOCK_DETECTOR_CONSTEXPR
#else
  typedef void* callstack_id;
#   define NS_GET_BACKTRACE() 0
#   define NS_DEADLOCK_DETECTOR_CONSTEXPR MOZ_CONSTEXPR
#endif  // ifdef NS_TRACE_MALLOC

  callstack_id mCallStack;

public:
  /**
   * CallStack
   * *ALWAYS* *ALWAYS* *ALWAYS* call this with no arguments.  This
   * constructor takes an argument *ONLY* so that |GET_BACKTRACE()|
   * can be evaluated in the stack frame of the caller, rather than
   * that of the constructor.
   *
   * *BEWARE*: this means that calling this constructor with no
   * arguments is not the same as a "default, do-nothing"
   * constructor: it *will* construct a backtrace.  This can cause
   * unexpected performance issues.
   */
  NS_DEADLOCK_DETECTOR_CONSTEXPR
  CallStack(const callstack_id aCallStack = NS_GET_BACKTRACE())
    : mCallStack(aCallStack)
  {
  }
  NS_DEADLOCK_DETECTOR_CONSTEXPR
  CallStack(const CallStack& aFrom)
    : mCallStack(aFrom.mCallStack)
  {
  }
  CallStack& operator=(const CallStack& aFrom)
  {
    mCallStack = aFrom.mCallStack;
    return *this;
  }
  bool operator==(const CallStack& aOther) const
  {
    return mCallStack == aOther.mCallStack;
  }
  bool operator!=(const CallStack& aOther) const
  {
    return mCallStack != aOther.mCallStack;
  }

  // FIXME bug 456272: if this is split off,
  // NS_TraceMallocPrintStackTrace should be modified to print into
  // an nsACString
  void Print(FILE* aFile) const
  {
#ifdef NS_TRACE_MALLOC
    if (this != &kNone && mCallStack) {
      NS_TraceMallocPrintStackTrace(aFile, mCallStack);
      return;
    }
#endif
    fputs("  [stack trace unavailable]\n", aFile);
  }

  /** The "null" callstack. */
  static const CallStack kNone;
};


/**
 * DeadlockDetector
 *
 * The following is an approximate description of how the deadlock detector
 * works.
 *
 * The deadlock detector ensures that all blocking resources are
 * acquired according to a partial order P.  One type of blocking
 * resource is a lock.  If a lock l1 is acquired (locked) before l2,
 * then we say that |l1 <_P l2|.  The detector flags an error if two
 * locks l1 and l2 have an inconsistent ordering in P; that is, if
 * both |l1 <_P l2| and |l2 <_P l1|.  This is a potential error
 * because a thread acquiring l1,l2 according to the first order might
 * race with a thread acquiring them according to the second order.
 * If this happens under the right conditions, then the acquisitions
 * will deadlock.
 *
 * This deadlock detector doesn't know at compile-time what P is.  So,
 * it tries to discover the order at run time.  More precisely, it
 * finds <i>some</i> order P, then tries to find chains of resource
 * acquisitions that violate P.  An example acquisition sequence, and
 * the orders they impose, is
 *   l1.lock()   // current chain: [ l1 ]
 *               // order: { }
 *
 *   l2.lock()   // current chain: [ l1, l2 ]
 *               // order: { l1 <_P l2 }
 *
 *   l3.lock()   // current chain: [ l1, l2, l3 ]
 *               // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
 *               // (note: <_P is transitive, so also |l1 <_P l3|)
 *
 *   l2.unlock() // current chain: [ l1, l3 ]
 *               // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
 *               // (note: it's OK, but weird, that l2 was unlocked out
 *               //  of order.  we still have l1 <_P l3).
 *
 *   l2.lock()   // current chain: [ l1, l3, l2 ]
 *               // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3,
 *                                      l3 <_P l2 (!!!) }
 * BEEP BEEP!  Here the detector will flag a potential error, since
 * l2 and l3 were used inconsistently (and potentially in ways that
 * would deadlock).
 */
template<typename T>
class DeadlockDetector
{
public:
  /**
   * ResourceAcquisition
   * Consists simply of a resource and the calling context from
   * which it was acquired.  We pack this information together so
   * that it can be returned back to the caller when a potential
   * deadlock has been found.
   */
  struct ResourceAcquisition
  {
    const T* mResource;
    CallStack mCallContext;

    ResourceAcquisition(const T* aResource,
                        const CallStack aCallContext = CallStack::kNone)
      : mResource(aResource)
      , mCallContext(aCallContext)
    {
    }
    ResourceAcquisition(const ResourceAcquisition& aFrom)
      : mResource(aFrom.mResource)
      , mCallContext(aFrom.mCallContext)
    {
    }
    ResourceAcquisition& operator=(const ResourceAcquisition& aFrom)
    {
      mResource = aFrom.mResource;
      mCallContext = aFrom.mCallContext;
      return *this;
    }
  };
  typedef nsTArray<ResourceAcquisition> ResourceAcquisitionArray;

private:
  typedef nsTArray<PLHashEntry*> HashEntryArray;
  typedef typename HashEntryArray::index_type index_type;
  typedef typename HashEntryArray::size_type size_type;
  static const HashEntryArray::index_type NoIndex = HashEntryArray::NoIndex;

  /**
   * Value type for the ordering table.  Contains the other
   * resources on which an ordering constraint |key < other|
   * exists.  The catch is that we also store the calling context at
   * which the other resource was acquired; this improves the
   * quality of error messages when potential deadlock is detected.
   */
  struct OrderingEntry
  {
    OrderingEntry()
      : mFirstSeen(CallStack::kNone)
      , mOrderedLT()        // FIXME bug 456272: set to empirical dep size?
    {
    }
    ~OrderingEntry()
    {
    }

    CallStack mFirstSeen; // first site from which the resource appeared
    HashEntryArray mOrderedLT; // this <_o Other
  };

  static void* TableAlloc(void* /*aPool*/, size_t aSize)
  {
    return operator new(aSize);
  }
  static void TableFree(void* /*aPool*/, void* aItem)
  {
    operator delete(aItem);
  }
  static PLHashEntry* EntryAlloc(void* /*aPool*/, const void* aKey)
  {
    return new PLHashEntry;
  }
  static void EntryFree(void* /*aPool*/, PLHashEntry* aEntry, unsigned aFlag)
  {
    delete static_cast<T*>(const_cast<void*>(aEntry->key));
    delete static_cast<OrderingEntry*>(aEntry->value);
    aEntry->value = 0;
    if (aFlag == HT_FREE_ENTRY) {
      delete aEntry;
    }
  }
  static PLHashNumber HashKey(const void* aKey)
  {
    return static_cast<PLHashNumber>(NS_PTR_TO_INT32(aKey) >> 2);
  }
  static const PLHashAllocOps kAllocOps;

  // Hash table "interface" the rest of the code should use

  PLHashEntry** GetEntry(const T* aKey)
  {
    return PL_HashTableRawLookup(mOrdering, HashKey(aKey), aKey);
  }

  void PutEntry(T* aKey)
  {
    PL_HashTableAdd(mOrdering, aKey, new OrderingEntry());
  }

  // XXX need these helper methods because OrderingEntry doesn't have
  // XXX access to underlying PLHashEntry

  /**
   * Add the order |aFirst <_o aSecond|.
   *
   * WARNING: this does not check whether it's sane to add this
   * order.  In the "best" bad case, when this order already exists,
   * adding it anyway may unnecessarily result in O(n^2) space.  In
   * the "worst" bad case, adding it anyway will cause
   * |InTransitiveClosure()| to diverge.
   */
  void AddOrder(PLHashEntry* aLT, PLHashEntry* aGT)
  {
    static_cast<OrderingEntry*>(aLT->value)->mOrderedLT
      .InsertElementSorted(aGT);
  }

  /**
   * Return true iff the order |aFirst < aSecond| has been
   * *explicitly* added.
   *
   * Does not consider transitivity.
   */
  bool IsOrdered(const PLHashEntry* aFirst, const PLHashEntry* aSecond)
  const
  {
    const OrderingEntry* entry =
      static_cast<const OrderingEntry*>(aFirst->value);
    return entry->mOrderedLT.BinaryIndexOf(aSecond) != NoIndex;
  }

  /**
   * Return a pointer to the array of all elements "that" for
   * which the order |this < that| has been explicitly added.
   *
   * NOTE: this does *not* consider transitive orderings.
   */
  PLHashEntry* const* GetOrders(const PLHashEntry* aEntry) const
  {
    return
      static_cast<const OrderingEntry*>(aEntry->value)->mOrderedLT.Elements();
  }

  /**
   * Return the number of elements "that" for which the order
   * |this < that| has been explicitly added.
   *
   * NOTE: this does *not* consider transitive orderings.
   */
  size_type NumOrders(const PLHashEntry* aEntry) const
  {
    return
      static_cast<const OrderingEntry*>(aEntry->value)->mOrderedLT.Length();
  }

  /** Make a ResourceAcquisition out of |aEntry|. */
  ResourceAcquisition MakeResourceAcquisition(const PLHashEntry* aEntry) const
  {
    return ResourceAcquisition(
      static_cast<const T*>(aEntry->key),
      static_cast<const OrderingEntry*>(aEntry->value)->mFirstSeen);
  }

  // Throwaway RAII lock to make the following code safer.
  struct PRAutoLock
  {
    PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); }
    ~PRAutoLock() { PR_Unlock(mLock); }
    PRLock* mLock;
  };

public:
  static const uint32_t kDefaultNumBuckets;

  /**
   * DeadlockDetector
   * Create a new deadlock detector.
   *
   * @param aNumResourcesGuess Guess at approximate number of resources
   *        that will be checked.
   */
  DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets)
  {
    mOrdering = PL_NewHashTable(aNumResourcesGuess,
                                HashKey,
                                PL_CompareValues, PL_CompareValues,
                                &kAllocOps, 0);
    if (!mOrdering) {
      NS_RUNTIMEABORT("couldn't initialize resource ordering table");
    }

    mLock = PR_NewLock();
    if (!mLock) {
      NS_RUNTIMEABORT("couldn't allocate deadlock detector lock");
    }
  }

  /**
   * ~DeadlockDetector
   *
   * *NOT* thread safe.
   */
  ~DeadlockDetector()
  {
    PL_HashTableDestroy(mOrdering);
    PR_DestroyLock(mLock);
  }

  /**
   * Add
   * Make the deadlock detector aware of |aResource|.
   *
   * WARNING: The deadlock detector owns |aResource|.
   *
   * Thread safe.
   *
   * @param aResource Resource to make deadlock detector aware of.
   */
  void Add(T* aResource)
  {
    PRAutoLock _(mLock);
    PutEntry(aResource);
  }

  // Nb: implementing a Remove() method makes the detector "more
  // unsound."  By removing a resource from the orderings, deadlocks
  // may be missed that would otherwise have been found.  However,
  // removing resources possibly reduces the # of false positives,
  // and additionally saves space.  So it's a trade off; we have
  // chosen to err on the side of caution and not implement Remove().

  /**
   * CheckAcquisition This method is called after acquiring |aLast|,
   * but before trying to acquire |aProposed| from |aCallContext|.
   * It determines whether actually trying to acquire |aProposed|
   * will create problems.  It is OK if |aLast| is nullptr; this is
   * interpreted as |aProposed| being the thread's first acquisition
   * of its current chain.
   *
   * Iff acquiring |aProposed| may lead to deadlock for some thread
   * interleaving (including the current one!), the cyclical
   * dependency from which this was deduced is returned.  Otherwise,
   * 0 is returned.
   *
   * If a potential deadlock is detected and a resource cycle is
   * returned, it is the *caller's* responsibility to free it.
   *
   * Thread safe.
   *
   * @param aLast Last resource acquired by calling thread (or 0).
   * @param aProposed Resource calling thread proposes to acquire.
   * @param aCallContext Calling context whence acquisiton request came.
   */
  ResourceAcquisitionArray* CheckAcquisition(const T* aLast,
                                             const T* aProposed,
                                             const CallStack& aCallContext)
  {
    NS_ASSERTION(aProposed, "null resource");
    PRAutoLock _(mLock);

    PLHashEntry* second = *GetEntry(aProposed);
    OrderingEntry* e = static_cast<OrderingEntry*>(second->value);
    if (CallStack::kNone == e->mFirstSeen) {
      e->mFirstSeen = aCallContext;
    }

    if (!aLast) {
      // don't check if |0 < aProposed|; just vamoose
      return 0;
    }

    PLHashEntry* first = *GetEntry(aLast);

    // this is the crux of the deadlock detector algorithm

    if (first == second) {
      // reflexive deadlock.  fastpath b/c InTransitiveClosure is
      // not applicable here.
      ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray();
      if (!cycle) {
        NS_RUNTIMEABORT("can't allocate dep. cycle array");
      }
      cycle->AppendElement(MakeResourceAcquisition(first));
      cycle->AppendElement(ResourceAcquisition(aProposed,
                                               aCallContext));
      return cycle;
    }
    if (InTransitiveClosure(first, second)) {
      // we've already established |aLast < aProposed|.  all is well.
      return 0;
    }
    if (InTransitiveClosure(second, first)) {
      // the order |aProposed < aLast| has been deduced, perhaps
      // transitively.  we're attempting to violate that
      // constraint by acquiring resources in the order
      // |aLast < aProposed|, and thus we may deadlock under the
      // right conditions.
      ResourceAcquisitionArray* cycle = GetDeductionChain(second, first);
      // show how acquiring |aProposed| would complete the cycle
      cycle->AppendElement(ResourceAcquisition(aProposed,
                                               aCallContext));
      return cycle;
    }
    // |aLast|, |aProposed| are unordered according to our
    // poset.  this is fine, but we now need to add this
    // ordering constraint.
    AddOrder(first, second);
    return 0;
  }

  /**
   * Return true iff |aTarget| is in the transitive closure of |aStart|
   * over the ordering relation `<_this'.
   *
   * @precondition |aStart != aTarget|
   */
  bool InTransitiveClosure(const PLHashEntry* aStart,
                           const PLHashEntry* aTarget) const
  {
    if (IsOrdered(aStart, aTarget)) {
      return true;
    }

    index_type i = 0;
    size_type len = NumOrders(aStart);
    for (const PLHashEntry* const* it = GetOrders(aStart); i < len; ++i, ++it) {
      if (InTransitiveClosure(*it, aTarget)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Return an array of all resource acquisitions
   *   aStart <_this r1 <_this r2 <_ ... <_ aTarget
   * from which |aStart <_this aTarget| was deduced, including
   * |aStart| and |aTarget|.
   *
   * Nb: there may be multiple deductions of |aStart <_this
   * aTarget|.  This function returns the first ordering found by
   * depth-first search.
   *
   * Nb: |InTransitiveClosure| could be replaced by this function.
   * However, this one is more expensive because we record the DFS
   * search stack on the heap whereas the other doesn't.
   *
   * @precondition |aStart != aTarget|
   */
  ResourceAcquisitionArray* GetDeductionChain(const PLHashEntry* aStart,
                                              const PLHashEntry* aTarget)
  {
    ResourceAcquisitionArray* chain = new ResourceAcquisitionArray();
    if (!chain) {
      NS_RUNTIMEABORT("can't allocate dep. cycle array");
    }
    chain->AppendElement(MakeResourceAcquisition(aStart));

    NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain),
                 "GetDeductionChain called when there's no deadlock");
    return chain;
  }

  // precondition: |aStart != aTarget|
  // invariant: |aStart| is the last element in |aChain|
  bool GetDeductionChain_Helper(const PLHashEntry* aStart,
                                const PLHashEntry* aTarget,
                                ResourceAcquisitionArray* aChain)
  {
    if (IsOrdered(aStart, aTarget)) {
      aChain->AppendElement(MakeResourceAcquisition(aTarget));
      return true;
    }

    index_type i = 0;
    size_type len = NumOrders(aStart);
    for (const PLHashEntry* const* it = GetOrders(aStart); i < len; ++i, ++it) {
      aChain->AppendElement(MakeResourceAcquisition(*it));
      if (GetDeductionChain_Helper(*it, aTarget, aChain)) {
        return true;
      }
      aChain->RemoveElementAt(aChain->Length() - 1);
    }
    return false;
  }

  /**
   * The partial order on resource acquisitions used by the deadlock
   * detector.
   */
  PLHashTable* mOrdering;     // T* -> PLHashEntry<OrderingEntry>

  /**
   * Protects contentious methods.
   * Nb: can't use mozilla::Mutex since we are used as its deadlock
   * detector.
   */
  PRLock* mLock;

private:
  DeadlockDetector(const DeadlockDetector& aDD) MOZ_DELETE;
  DeadlockDetector& operator=(const DeadlockDetector& aDD) MOZ_DELETE;
};


template<typename T>
const PLHashAllocOps DeadlockDetector<T>::kAllocOps = {
  DeadlockDetector<T>::TableAlloc, DeadlockDetector<T>::TableFree,
  DeadlockDetector<T>::EntryAlloc, DeadlockDetector<T>::EntryFree
};


template<typename T>
// FIXME bug 456272: tune based on average workload
const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 64;


} // namespace mozilla

#endif // ifndef mozilla_DeadlockDetector_h
