DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
/* 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/. */

#include "ThreadAnnotation.h"

#include <stddef.h>

#include "mozilla/Assertions.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/UniquePtr.h"

#include "prthread.h"
#include "nsDebug.h"
#include "nsExceptionHandler.h"
#include "nsString.h"
#include "nsTArray.h"

using mozilla::StaticMutex;
using mozilla::StaticMutexAutoLock;
using mozilla::UniquePtr;

namespace CrashReporter {

namespace {

#ifdef XP_MACOSX
/*
 * On the Mac, exception handler callbacks are invoked in a context where all
 * other threads are paused. As a result, attempting to acquire a mutex is
 * problematic because 1) the mutex may be held by another thread which is
 * now suspended and 2) acquiring an unheld mutex can trigger memory allocation
 * which generally requires allocator locks. This class is a wrapper around a
 * StaticMutex, providing an IsLocked() method which only makes sense to use
 * in the Mac exception handling context when other threads are paused.
 */
class MacCrashReporterLock {
 public:
  void Lock() {
    sInnerMutex.Lock();
    sIsLocked = true;
  }
  void Unlock() {
    sIsLocked = false;
    sInnerMutex.Unlock();
  }
  /*
   * Returns true if the lock is held at the time the method is called.
   * The return value is out-of-date by the time this method returns unless
   * we have a guarantee that other threads are not running such as in Mac
   * breadkpad exception handler context.
   */
  bool IsLocked() { return sIsLocked; }
  void AssertCurrentThreadOwns() { sInnerMutex.AssertCurrentThreadOwns(); }

 private:
  static StaticMutex sInnerMutex;
  static bool sIsLocked;
};
StaticMutex MacCrashReporterLock::sInnerMutex;
bool MacCrashReporterLock::sIsLocked;

// Use MacCrashReporterLock for locking
typedef mozilla::BaseAutoLock<MacCrashReporterLock&> CrashReporterAutoLock;
typedef MacCrashReporterLock CrashReporterLockType;
#else  /* !XP_MACOSX */
// Use StaticMutex for locking
typedef StaticMutexAutoLock CrashReporterAutoLock;
typedef StaticMutex CrashReporterLockType;
#endif /* XP_MACOSX */

// Protects access to sInitialized and sThreadAnnotations.
static CrashReporterLockType sMutex;

class ThreadAnnotationSpan {
 public:
  ThreadAnnotationSpan(uint32_t aBegin, uint32_t aEnd)
      : mBegin(aBegin), mEnd(aEnd) {
    MOZ_ASSERT(mBegin < mEnd);
  }

  ~ThreadAnnotationSpan();

  class Comparator {
   public:
    bool Equals(const ThreadAnnotationSpan* const& a,
                const ThreadAnnotationSpan* const& b) const {
      return a->mBegin == b->mBegin;
    }

    bool LessThan(const ThreadAnnotationSpan* const& a,
                  const ThreadAnnotationSpan* const& b) const {
      return a->mBegin < b->mBegin;
    }
  };

 private:
  // ~ThreadAnnotationSpan() does nontrivial thing. Make sure we don't
  // instantiate accidentally.
  ThreadAnnotationSpan(const ThreadAnnotationSpan& aOther) = delete;
  ThreadAnnotationSpan& operator=(const ThreadAnnotationSpan& aOther) = delete;

  friend class ThreadAnnotationData;
  friend class Comparator;

  uint32_t mBegin;
  uint32_t mEnd;
};

// This class keeps the flat version of thread annotations for each thread.
// When a thread calls CrashReporter::SetCurrentThreadName(), it adds
// information about the calling thread (thread id and name) to this class.
// When crash happens, the crash reporter gets flat representation and add to
// the crash annotation file.
class ThreadAnnotationData {
 public:
  ThreadAnnotationData() {}

  ~ThreadAnnotationData() {}

  // Adds <pre> tid:"thread name",</pre> annotation to the current annotations.
  // Returns an instance of ThreadAnnotationSpan for cleanup on thread
  // termination.
  ThreadAnnotationSpan* AddThreadAnnotation(ThreadId aTid,
                                            const char* aThreadName) {
    if (!aTid || !aThreadName) {
      return nullptr;
    }

    uint32_t oldLength = mData.Length();
    mData.AppendPrintf("%u:\"%s\",", aTid, aThreadName);
    uint32_t newLength = mData.Length();

    ThreadAnnotationSpan* rv = new ThreadAnnotationSpan(oldLength, newLength);
    mDataSpans.AppendElement(rv);
    return rv;
  }

