summaryrefslogtreecommitdiffstats
path: root/xpcom/glue/BlockingResourceBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/glue/BlockingResourceBase.cpp')
-rw-r--r--xpcom/glue/BlockingResourceBase.cpp511
1 files changed, 511 insertions, 0 deletions
diff --git a/xpcom/glue/BlockingResourceBase.cpp b/xpcom/glue/BlockingResourceBase.cpp
new file mode 100644
index 000000000..ada02c72c
--- /dev/null
+++ b/xpcom/glue/BlockingResourceBase.cpp
@@ -0,0 +1,511 @@
+/* -*- 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/. */
+
+#include "mozilla/BlockingResourceBase.h"
+
+#ifdef DEBUG
+#include "prthread.h"
+
+#include "nsAutoPtr.h"
+
+#ifndef MOZ_CALLSTACK_DISABLED
+#include "CodeAddressService.h"
+#include "nsHashKeys.h"
+#include "mozilla/StackWalk.h"
+#include "nsTHashtable.h"
+#endif
+
+#include "mozilla/CondVar.h"
+#include "mozilla/DeadlockDetector.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Mutex.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+#include "GeckoProfiler.h"
+#endif //MOZILLA_INTERNAL_API
+
+#endif // ifdef DEBUG
+
+namespace mozilla {
+//
+// BlockingResourceBase implementation
+//
+
+// static members
+const char* const BlockingResourceBase::kResourceTypeName[] = {
+ // needs to be kept in sync with BlockingResourceType
+ "Mutex", "ReentrantMonitor", "CondVar"
+};
+
+#ifdef DEBUG
+
+PRCallOnceType BlockingResourceBase::sCallOnce;
+unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
+BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
+
+
+void
+BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+ void* aSp, void* aClosure)
+{
+#ifndef MOZ_CALLSTACK_DISABLED
+ AcquisitionState* state = (AcquisitionState*)aClosure;
+ state->AppendElement(aPc);
+#endif
+}
+
+void
+BlockingResourceBase::GetStackTrace(AcquisitionState& aState)
+{
+#ifndef MOZ_CALLSTACK_DISABLED
+ // Skip this function and the calling function.
+ const uint32_t kSkipFrames = 2;
+
+ aState.Clear();
+
+ // NB: Ignore the return value, there's nothing useful we can do if this
+ // this fails.
+ MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
+#endif
+}
+
+/**
+ * PrintCycle
+ * Append to |aOut| detailed information about the circular
+ * dependency in |aCycle|. Returns true if it *appears* that this
+ * cycle may represent an imminent deadlock, but this is merely a
+ * heuristic; the value returned may be a false positive or false
+ * negative.
+ *
+ * *NOT* thread safe. Calls |Print()|.
+ *
+ * FIXME bug 456272 hack alert: because we can't write call
+ * contexts into strings, all info is written to stderr, but only
+ * some info is written into |aOut|
+ */
+bool
+PrintCycle(const BlockingResourceBase::DDT::ResourceAcquisitionArray* aCycle,
+ nsACString& aOut)
+{
+ NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!");
+
+ bool maybeImminent = true;
+
+ fputs("=== Cyclical dependency starts at\n", stderr);
+ aOut += "Cyclical dependency starts at\n";
+
+ const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res =
+ aCycle->ElementAt(0);
+ maybeImminent &= res->Print(aOut);
+
+ BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
+ BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
+ aCycle->Length();
+ const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it =
+ 1 + aCycle->Elements();
+ for (i = 1; i < len - 1; ++i, ++it) {
+ fputs("\n--- Next dependency:\n", stderr);
+ aOut += "\nNext dependency:\n";
+
+ maybeImminent &= (*it)->Print(aOut);
+ }
+
+ fputs("\n=== Cycle completed at\n", stderr);
+ aOut += "Cycle completed at\n";
+ (*it)->Print(aOut);
+
+ return maybeImminent;
+}
+
+#ifndef MOZ_CALLSTACK_DISABLED
+struct CodeAddressServiceLock final
+{
+ static void Unlock() { }
+ static void Lock() { }
+ static bool IsLocked() { return true; }
+};
+
+struct CodeAddressServiceStringAlloc final
+{
+ static char* copy(const char* aString) { return ::strdup(aString); }
+ static void free(char* aString) { ::free(aString); }
+};
+
+class CodeAddressServiceStringTable final
+{
+public:
+ CodeAddressServiceStringTable() : mSet(32) {}
+
+ const char* Intern(const char* aString)
+ {
+ nsCharPtrHashKey* e = mSet.PutEntry(aString);
+ return e->GetKey();
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return mSet.SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+private:
+ typedef nsTHashtable<nsCharPtrHashKey> StringSet;
+ StringSet mSet;
+};
+
+typedef CodeAddressService<CodeAddressServiceStringTable,
+ CodeAddressServiceStringAlloc,
+ CodeAddressServiceLock> WalkTheStackCodeAddressService;
+#endif
+
+bool
+BlockingResourceBase::Print(nsACString& aOut) const
+{
+ fprintf(stderr, "--- %s : %s",
+ kResourceTypeName[mType], mName);
+ aOut += BlockingResourceBase::kResourceTypeName[mType];
+ aOut += " : ";
+ aOut += mName;
+
+ bool acquired = IsAcquired();
+
+ if (acquired) {
+ fputs(" (currently acquired)\n", stderr);
+ aOut += " (currently acquired)\n";
+ }
+
+ fputs(" calling context\n", stderr);
+#ifdef MOZ_CALLSTACK_DISABLED
+ fputs(" [stack trace unavailable]\n", stderr);
+#else
+ const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
+
+ WalkTheStackCodeAddressService addressService;
+
+ for (uint32_t i = 0; i < state.Length(); i++) {
+ const size_t kMaxLength = 1024;
+ char buffer[kMaxLength];
+ addressService.GetLocation(i + 1, state[i], buffer, kMaxLength);
+ const char* fmt = " %s\n";
+ aOut.AppendLiteral(" ");
+ aOut.Append(buffer);
+ aOut.AppendLiteral("\n");
+ fprintf(stderr, fmt, buffer);
+ }
+
+#endif
+
+ return acquired;
+}
+
+
+BlockingResourceBase::BlockingResourceBase(
+ const char* aName,
+ BlockingResourceBase::BlockingResourceType aType)
+ : mName(aName)
+ , mType(aType)
+#ifdef MOZ_CALLSTACK_DISABLED
+ , mAcquired(false)
+#else
+ , mAcquired()
+#endif
+{
+ MOZ_ASSERT(mName, "Name must be nonnull");
+ // PR_CallOnce guaranatees that InitStatics is called in a
+ // thread-safe way
+ if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
+ NS_RUNTIMEABORT("can't initialize blocking resource static members");
+ }
+
+ mChainPrev = 0;
+ sDeadlockDetector->Add(this);
+}
+
+
+BlockingResourceBase::~BlockingResourceBase()
+{
+ // we don't check for really obviously bad things like freeing
+ // Mutexes while they're still locked. it is assumed that the
+ // base class, or its underlying primitive, will check for such
+ // stupid mistakes.
+ mChainPrev = 0; // racy only for stupidly buggy client code
+ if (sDeadlockDetector) {
+ sDeadlockDetector->Remove(this);
+ }
+}
+
+
+size_t
+BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf)
+{
+ return sDeadlockDetector ?
+ sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) : 0;
+}
+
+
+PRStatus
+BlockingResourceBase::InitStatics()
+{
+ PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0);
+ sDeadlockDetector = new DDT();
+ if (!sDeadlockDetector) {
+ NS_RUNTIMEABORT("can't allocate deadlock detector");
+ }
+ return PR_SUCCESS;
+}
+
+
+void
+BlockingResourceBase::Shutdown()
+{
+ delete sDeadlockDetector;
+ sDeadlockDetector = 0;
+}
+
+
+void
+BlockingResourceBase::CheckAcquire()
+{
+ if (mType == eCondVar) {
+ NS_NOTYETIMPLEMENTED(
+ "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
+ return;
+ }
+
+ BlockingResourceBase* chainFront = ResourceChainFront();
+ nsAutoPtr<DDT::ResourceAcquisitionArray> cycle(
+ sDeadlockDetector->CheckAcquisition(
+ chainFront ? chainFront : 0, this));
+ if (!cycle) {
+ return;
+ }
+
+#ifndef MOZ_CALLSTACK_DISABLED
+ // Update the current stack before printing.
+ GetStackTrace(mAcquired);
+#endif
+
+ fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
+ nsAutoCString out("Potential deadlock detected:\n");
+ bool maybeImminent = PrintCycle(cycle, out);
+
+ if (maybeImminent) {
+ fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
+ out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
+ } else {
+ fputs("\nDeadlock may happen for some other execution\n\n",
+ stderr);
+ out.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
+ }
+
+ // XXX can customize behavior on whether we /think/ deadlock is
+ // XXX about to happen. for example:
+ // XXX if (maybeImminent)
+ // NS_RUNTIMEABORT(out.get());
+ NS_ERROR(out.get());
+}
+
+
+void
+BlockingResourceBase::Acquire()
+{
+ if (mType == eCondVar) {
+ NS_NOTYETIMPLEMENTED(
+ "FIXME bug 456272: annots. to allow Acquire()ing condvars");
+ return;
+ }
+ NS_ASSERTION(!IsAcquired(),
+ "reacquiring already acquired resource");
+
+ ResourceChainAppend(ResourceChainFront());
+
+#ifdef MOZ_CALLSTACK_DISABLED
+ mAcquired = true;
+#else
+ // Take a stack snapshot.
+ GetStackTrace(mAcquired);
+ if (mFirstSeen.IsEmpty()) {
+ mFirstSeen = mAcquired;
+ }
+#endif
+}
+
+
+void
+BlockingResourceBase::Release()
+{
+ if (mType == eCondVar) {
+ NS_NOTYETIMPLEMENTED(
+ "FIXME bug 456272: annots. to allow Release()ing condvars");
+ return;
+ }
+
+ BlockingResourceBase* chainFront = ResourceChainFront();
+ NS_ASSERTION(chainFront && IsAcquired(),
+ "Release()ing something that hasn't been Acquire()ed");
+
+ if (chainFront == this) {
+ ResourceChainRemove();
+ } else {
+ // not an error, but makes code hard to reason about.
+ NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
+ nsCString tmp;
+ Print(tmp);
+
+ // remove this resource from wherever it lives in the chain
+ // we walk backwards in order of acquisition:
+ // (1) ...node<-prev<-curr...
+ // / /
+ // (2) ...prev<-curr...
+ BlockingResourceBase* curr = chainFront;
+ BlockingResourceBase* prev = nullptr;
+ while (curr && (prev = curr->mChainPrev) && (prev != this)) {
+ curr = prev;
+ }
+ if (prev == this) {
+ curr->mChainPrev = prev->mChainPrev;
+ }
+ }
+
+ ClearAcquisitionState();
+}
+
+
+//
+// Debug implementation of (OffTheBooks)Mutex
+void
+OffTheBooksMutex::Lock()
+{
+ CheckAcquire();
+ PR_Lock(mLock);
+ Acquire(); // protected by mLock
+}
+
+void
+OffTheBooksMutex::Unlock()
+{
+ Release(); // protected by mLock
+ PRStatus status = PR_Unlock(mLock);
+ NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()");
+}
+
+
+//
+// Debug implementation of ReentrantMonitor
+void
+ReentrantMonitor::Enter()
+{
+ BlockingResourceBase* chainFront = ResourceChainFront();
+
+ // the code below implements monitor reentrancy semantics
+
+ if (this == chainFront) {
+ // immediately re-entered the monitor: acceptable
+ PR_EnterMonitor(mReentrantMonitor);
+ ++mEntryCount;
+ return;
+ }
+
+ // this is sort of a hack around not recording the thread that
+ // owns this monitor
+ if (chainFront) {
+ for (BlockingResourceBase* br = ResourceChainPrev(chainFront);
+ br;
+ br = ResourceChainPrev(br)) {
+ if (br == this) {
+ NS_WARNING(
+ "Re-entering ReentrantMonitor after acquiring other resources.");
+
+ // show the caller why this is potentially bad
+ CheckAcquire();
+
+ PR_EnterMonitor(mReentrantMonitor);
+ ++mEntryCount;
+ return;
+ }
+ }
+ }
+
+ CheckAcquire();
+ PR_EnterMonitor(mReentrantMonitor);
+ NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
+ Acquire(); // protected by mReentrantMonitor
+ mEntryCount = 1;
+}
+
+void
+ReentrantMonitor::Exit()
+{
+ if (--mEntryCount == 0) {
+ Release(); // protected by mReentrantMonitor
+ }
+ PRStatus status = PR_ExitMonitor(mReentrantMonitor);
+ NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
+}
+
+nsresult
+ReentrantMonitor::Wait(PRIntervalTime aInterval)
+{
+ AssertCurrentThreadIn();
+
+ // save monitor state and reset it to empty
+ int32_t savedEntryCount = mEntryCount;
+ AcquisitionState savedAcquisitionState = GetAcquisitionState();
+ BlockingResourceBase* savedChainPrev = mChainPrev;
+ mEntryCount = 0;
+ ClearAcquisitionState();
+ mChainPrev = 0;
+
+ nsresult rv;
+#if defined(MOZILLA_INTERNAL_API)
+ {
+ GeckoProfilerSleepRAII profiler_sleep;
+#endif //MOZILLA_INTERNAL_API
+
+ // give up the monitor until we're back from Wait()
+ rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK :
+ NS_ERROR_FAILURE;
+
+#if defined(MOZILLA_INTERNAL_API)
+ }
+#endif //MOZILLA_INTERNAL_API
+
+ // restore saved state
+ mEntryCount = savedEntryCount;
+ SetAcquisitionState(savedAcquisitionState);
+ mChainPrev = savedChainPrev;
+
+ return rv;
+}
+
+
+//
+// Debug implementation of CondVar
+nsresult
+CondVar::Wait(PRIntervalTime aInterval)
+{
+ AssertCurrentThreadOwnsMutex();
+
+ // save mutex state and reset to empty
+ AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
+ BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
+ mLock->ClearAcquisitionState();
+ mLock->mChainPrev = 0;
+
+ // give up mutex until we're back from Wait()
+ nsresult rv =
+ PR_WaitCondVar(mCvar, aInterval) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
+
+ // restore saved state
+ mLock->SetAcquisitionState(savedAcquisitionState);
+ mLock->mChainPrev = savedChainPrev;
+
+ return rv;
+}
+
+#endif // ifdef DEBUG
+
+
+} // namespace mozilla