/* -*- 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.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(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