  // Called on thread termination. Removes the thread annotation, represented as
  // ThreadAnnotationSpan, from the flat representation.
  void EraseThreadAnnotation(const ThreadAnnotationSpan& aThreadInfo) {
    uint32_t begin = aThreadInfo.mBegin;
    uint32_t end = aThreadInfo.mEnd;

    if (!(begin < end && end <= mData.Length())) {
      return;
    }

    uint32_t cutLength = end - begin;
    mData.Cut(begin, cutLength);

    // Adjust the ThreadAnnotationSpan affected by data shifting.
    size_t index = mDataSpans.BinaryIndexOf(&aThreadInfo,
                                            ThreadAnnotationSpan::Comparator());
    for (size_t i = index + 1; i < mDataSpans.Length(); i++) {
      ThreadAnnotationSpan* elem = mDataSpans[i];

      MOZ_ASSERT(elem->mBegin >= cutLength);
      MOZ_ASSERT(elem->mEnd > cutLength);

      elem->mBegin -= cutLength;
      elem->mEnd -= cutLength;
    }

    // No loner tracking aThreadInfo.
    mDataSpans.RemoveElementAt(index);
  }

  // Gets the flat representation of thread annotations.
  void GetData(const std::function<void(const char*)>& aCallback) {
    aCallback(mData.BeginReading());
  }

 private:
  // The flat representation of thread annotations.
  nsCString mData;

  // This array tracks the created ThreadAnnotationSpan instances so that we
  // can make adjustments accordingly when we cut substrings from mData on
  // thread exit.
  nsTArray<ThreadAnnotationSpan*> mDataSpans;
};

static bool sInitialized = false;
static UniquePtr<ThreadAnnotationData> sThreadAnnotations;

static unsigned sTLSThreadInfoKey = (unsigned)-1;
void ThreadLocalDestructor(void* aUserData) {
  MOZ_ASSERT(aUserData);

  CrashReporterAutoLock lock(sMutex);

  ThreadAnnotationSpan* aThreadInfo =
      static_cast<ThreadAnnotationSpan*>(aUserData);
  delete aThreadInfo;
}

// This is called on thread termination.
ThreadAnnotationSpan::~ThreadAnnotationSpan() {
  // Note that we can't lock the mutex here because this function may be called
  // from SetCurrentThreadName().
  sMutex.AssertCurrentThreadOwns();

  if (sThreadAnnotations) {
    sThreadAnnotations->EraseThreadAnnotation(*this);
  }
}

}  // Anonymous namespace.

void InitThreadAnnotation() {
  CrashReporterAutoLock lock(sMutex);

  if (sInitialized) {
    return;
  }

  PRStatus status =
      PR_NewThreadPrivateIndex(&sTLSThreadInfoKey, &ThreadLocalDestructor);
  if (status == PR_FAILURE) {
    return;
  }

  sInitialized = true;

  sThreadAnnotations = mozilla::MakeUnique<ThreadAnnotationData>();
}

void SetCurrentThreadName(const char* aName) {
  if (PR_GetThreadPrivate(sTLSThreadInfoKey)) {
    // Explicitly set TLS value to null (and call the dtor function ) before
    // acquiring sMutex to avoid reentrant deadlock.
    PR_SetThreadPrivate(sTLSThreadInfoKey, nullptr);
  }

  CrashReporterAutoLock lock(sMutex);

  if (!sInitialized) {
    return;
  }

  ThreadAnnotationSpan* threadInfo =
      sThreadAnnotations->AddThreadAnnotation(CurrentThreadId(), aName);
  // This may destroy the old insatnce.
  PR_SetThreadPrivate(sTLSThreadInfoKey, threadInfo);
}

void GetFlatThreadAnnotation(const std::function<void(const char*)>& aCallback,
                             bool aIsHandlingException) {
  bool lockNeeded = true;

#ifdef XP_MACOSX
  if (aIsHandlingException) {
    // Don't acquire the lock on Mac because we are
    // executing in exception context where all other
    // threads are paused. If the lock is held, skip
    // thread annotations to avoid deadlock caused by
    // waiting for a suspended thread. If the lock
    // isn't held, acquiring it serves no purpose and
    // can trigger memory allocations.
    if (sMutex.IsLocked()) {
      aCallback("");
      return;
    }
    lockNeeded = false;
  }
#endif

  if (lockNeeded) {
    sMutex.Lock();
  }

  if (sThreadAnnotations) {
    sThreadAnnotations->GetData(aCallback);
  } else {
    // Maybe already shutdown: call aCallback with empty annotation data.
    aCallback("");
  }

  if (lockNeeded) {
    sMutex.Unlock();
  }
}

void ShutdownThreadAnnotation() {
  CrashReporterAutoLock lock(sMutex);

  sInitialized = false;
  sThreadAnnotations.reset();
}

}  // namespace CrashReporter