michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: sw=4 ts=4 et : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/BlockingResourceBase.h" michael@0: michael@0: #ifdef DEBUG michael@0: #include "nsAutoPtr.h" michael@0: michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/ReentrantMonitor.h" michael@0: #include "mozilla/Mutex.h" michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #include "GeckoProfiler.h" michael@0: #endif //MOZILLA_INTERNAL_API michael@0: michael@0: #endif // ifdef DEBUG michael@0: michael@0: namespace mozilla { michael@0: // michael@0: // BlockingResourceBase implementation michael@0: // michael@0: michael@0: // static members michael@0: const char* const BlockingResourceBase::kResourceTypeName[] = michael@0: { michael@0: // needs to be kept in sync with BlockingResourceType michael@0: "Mutex", "ReentrantMonitor", "CondVar" michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: PRCallOnceType BlockingResourceBase::sCallOnce; michael@0: unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1; michael@0: BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; michael@0: michael@0: bool michael@0: BlockingResourceBase::DeadlockDetectorEntry::Print( michael@0: const DDT::ResourceAcquisition& aFirstSeen, michael@0: nsACString& out, michael@0: bool aPrintFirstSeenCx) const michael@0: { michael@0: CallStack lastAcquisition = mAcquisitionContext; // RACY, but benign michael@0: bool maybeCurrentlyAcquired = (CallStack::kNone != lastAcquisition); michael@0: CallStack printAcquisition = michael@0: (aPrintFirstSeenCx || !maybeCurrentlyAcquired) ? michael@0: aFirstSeen.mCallContext : lastAcquisition; michael@0: michael@0: fprintf(stderr, "--- %s : %s", michael@0: kResourceTypeName[mType], mName); michael@0: out += BlockingResourceBase::kResourceTypeName[mType]; michael@0: out += " : "; michael@0: out += mName; michael@0: michael@0: if (maybeCurrentlyAcquired) { michael@0: fputs(" (currently acquired)\n", stderr); michael@0: out += " (currently acquired)\n"; michael@0: } michael@0: michael@0: fputs(" calling context\n", stderr); michael@0: printAcquisition.Print(stderr); michael@0: michael@0: return maybeCurrentlyAcquired; michael@0: } michael@0: michael@0: michael@0: BlockingResourceBase::BlockingResourceBase( michael@0: const char* aName, michael@0: BlockingResourceBase::BlockingResourceType aType) michael@0: { michael@0: // PR_CallOnce guaranatees that InitStatics is called in a michael@0: // thread-safe way michael@0: if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) michael@0: NS_RUNTIMEABORT("can't initialize blocking resource static members"); michael@0: michael@0: mDDEntry = new BlockingResourceBase::DeadlockDetectorEntry(aName, aType); michael@0: mChainPrev = 0; michael@0: sDeadlockDetector->Add(mDDEntry); michael@0: } michael@0: michael@0: michael@0: BlockingResourceBase::~BlockingResourceBase() michael@0: { michael@0: // we don't check for really obviously bad things like freeing michael@0: // Mutexes while they're still locked. it is assumed that the michael@0: // base class, or its underlying primitive, will check for such michael@0: // stupid mistakes. michael@0: mChainPrev = 0; // racy only for stupidly buggy client code michael@0: mDDEntry = 0; // owned by deadlock detector michael@0: } michael@0: michael@0: michael@0: void michael@0: BlockingResourceBase::CheckAcquire(const CallStack& aCallContext) michael@0: { michael@0: if (eCondVar == mDDEntry->mType) { michael@0: NS_NOTYETIMPLEMENTED( michael@0: "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); michael@0: return; michael@0: } michael@0: michael@0: BlockingResourceBase* chainFront = ResourceChainFront(); michael@0: nsAutoPtr cycle( michael@0: sDeadlockDetector->CheckAcquisition( michael@0: chainFront ? chainFront->mDDEntry : 0, mDDEntry, michael@0: aCallContext)); michael@0: if (!cycle) michael@0: return; michael@0: michael@0: fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); michael@0: nsAutoCString out("Potential deadlock detected:\n"); michael@0: bool maybeImminent = PrintCycle(cycle, out); michael@0: michael@0: if (maybeImminent) { michael@0: fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); michael@0: out.Append("\n###!!! Deadlock may happen NOW!\n\n"); michael@0: } else { michael@0: fputs("\nDeadlock may happen for some other execution\n\n", michael@0: stderr); michael@0: out.Append("\nDeadlock may happen for some other execution\n\n"); michael@0: } michael@0: michael@0: // XXX can customize behavior on whether we /think/ deadlock is michael@0: // XXX about to happen. for example: michael@0: // XXX if (maybeImminent) michael@0: // NS_RUNTIMEABORT(out.get()); michael@0: NS_ERROR(out.get()); michael@0: } michael@0: michael@0: michael@0: void michael@0: BlockingResourceBase::Acquire(const CallStack& aCallContext) michael@0: { michael@0: if (eCondVar == mDDEntry->mType) { michael@0: NS_NOTYETIMPLEMENTED( michael@0: "FIXME bug 456272: annots. to allow Acquire()ing condvars"); michael@0: return; michael@0: } michael@0: NS_ASSERTION(mDDEntry->mAcquisitionContext == CallStack::kNone, michael@0: "reacquiring already acquired resource"); michael@0: michael@0: ResourceChainAppend(ResourceChainFront()); michael@0: mDDEntry->mAcquisitionContext = aCallContext; michael@0: } michael@0: michael@0: michael@0: void michael@0: BlockingResourceBase::Release() michael@0: { michael@0: if (eCondVar == mDDEntry->mType) { michael@0: NS_NOTYETIMPLEMENTED( michael@0: "FIXME bug 456272: annots. to allow Release()ing condvars"); michael@0: return; michael@0: } michael@0: michael@0: BlockingResourceBase* chainFront = ResourceChainFront(); michael@0: NS_ASSERTION(chainFront michael@0: && CallStack::kNone != mDDEntry->mAcquisitionContext, michael@0: "Release()ing something that hasn't been Acquire()ed"); michael@0: michael@0: if (chainFront == this) { michael@0: ResourceChainRemove(); michael@0: } michael@0: else { michael@0: // not an error, but makes code hard to reason about. michael@0: NS_WARNING("Resource acquired at calling context\n"); michael@0: mDDEntry->mAcquisitionContext.Print(stderr); michael@0: NS_WARNING("\nis being released in non-LIFO order; why?"); michael@0: michael@0: // remove this resource from wherever it lives in the chain michael@0: // we walk backwards in order of acquisition: michael@0: // (1) ...node<-prev<-curr... michael@0: // / / michael@0: // (2) ...prev<-curr... michael@0: BlockingResourceBase* curr = chainFront; michael@0: BlockingResourceBase* prev = nullptr; michael@0: while (curr && (prev = curr->mChainPrev) && (prev != this)) michael@0: curr = prev; michael@0: if (prev == this) michael@0: curr->mChainPrev = prev->mChainPrev; michael@0: } michael@0: michael@0: mDDEntry->mAcquisitionContext = CallStack::kNone; michael@0: } michael@0: michael@0: michael@0: bool michael@0: BlockingResourceBase::PrintCycle(const DDT::ResourceAcquisitionArray* aCycle, michael@0: nsACString& out) michael@0: { michael@0: NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!"); michael@0: michael@0: bool maybeImminent = true; michael@0: michael@0: fputs("=== Cyclical dependency starts at\n", stderr); michael@0: out += "Cyclical dependency starts at\n"; michael@0: michael@0: const DDT::ResourceAcquisition res = aCycle->ElementAt(0); michael@0: maybeImminent &= res.mResource->Print(res, out); michael@0: michael@0: DDT::ResourceAcquisitionArray::index_type i; michael@0: DDT::ResourceAcquisitionArray::size_type len = aCycle->Length(); michael@0: const DDT::ResourceAcquisition* it = 1 + aCycle->Elements(); michael@0: for (i = 1; i < len - 1; ++i, ++it) { michael@0: fputs("\n--- Next dependency:\n", stderr); michael@0: out += "\nNext dependency:\n"; michael@0: michael@0: maybeImminent &= it->mResource->Print(*it, out); michael@0: } michael@0: michael@0: fputs("\n=== Cycle completed at\n", stderr); michael@0: out += "Cycle completed at\n"; michael@0: it->mResource->Print(*it, out, true); michael@0: michael@0: return maybeImminent; michael@0: } michael@0: michael@0: michael@0: // michael@0: // Debug implementation of (OffTheBooks)Mutex michael@0: void michael@0: OffTheBooksMutex::Lock() michael@0: { michael@0: CallStack callContext = CallStack(); michael@0: michael@0: CheckAcquire(callContext); michael@0: PR_Lock(mLock); michael@0: Acquire(callContext); // protected by mLock michael@0: } michael@0: michael@0: void michael@0: OffTheBooksMutex::Unlock() michael@0: { michael@0: Release(); // protected by mLock michael@0: PRStatus status = PR_Unlock(mLock); michael@0: NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()"); michael@0: } michael@0: michael@0: michael@0: // michael@0: // Debug implementation of ReentrantMonitor michael@0: void michael@0: ReentrantMonitor::Enter() michael@0: { michael@0: BlockingResourceBase* chainFront = ResourceChainFront(); michael@0: michael@0: // the code below implements monitor reentrancy semantics michael@0: michael@0: if (this == chainFront) { michael@0: // immediately re-entered the monitor: acceptable michael@0: PR_EnterMonitor(mReentrantMonitor); michael@0: ++mEntryCount; michael@0: return; michael@0: } michael@0: michael@0: CallStack callContext = CallStack(); michael@0: michael@0: // this is sort of a hack around not recording the thread that michael@0: // owns this monitor michael@0: if (chainFront) { michael@0: for (BlockingResourceBase* br = ResourceChainPrev(chainFront); michael@0: br; michael@0: br = ResourceChainPrev(br)) { michael@0: if (br == this) { michael@0: NS_WARNING( michael@0: "Re-entering ReentrantMonitor after acquiring other resources.\n" michael@0: "At calling context\n"); michael@0: GetAcquisitionContext().Print(stderr); michael@0: michael@0: // show the caller why this is potentially bad michael@0: CheckAcquire(callContext); michael@0: michael@0: PR_EnterMonitor(mReentrantMonitor); michael@0: ++mEntryCount; michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: CheckAcquire(callContext); michael@0: PR_EnterMonitor(mReentrantMonitor); michael@0: NS_ASSERTION(0 == mEntryCount, "ReentrantMonitor isn't free!"); michael@0: Acquire(callContext); // protected by mReentrantMonitor michael@0: mEntryCount = 1; michael@0: } michael@0: michael@0: void michael@0: ReentrantMonitor::Exit() michael@0: { michael@0: if (0 == --mEntryCount) michael@0: Release(); // protected by mReentrantMonitor michael@0: PRStatus status = PR_ExitMonitor(mReentrantMonitor); michael@0: NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); michael@0: } michael@0: michael@0: nsresult michael@0: ReentrantMonitor::Wait(PRIntervalTime interval) michael@0: { michael@0: AssertCurrentThreadIn(); michael@0: michael@0: // save monitor state and reset it to empty michael@0: int32_t savedEntryCount = mEntryCount; michael@0: CallStack savedAcquisitionContext = GetAcquisitionContext(); michael@0: BlockingResourceBase* savedChainPrev = mChainPrev; michael@0: mEntryCount = 0; michael@0: SetAcquisitionContext(CallStack::kNone); michael@0: mChainPrev = 0; michael@0: michael@0: nsresult rv; michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: { michael@0: GeckoProfilerSleepRAII profiler_sleep; michael@0: #endif //MOZILLA_INTERNAL_API michael@0: michael@0: // give up the monitor until we're back from Wait() michael@0: rv = PR_Wait(mReentrantMonitor, interval) == PR_SUCCESS ? michael@0: NS_OK : NS_ERROR_FAILURE; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: } michael@0: #endif //MOZILLA_INTERNAL_API michael@0: michael@0: // restore saved state michael@0: mEntryCount = savedEntryCount; michael@0: SetAcquisitionContext(savedAcquisitionContext); michael@0: mChainPrev = savedChainPrev; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // michael@0: // Debug implementation of CondVar michael@0: nsresult michael@0: CondVar::Wait(PRIntervalTime interval) michael@0: { michael@0: AssertCurrentThreadOwnsMutex(); michael@0: michael@0: // save mutex state and reset to empty michael@0: CallStack savedAcquisitionContext = mLock->GetAcquisitionContext(); michael@0: BlockingResourceBase* savedChainPrev = mLock->mChainPrev; michael@0: mLock->SetAcquisitionContext(CallStack::kNone); michael@0: mLock->mChainPrev = 0; michael@0: michael@0: // give up mutex until we're back from Wait() michael@0: nsresult rv = michael@0: PR_WaitCondVar(mCvar, interval) == PR_SUCCESS ? michael@0: NS_OK : NS_ERROR_FAILURE; michael@0: michael@0: // restore saved state michael@0: mLock->SetAcquisitionContext(savedAcquisitionContext); michael@0: mLock->mChainPrev = savedChainPrev; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #endif // ifdef DEBUG michael@0: michael@0: michael@0: } // namespace mozilla