diff -r 000000000000 -r 6474c204b198 xpcom/glue/BlockingResourceBase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xpcom/glue/BlockingResourceBase.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * 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 "nsAutoPtr.h" + +#include "mozilla/CondVar.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#ifdef 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; + +bool +BlockingResourceBase::DeadlockDetectorEntry::Print( + const DDT::ResourceAcquisition& aFirstSeen, + nsACString& out, + bool aPrintFirstSeenCx) const +{ + CallStack lastAcquisition = mAcquisitionContext; // RACY, but benign + bool maybeCurrentlyAcquired = (CallStack::kNone != lastAcquisition); + CallStack printAcquisition = + (aPrintFirstSeenCx || !maybeCurrentlyAcquired) ? + aFirstSeen.mCallContext : lastAcquisition; + + fprintf(stderr, "--- %s : %s", + kResourceTypeName[mType], mName); + out += BlockingResourceBase::kResourceTypeName[mType]; + out += " : "; + out += mName; + + if (maybeCurrentlyAcquired) { + fputs(" (currently acquired)\n", stderr); + out += " (currently acquired)\n"; + } + + fputs(" calling context\n", stderr); + printAcquisition.Print(stderr); + + return maybeCurrentlyAcquired; +} + + +BlockingResourceBase::BlockingResourceBase( + const char* aName, + BlockingResourceBase::BlockingResourceType aType) +{ + // 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"); + + mDDEntry = new BlockingResourceBase::DeadlockDetectorEntry(aName, aType); + mChainPrev = 0; + sDeadlockDetector->Add(mDDEntry); +} + + +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 + mDDEntry = 0; // owned by deadlock detector +} + + +void +BlockingResourceBase::CheckAcquire(const CallStack& aCallContext) +{ + if (eCondVar == mDDEntry->mType) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + nsAutoPtr cycle( + sDeadlockDetector->CheckAcquisition( + chainFront ? chainFront->mDDEntry : 0, mDDEntry, + aCallContext)); + if (!cycle) + return; + + 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.Append("\n###!!! Deadlock may happen NOW!\n\n"); + } else { + fputs("\nDeadlock may happen for some other execution\n\n", + stderr); + out.Append("\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(const CallStack& aCallContext) +{ + if (eCondVar == mDDEntry->mType) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow Acquire()ing condvars"); + return; + } + NS_ASSERTION(mDDEntry->mAcquisitionContext == CallStack::kNone, + "reacquiring already acquired resource"); + + ResourceChainAppend(ResourceChainFront()); + mDDEntry->mAcquisitionContext = aCallContext; +} + + +void +BlockingResourceBase::Release() +{ + if (eCondVar == mDDEntry->mType) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow Release()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + NS_ASSERTION(chainFront + && CallStack::kNone != mDDEntry->mAcquisitionContext, + "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 at calling context\n"); + mDDEntry->mAcquisitionContext.Print(stderr); + NS_WARNING("\nis being released in non-LIFO order; why?"); + + // 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; + } + + mDDEntry->mAcquisitionContext = CallStack::kNone; +} + + +bool +BlockingResourceBase::PrintCycle(const DDT::ResourceAcquisitionArray* aCycle, + nsACString& out) +{ + NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!"); + + bool maybeImminent = true; + + fputs("=== Cyclical dependency starts at\n", stderr); + out += "Cyclical dependency starts at\n"; + + const DDT::ResourceAcquisition res = aCycle->ElementAt(0); + maybeImminent &= res.mResource->Print(res, out); + + DDT::ResourceAcquisitionArray::index_type i; + DDT::ResourceAcquisitionArray::size_type len = aCycle->Length(); + const DDT::ResourceAcquisition* it = 1 + aCycle->Elements(); + for (i = 1; i < len - 1; ++i, ++it) { + fputs("\n--- Next dependency:\n", stderr); + out += "\nNext dependency:\n"; + + maybeImminent &= it->mResource->Print(*it, out); + } + + fputs("\n=== Cycle completed at\n", stderr); + out += "Cycle completed at\n"; + it->mResource->Print(*it, out, true); + + return maybeImminent; +} + + +// +// Debug implementation of (OffTheBooks)Mutex +void +OffTheBooksMutex::Lock() +{ + CallStack callContext = CallStack(); + + CheckAcquire(callContext); + PR_Lock(mLock); + Acquire(callContext); // 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; + } + + CallStack callContext = CallStack(); + + // 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.\n" + "At calling context\n"); + GetAcquisitionContext().Print(stderr); + + // show the caller why this is potentially bad + CheckAcquire(callContext); + + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(callContext); + PR_EnterMonitor(mReentrantMonitor); + NS_ASSERTION(0 == mEntryCount, "ReentrantMonitor isn't free!"); + Acquire(callContext); // protected by mReentrantMonitor + mEntryCount = 1; +} + +void +ReentrantMonitor::Exit() +{ + if (0 == --mEntryCount) + Release(); // protected by mReentrantMonitor + PRStatus status = PR_ExitMonitor(mReentrantMonitor); + NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); +} + +nsresult +ReentrantMonitor::Wait(PRIntervalTime interval) +{ + AssertCurrentThreadIn(); + + // save monitor state and reset it to empty + int32_t savedEntryCount = mEntryCount; + CallStack savedAcquisitionContext = GetAcquisitionContext(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + SetAcquisitionContext(CallStack::kNone); + mChainPrev = 0; + + nsresult rv; +#ifdef MOZILLA_INTERNAL_API + { + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, interval) == PR_SUCCESS ? + NS_OK : NS_ERROR_FAILURE; + +#ifdef MOZILLA_INTERNAL_API + } +#endif //MOZILLA_INTERNAL_API + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionContext(savedAcquisitionContext); + mChainPrev = savedChainPrev; + + return rv; +} + + +// +// Debug implementation of CondVar +nsresult +CondVar::Wait(PRIntervalTime interval) +{ + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + CallStack savedAcquisitionContext = mLock->GetAcquisitionContext(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + mLock->SetAcquisitionContext(CallStack::kNone); + mLock->mChainPrev = 0; + + // give up mutex until we're back from Wait() + nsresult rv = + PR_WaitCondVar(mCvar, interval) == PR_SUCCESS ? + NS_OK : NS_ERROR_FAILURE; + + // restore saved state + mLock->SetAcquisitionContext(savedAcquisitionContext); + mLock->mChainPrev = savedChainPrev; + + return rv; +} + +#endif // ifdef DEBUG + + +} // namespace mozilla