LCOV - code coverage report
Current view: top level - xpcom/threads - BlockingResourceBase.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 100 193 51.8 %
Date: 2017-07-14 16:53:18 Functions: 13 23 56.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "mozilla/BlockingResourceBase.h"
       8             : 
       9             : #ifdef DEBUG
      10             : #include "prthread.h"
      11             : 
      12             : #include "nsAutoPtr.h"
      13             : 
      14             : #ifndef MOZ_CALLSTACK_DISABLED
      15             : #include "CodeAddressService.h"
      16             : #include "nsHashKeys.h"
      17             : #include "mozilla/StackWalk.h"
      18             : #include "nsTHashtable.h"
      19             : #endif
      20             : 
      21             : #include "mozilla/CondVar.h"
      22             : #include "mozilla/DeadlockDetector.h"
      23             : #include "mozilla/ReentrantMonitor.h"
      24             : #include "mozilla/Mutex.h"
      25             : #include "mozilla/RWLock.h"
      26             : 
      27             : #if defined(MOZILLA_INTERNAL_API)
      28             : #include "GeckoProfiler.h"
      29             : #endif //MOZILLA_INTERNAL_API
      30             : 
      31             : #endif // ifdef DEBUG
      32             : 
      33             : namespace mozilla {
      34             : //
      35             : // BlockingResourceBase implementation
      36             : //
      37             : 
      38             : // static members
      39             : const char* const BlockingResourceBase::kResourceTypeName[] = {
      40             :   // needs to be kept in sync with BlockingResourceType
      41             :   "Mutex", "ReentrantMonitor", "CondVar"
      42             : };
      43             : 
      44             : #ifdef DEBUG
      45             : 
      46             : PRCallOnceType BlockingResourceBase::sCallOnce;
      47             : unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
      48             : BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
      49             : 
      50             : 
      51             : void
      52           0 : BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
      53             :                                         void* aSp, void* aClosure)
      54             : {
      55             : #ifndef MOZ_CALLSTACK_DISABLED
      56             :   AcquisitionState* state = (AcquisitionState*)aClosure;
      57             :   state->AppendElement(aPc);
      58             : #endif
      59           0 : }
      60             : 
      61             : void
      62           0 : BlockingResourceBase::GetStackTrace(AcquisitionState& aState)
      63             : {
      64             : #ifndef MOZ_CALLSTACK_DISABLED
      65             :   // Skip this function and the calling function.
      66             :   const uint32_t kSkipFrames = 2;
      67             : 
      68             :   aState.Clear();
      69             : 
      70             :   // NB: Ignore the return value, there's nothing useful we can do if this
      71             :   //     this fails.
      72             :   MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
      73             : #endif
      74           0 : }
      75             : 
      76             : /**
      77             :  * PrintCycle
      78             :  * Append to |aOut| detailed information about the circular
      79             :  * dependency in |aCycle|.  Returns true if it *appears* that this
      80             :  * cycle may represent an imminent deadlock, but this is merely a
      81             :  * heuristic; the value returned may be a false positive or false
      82             :  * negative.
      83             :  *
      84             :  * *NOT* thread safe.  Calls |Print()|.
      85             :  *
      86             :  * FIXME bug 456272 hack alert: because we can't write call
      87             :  * contexts into strings, all info is written to stderr, but only
      88             :  * some info is written into |aOut|
      89             :  */
      90             : bool
      91           0 : PrintCycle(const BlockingResourceBase::DDT::ResourceAcquisitionArray* aCycle,
      92             :            nsACString& aOut)
      93             : {
      94           0 :   NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!");
      95             : 
      96           0 :   bool maybeImminent = true;
      97             : 
      98           0 :   fputs("=== Cyclical dependency starts at\n", stderr);
      99           0 :   aOut += "Cyclical dependency starts at\n";
     100             : 
     101             :   const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res =
     102           0 :     aCycle->ElementAt(0);
     103           0 :   maybeImminent &= res->Print(aOut);
     104             : 
     105             :   BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
     106             :   BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
     107           0 :     aCycle->Length();
     108             :   const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it =
     109           0 :     1 + aCycle->Elements();
     110           0 :   for (i = 1; i < len - 1; ++i, ++it) {
     111           0 :     fputs("\n--- Next dependency:\n", stderr);
     112           0 :     aOut += "\nNext dependency:\n";
     113             : 
     114           0 :     maybeImminent &= (*it)->Print(aOut);
     115             :   }
     116             : 
     117           0 :   fputs("\n=== Cycle completed at\n", stderr);
     118           0 :   aOut += "Cycle completed at\n";
     119           0 :   (*it)->Print(aOut);
     120             : 
     121           0 :   return maybeImminent;
     122             : }
     123             : 
     124             : #ifndef MOZ_CALLSTACK_DISABLED
     125             : struct CodeAddressServiceLock final
     126             : {
     127             :   static void Unlock() { }
     128             :   static void Lock() { }
     129             :   static bool IsLocked() { return true; }
     130             : };
     131             : 
     132             : struct CodeAddressServiceStringAlloc final
     133             : {
     134             :   static char* copy(const char* aString) { return ::strdup(aString); }
     135             :   static void free(char* aString) { ::free(aString); }
     136             : };
     137             : 
     138             : class CodeAddressServiceStringTable final
     139             : {
     140             : public:
     141             :   CodeAddressServiceStringTable() : mSet(32) {}
     142             : 
     143             :   const char* Intern(const char* aString)
     144             :   {
     145             :     nsCharPtrHashKey* e = mSet.PutEntry(aString);
     146             :     return e->GetKey();
     147             :   }
     148             : 
     149             :   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     150             :   {
     151             :     return mSet.SizeOfExcludingThis(aMallocSizeOf);
     152             :   }
     153             : 
     154             : private:
     155             :   typedef nsTHashtable<nsCharPtrHashKey> StringSet;
     156             :   StringSet mSet;
     157             : };
     158             : 
     159             : typedef CodeAddressService<CodeAddressServiceStringTable,
     160             :                            CodeAddressServiceStringAlloc,
     161             :                            CodeAddressServiceLock> WalkTheStackCodeAddressService;
     162             : #endif
     163             : 
     164             : bool
     165           0 : BlockingResourceBase::Print(nsACString& aOut) const
     166             : {
     167             :   fprintf(stderr, "--- %s : %s",
     168           0 :           kResourceTypeName[mType], mName);
     169           0 :   aOut += BlockingResourceBase::kResourceTypeName[mType];
     170           0 :   aOut += " : ";
     171           0 :   aOut += mName;
     172             : 
     173           0 :   bool acquired = IsAcquired();
     174             : 
     175           0 :   if (acquired) {
     176           0 :     fputs(" (currently acquired)\n", stderr);
     177           0 :     aOut += " (currently acquired)\n";
     178             :   }
     179             : 
     180           0 :   fputs(" calling context\n", stderr);
     181             : #ifdef MOZ_CALLSTACK_DISABLED
     182           0 :   fputs("  [stack trace unavailable]\n", stderr);
     183             : #else
     184             :   const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
     185             : 
     186             :   WalkTheStackCodeAddressService addressService;
     187             : 
     188             :   for (uint32_t i = 0; i < state.Length(); i++) {
     189             :     const size_t kMaxLength = 1024;
     190             :     char buffer[kMaxLength];
     191             :     addressService.GetLocation(i + 1, state[i], buffer, kMaxLength);
     192             :     const char* fmt = "    %s\n";
     193             :     aOut.AppendLiteral("    ");
     194             :     aOut.Append(buffer);
     195             :     aOut.AppendLiteral("\n");
     196             :     fprintf(stderr, fmt, buffer);
     197             :   }
     198             : 
     199             : #endif
     200             : 
     201           0 :   return acquired;
     202             : }
     203             : 
     204             : 
     205        1932 : BlockingResourceBase::BlockingResourceBase(
     206             :     const char* aName,
     207        1932 :     BlockingResourceBase::BlockingResourceType aType)
     208             :   : mName(aName)
     209             :   , mType(aType)
     210             : #ifdef MOZ_CALLSTACK_DISABLED
     211        1932 :   , mAcquired(false)
     212             : #else
     213             :   , mAcquired()
     214             : #endif
     215             : {
     216        1932 :   MOZ_ASSERT(mName, "Name must be nonnull");
     217             :   // PR_CallOnce guaranatees that InitStatics is called in a
     218             :   // thread-safe way
     219        1932 :   if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
     220           0 :     MOZ_CRASH("can't initialize blocking resource static members");
     221             :   }
     222             : 
     223        1932 :   mChainPrev = 0;
     224        1932 :   sDeadlockDetector->Add(this);
     225        1934 : }
     226             : 
     227             : 
     228        1000 : BlockingResourceBase::~BlockingResourceBase()
     229             : {
     230             :   // we don't check for really obviously bad things like freeing
     231             :   // Mutexes while they're still locked.  it is assumed that the
     232             :   // base class, or its underlying primitive, will check for such
     233             :   // stupid mistakes.
     234         500 :   mChainPrev = 0;             // racy only for stupidly buggy client code
     235         500 :   if (sDeadlockDetector) {
     236         500 :     sDeadlockDetector->Remove(this);
     237             :   }
     238         500 : }
     239             : 
     240             : 
     241             : size_t
     242           0 : BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf)
     243             : {
     244           0 :   return sDeadlockDetector ?
     245           0 :       sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) : 0;
     246             : }
     247             : 
     248             : 
     249             : PRStatus
     250           3 : BlockingResourceBase::InitStatics()
     251             : {
     252           3 :   PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0);
     253           3 :   sDeadlockDetector = new DDT();
     254           3 :   if (!sDeadlockDetector) {
     255           0 :     MOZ_CRASH("can't allocate deadlock detector");
     256             :   }
     257           3 :   return PR_SUCCESS;
     258             : }
     259             : 
     260             : 
     261             : void
     262           0 : BlockingResourceBase::Shutdown()
     263             : {
     264           0 :   delete sDeadlockDetector;
     265           0 :   sDeadlockDetector = 0;
     266           0 : }
     267             : 
     268             : 
     269             : void
     270       93714 : BlockingResourceBase::CheckAcquire()
     271             : {
     272       93714 :   if (mType == eCondVar) {
     273           0 :     NS_NOTYETIMPLEMENTED(
     274             :       "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
     275           0 :     return;
     276             :   }
     277             : 
     278       93714 :   BlockingResourceBase* chainFront = ResourceChainFront();
     279             :   nsAutoPtr<DDT::ResourceAcquisitionArray> cycle(
     280             :     sDeadlockDetector->CheckAcquisition(
     281       93705 :       chainFront ? chainFront : 0, this));
     282       93719 :   if (!cycle) {
     283       93720 :     return;
     284             :   }
     285             : 
     286             : #ifndef MOZ_CALLSTACK_DISABLED
     287             :   // Update the current stack before printing.
     288             :   GetStackTrace(mAcquired);
     289             : #endif
     290             : 
     291           0 :   fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
     292           0 :   nsAutoCString out("Potential deadlock detected:\n");
     293           0 :   bool maybeImminent = PrintCycle(cycle, out);
     294             : 
     295           0 :   if (maybeImminent) {
     296           0 :     fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
     297           0 :     out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
     298             :   } else {
     299             :     fputs("\nDeadlock may happen for some other execution\n\n",
     300           0 :           stderr);
     301           0 :     out.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
     302             :   }
     303             : 
     304             :   // Only error out if we think a deadlock is imminent.
     305           0 :   if (maybeImminent) {
     306           0 :     NS_ERROR(out.get());
     307             :   } else {
     308           0 :     NS_WARNING(out.get());
     309             :   }
     310             : }
     311             : 
     312             : 
     313             : void
     314       93740 : BlockingResourceBase::Acquire()
     315             : {
     316       93740 :   if (mType == eCondVar) {
     317           0 :     NS_NOTYETIMPLEMENTED(
     318             :       "FIXME bug 456272: annots. to allow Acquire()ing condvars");
     319           0 :     return;
     320             :   }
     321       93740 :   NS_ASSERTION(!IsAcquired(),
     322             :                "reacquiring already acquired resource");
     323             : 
     324       93741 :   ResourceChainAppend(ResourceChainFront());
     325             : 
     326             : #ifdef MOZ_CALLSTACK_DISABLED
     327       93718 :   mAcquired = true;
     328             : #else
     329             :   // Take a stack snapshot.
     330             :   GetStackTrace(mAcquired);
     331             :   if (mFirstSeen.IsEmpty()) {
     332             :     mFirstSeen = mAcquired;
     333             :   }
     334             : #endif
     335             : }
     336             : 
     337             : 
     338             : void
     339       93662 : BlockingResourceBase::Release()
     340             : {
     341       93662 :   if (mType == eCondVar) {
     342           0 :     NS_NOTYETIMPLEMENTED(
     343             :       "FIXME bug 456272: annots. to allow Release()ing condvars");
     344           0 :     return;
     345             :   }
     346             : 
     347       93662 :   BlockingResourceBase* chainFront = ResourceChainFront();
     348       93660 :   NS_ASSERTION(chainFront && IsAcquired(),
     349             :                "Release()ing something that hasn't been Acquire()ed");
     350             : 
     351       93660 :   if (chainFront == this) {
     352       93660 :     ResourceChainRemove();
     353             :   } else {
     354             :     // not an error, but makes code hard to reason about.
     355           0 :     NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
     356           0 :     nsCString tmp;
     357           0 :     Print(tmp);
     358             : 
     359             :     // remove this resource from wherever it lives in the chain
     360             :     // we walk backwards in order of acquisition:
     361             :     //  (1)  ...node<-prev<-curr...
     362             :     //              /     /
     363             :     //  (2)  ...prev<-curr...
     364           0 :     BlockingResourceBase* curr = chainFront;
     365           0 :     BlockingResourceBase* prev = nullptr;
     366           0 :     while (curr && (prev = curr->mChainPrev) && (prev != this)) {
     367           0 :       curr = prev;
     368             :     }
     369           0 :     if (prev == this) {
     370           0 :       curr->mChainPrev = prev->mChainPrev;
     371             :     }
     372             :   }
     373             : 
     374       93652 :   ClearAcquisitionState();
     375             : }
     376             : 
     377             : 
     378             : //
     379             : // Debug implementation of (OffTheBooks)Mutex
     380             : void
     381       85468 : OffTheBooksMutex::Lock()
     382             : {
     383       85468 :   CheckAcquire();
     384       85474 :   this->lock();
     385       85492 :   mOwningThread = PR_GetCurrentThread();
     386       85493 :   Acquire();
     387       85470 : }
     388             : 
     389             : void
     390       85415 : OffTheBooksMutex::Unlock()
     391             : {
     392       85415 :   Release();
     393       85405 :   mOwningThread = nullptr;
     394       85405 :   this->unlock();
     395       85435 : }
     396             : 
     397             : void
     398       52271 : OffTheBooksMutex::AssertCurrentThreadOwns() const
     399             : {
     400       52271 :   MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
     401       52273 : }
     402             : 
     403             : //
     404             : // Debug implementation of RWLock
     405             : //
     406             : 
     407             : void
     408           0 : RWLock::ReadLock()
     409             : {
     410             :   // All we want to ensure here is that we're not attempting to acquire the
     411             :   // read lock while this thread is holding the write lock.
     412           0 :   CheckAcquire();
     413           0 :   this->ReadLockInternal();
     414           0 :   MOZ_ASSERT(mOwningThread == nullptr);
     415           0 : }
     416             : 
     417             : void
     418           0 : RWLock::ReadUnlock()
     419             : {
     420           0 :   MOZ_ASSERT(mOwningThread == nullptr);
     421           0 :   this->ReadUnlockInternal();
     422           0 : }
     423             : 
     424             : void
     425           0 : RWLock::WriteLock()
     426             : {
     427           0 :   CheckAcquire();
     428           0 :   this->WriteLockInternal();
     429           0 :   mOwningThread = PR_GetCurrentThread();
     430           0 :   Acquire();
     431           0 : }
     432             : 
     433             : void
     434           0 : RWLock::WriteUnlock()
     435             : {
     436           0 :   Release();
     437           0 :   mOwningThread = nullptr;
     438           0 :   this->WriteUnlockInternal();
     439           0 : }
     440             : 
     441             : //
     442             : // Debug implementation of ReentrantMonitor
     443             : void
     444        8615 : ReentrantMonitor::Enter()
     445             : {
     446        8615 :   BlockingResourceBase* chainFront = ResourceChainFront();
     447             : 
     448             :   // the code below implements monitor reentrancy semantics
     449             : 
     450        8615 :   if (this == chainFront) {
     451             :     // immediately re-entered the monitor: acceptable
     452         404 :     PR_EnterMonitor(mReentrantMonitor);
     453         404 :     ++mEntryCount;
     454         404 :     return;
     455             :   }
     456             : 
     457             :   // this is sort of a hack around not recording the thread that
     458             :   // owns this monitor
     459        8211 :   if (chainFront) {
     460         814 :     for (BlockingResourceBase* br = ResourceChainPrev(chainFront);
     461         814 :          br;
     462             :          br = ResourceChainPrev(br)) {
     463           8 :       if (br == this) {
     464             :         NS_WARNING(
     465           0 :           "Re-entering ReentrantMonitor after acquiring other resources.");
     466             : 
     467             :         // show the caller why this is potentially bad
     468           0 :         CheckAcquire();
     469             : 
     470           0 :         PR_EnterMonitor(mReentrantMonitor);
     471           0 :         ++mEntryCount;
     472           0 :         return;
     473             :       }
     474             :     }
     475             :   }
     476             : 
     477        8211 :   CheckAcquire();
     478        8211 :   PR_EnterMonitor(mReentrantMonitor);
     479        8211 :   NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
     480        8211 :   Acquire();       // protected by mReentrantMonitor
     481        8211 :   mEntryCount = 1;
     482             : }
     483             : 
     484             : void
     485        8615 : ReentrantMonitor::Exit()
     486             : {
     487        8615 :   if (--mEntryCount == 0) {
     488        8211 :     Release();              // protected by mReentrantMonitor
     489             :   }
     490        8615 :   PRStatus status = PR_ExitMonitor(mReentrantMonitor);
     491        8615 :   NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
     492        8615 : }
     493             : 
     494             : nsresult
     495          56 : ReentrantMonitor::Wait(PRIntervalTime aInterval)
     496             : {
     497          56 :   AssertCurrentThreadIn();
     498             : 
     499             :   // save monitor state and reset it to empty
     500          56 :   int32_t savedEntryCount = mEntryCount;
     501          56 :   AcquisitionState savedAcquisitionState = GetAcquisitionState();
     502          56 :   BlockingResourceBase* savedChainPrev = mChainPrev;
     503          56 :   mEntryCount = 0;
     504          56 :   ClearAcquisitionState();
     505          56 :   mChainPrev = 0;
     506             : 
     507             :   nsresult rv;
     508             : #if defined(MOZILLA_INTERNAL_API)
     509             :   {
     510         112 :     AutoProfilerThreadSleep sleep;
     511             : #endif //MOZILLA_INTERNAL_API
     512             : 
     513             :     // give up the monitor until we're back from Wait()
     514          56 :     rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK :
     515             :                                                                NS_ERROR_FAILURE;
     516             : 
     517             : #if defined(MOZILLA_INTERNAL_API)
     518             :   }
     519             : #endif //MOZILLA_INTERNAL_API
     520             : 
     521             :   // restore saved state
     522          56 :   mEntryCount = savedEntryCount;
     523          56 :   SetAcquisitionState(savedAcquisitionState);
     524          56 :   mChainPrev = savedChainPrev;
     525             : 
     526          56 :   return rv;
     527             : }
     528             : 
     529             : 
     530             : //
     531             : // Debug implementation of CondVar
     532             : nsresult
     533         696 : CondVar::Wait(PRIntervalTime aInterval)
     534             : {
     535         696 :   AssertCurrentThreadOwnsMutex();
     536             : 
     537             :   // save mutex state and reset to empty
     538         696 :   AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
     539         696 :   BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
     540         696 :   PRThread* savedOwningThread = mLock->mOwningThread;
     541         696 :   mLock->ClearAcquisitionState();
     542         696 :   mLock->mChainPrev = 0;
     543         696 :   mLock->mOwningThread = nullptr;
     544             : 
     545             :   // give up mutex until we're back from Wait()
     546         696 :   if (aInterval == PR_INTERVAL_NO_TIMEOUT) {
     547         317 :     mImpl.wait(*mLock);
     548             :   } else {
     549         379 :     mImpl.wait_for(*mLock, TimeDuration::FromMilliseconds(double(aInterval)));
     550             :   }
     551             : 
     552             :   // restore saved state
     553         639 :   mLock->SetAcquisitionState(savedAcquisitionState);
     554         638 :   mLock->mChainPrev = savedChainPrev;
     555         638 :   mLock->mOwningThread = savedOwningThread;
     556             : 
     557         638 :   return NS_OK;
     558             : }
     559             : 
     560             : #endif // ifdef DEBUG
     561             : 
     562             : 
     563             : } // namespace mozilla

Generated by: LCOV version 1.13