diff options
Diffstat (limited to 'xpcom/glue/BlockingResourceBase.cpp')
-rw-r--r-- | xpcom/glue/BlockingResourceBase.cpp | 511 |
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 |