forked from mirrors/gecko-dev
		
	 7743d4fd62
			
		
	
	
		7743d4fd62
		
	
	
	
	
		
			
			There weren't that many uses of the existing typedef, so it seemed like it might be worthwhile to just replace all uses of the previous typedef with the stl-like one. Differential Revision: https://phabricator.services.mozilla.com/D142705
		
			
				
	
	
		
			547 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			547 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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"
 | |
| 
 | |
| #  ifndef MOZ_CALLSTACK_DISABLED
 | |
| #    include "CodeAddressService.h"
 | |
| #    include "nsHashKeys.h"
 | |
| #    include "mozilla/StackWalk.h"
 | |
| #    include "nsTHashtable.h"
 | |
| #  endif
 | |
| 
 | |
| #  include "mozilla/Attributes.h"
 | |
| #  include "mozilla/CondVar.h"
 | |
| #  include "mozilla/DeadlockDetector.h"
 | |
| #  include "mozilla/RecursiveMutex.h"
 | |
| #  include "mozilla/ReentrantMonitor.h"
 | |
| #  include "mozilla/Mutex.h"
 | |
| #  include "mozilla/RWLock.h"
 | |
| #  include "mozilla/UniquePtr.h"
 | |
| 
 | |
| #  if defined(MOZILLA_INTERNAL_API)
 | |
| #    include "mozilla/ProfilerThreadSleep.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", "RecursiveMutex"};
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 
 | |
| PRCallOnceType BlockingResourceBase::sCallOnce;
 | |
| MOZ_THREAD_LOCAL(BlockingResourceBase*)
 | |
| BlockingResourceBase::sResourceAcqnChainFront;
 | |
| 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->ref().AppendElement(aPc);
 | |
| #  endif
 | |
| }
 | |
| 
 | |
| void BlockingResourceBase::GetStackTrace(AcquisitionState& aState,
 | |
|                                          const void* aFirstFramePC) {
 | |
| #  ifndef MOZ_CALLSTACK_DISABLED
 | |
|   // Clear the array...
 | |
|   aState.reset();
 | |
|   // ...and create a new one; this also puts the state to 'acquired' status
 | |
|   // regardless of whether we obtain a stack trace or not.
 | |
|   aState.emplace();
 | |
| 
 | |
|   MozStackWalk(StackWalkCallback, aFirstFramePC, kAcquisitionStateStackSize,
 | |
|                aState.ptr());
 | |
| #  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|
 | |
|  */
 | |
| static 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::value_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::value_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;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
|   CodeAddressService<> addressService;
 | |
| 
 | |
|   for (uint32_t i = 0; i < state.ref().Length(); i++) {
 | |
|     const size_t kMaxLength = 1024;
 | |
|     char buffer[kMaxLength];
 | |
|     addressService.GetLocation(i + 1, state.ref()[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)) {
 | |
|     MOZ_CRASH("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() {
 | |
|   MOZ_ASSERT(sResourceAcqnChainFront.init());
 | |
|   sDeadlockDetector = new DDT();
 | |
|   if (!sDeadlockDetector) {
 | |
|     MOZ_CRASH("can't allocate deadlock detector");
 | |
|   }
 | |
|   return PR_SUCCESS;
 | |
| }
 | |
| 
 | |
| void BlockingResourceBase::Shutdown() {
 | |
|   delete sDeadlockDetector;
 | |
|   sDeadlockDetector = 0;
 | |
| }
 | |
| 
 | |
| MOZ_NEVER_INLINE void BlockingResourceBase::CheckAcquire() {
 | |
|   if (mType == eCondVar) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   BlockingResourceBase* chainFront = ResourceChainFront();
 | |
|   mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle(
 | |
|       sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this));
 | |
|   if (!cycle) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #  ifndef MOZ_CALLSTACK_DISABLED
 | |
|   // Update the current stack before printing.
 | |
|   GetStackTrace(mAcquired, CallerPC());
 | |
| #  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");
 | |
|   }
 | |
| 
 | |
|   // Only error out if we think a deadlock is imminent.
 | |
|   if (maybeImminent) {
 | |
|     NS_ERROR(out.get());
 | |
|   } else {
 | |
|     NS_WARNING(out.get());
 | |
|   }
 | |
| }
 | |
| 
 | |
| MOZ_NEVER_INLINE void BlockingResourceBase::Acquire() {
 | |
|   if (mType == eCondVar) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "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, CallerPC());
 | |
|   MOZ_ASSERT(IsAcquired());
 | |
| 
 | |
|   if (!mFirstSeen) {
 | |
|     mFirstSeen = mAcquired.map(
 | |
|         [](AcquisitionState::ValueType& state) { return state.Clone(); });
 | |
|   }
 | |
| #  endif
 | |
| }
 | |
| 
 | |
| void BlockingResourceBase::Release() {
 | |
|   if (mType == eCondVar) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "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 {
 | |
|     // 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();
 | |
|   this->lock();
 | |
|   mOwningThread = PR_GetCurrentThread();
 | |
|   Acquire();
 | |
| }
 | |
| 
 | |
| bool OffTheBooksMutex::TryLock() {
 | |
|   bool locked = this->tryLock();
 | |
|   if (locked) {
 | |
|     mOwningThread = PR_GetCurrentThread();
 | |
|     Acquire();
 | |
|   }
 | |
|   return locked;
 | |
| }
 | |
| 
 | |
| void OffTheBooksMutex::Unlock() {
 | |
|   Release();
 | |
|   mOwningThread = nullptr;
 | |
|   this->unlock();
 | |
| }
 | |
| 
 | |
| void OffTheBooksMutex::AssertCurrentThreadOwns() const {
 | |
|   MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
 | |
| }
 | |
| 
 | |
| //
 | |
| // Debug implementation of RWLock
 | |
| //
 | |
| 
 | |
| bool RWLock::TryReadLock() {
 | |
|   bool locked = this->detail::RWLockImpl::tryReadLock();
 | |
|   MOZ_ASSERT_IF(locked, mOwningThread == nullptr);
 | |
|   return locked;
 | |
| }
 | |
| 
 | |
| void RWLock::ReadLock() {
 | |
|   // All we want to ensure here is that we're not attempting to acquire the
 | |
|   // read lock while this thread is holding the write lock.
 | |
|   CheckAcquire();
 | |
|   this->detail::RWLockImpl::readLock();
 | |
|   MOZ_ASSERT(mOwningThread == nullptr);
 | |
| }
 | |
| 
 | |
| void RWLock::ReadUnlock() {
 | |
|   MOZ_ASSERT(mOwningThread == nullptr);
 | |
|   this->detail::RWLockImpl::readUnlock();
 | |
| }
 | |
| 
 | |
| bool RWLock::TryWriteLock() {
 | |
|   bool locked = this->detail::RWLockImpl::tryWriteLock();
 | |
|   if (locked) {
 | |
|     mOwningThread = PR_GetCurrentThread();
 | |
|     Acquire();
 | |
|   }
 | |
|   return locked;
 | |
| }
 | |
| 
 | |
| void RWLock::WriteLock() {
 | |
|   CheckAcquire();
 | |
|   this->detail::RWLockImpl::writeLock();
 | |
|   mOwningThread = PR_GetCurrentThread();
 | |
|   Acquire();
 | |
| }
 | |
| 
 | |
| void RWLock::WriteUnlock() {
 | |
|   Release();
 | |
|   mOwningThread = nullptr;
 | |
|   this->detail::RWLockImpl::writeUnlock();
 | |
| }
 | |
| 
 | |
| //
 | |
| // 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 = TakeAcquisitionState();
 | |
|   BlockingResourceBase* savedChainPrev = mChainPrev;
 | |
|   mEntryCount = 0;
 | |
|   mChainPrev = 0;
 | |
| 
 | |
|   nsresult rv;
 | |
|   {
 | |
| #  if defined(MOZILLA_INTERNAL_API)
 | |
|     AUTO_PROFILER_THREAD_SLEEP;
 | |
| #  endif
 | |
|     // give up the monitor until we're back from Wait()
 | |
|     rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK
 | |
|                                                              : NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // restore saved state
 | |
|   mEntryCount = savedEntryCount;
 | |
|   SetAcquisitionState(std::move(savedAcquisitionState));
 | |
|   mChainPrev = savedChainPrev;
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| //
 | |
| // Debug implementation of RecursiveMutex
 | |
| void RecursiveMutex::Lock() {
 | |
|   BlockingResourceBase* chainFront = ResourceChainFront();
 | |
| 
 | |
|   // the code below implements mutex reentrancy semantics
 | |
| 
 | |
|   if (this == chainFront) {
 | |
|     // immediately re-entered the mutex: acceptable
 | |
|     LockInternal();
 | |
|     ++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 RecursiveMutex after acquiring other resources.");
 | |
| 
 | |
|         // show the caller why this is potentially bad
 | |
|         CheckAcquire();
 | |
| 
 | |
|         LockInternal();
 | |
|         ++mEntryCount;
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   CheckAcquire();
 | |
|   LockInternal();
 | |
|   NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
 | |
|   Acquire();  // protected by us
 | |
|   mOwningThread = PR_GetCurrentThread();
 | |
|   mEntryCount = 1;
 | |
| }
 | |
| 
 | |
| void RecursiveMutex::Unlock() {
 | |
|   if (--mEntryCount == 0) {
 | |
|     Release();  // protected by us
 | |
|     mOwningThread = nullptr;
 | |
|   }
 | |
|   UnlockInternal();
 | |
| }
 | |
| 
 | |
| void RecursiveMutex::AssertCurrentThreadIn() const {
 | |
|   MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
 | |
| }
 | |
| 
 | |
| //
 | |
| // Debug implementation of CondVar
 | |
| void OffTheBooksCondVar::Wait() {
 | |
|   // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code
 | |
|   // duplication.
 | |
|   CVStatus status = Wait(TimeDuration::Forever());
 | |
|   MOZ_ASSERT(status == CVStatus::NoTimeout);
 | |
| }
 | |
| 
 | |
| CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) {
 | |
|   AssertCurrentThreadOwnsMutex();
 | |
| 
 | |
|   // save mutex state and reset to empty
 | |
|   AcquisitionState savedAcquisitionState = mLock->TakeAcquisitionState();
 | |
|   BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
 | |
|   PRThread* savedOwningThread = mLock->mOwningThread;
 | |
|   mLock->mChainPrev = 0;
 | |
|   mLock->mOwningThread = nullptr;
 | |
| 
 | |
|   // give up mutex until we're back from Wait()
 | |
|   CVStatus status;
 | |
|   {
 | |
| #  if defined(MOZILLA_INTERNAL_API)
 | |
|     AUTO_PROFILER_THREAD_SLEEP;
 | |
| #  endif
 | |
|     status = mImpl.wait_for(*mLock, aDuration);
 | |
|   }
 | |
| 
 | |
|   // restore saved state
 | |
|   mLock->SetAcquisitionState(std::move(savedAcquisitionState));
 | |
|   mLock->mChainPrev = savedChainPrev;
 | |
|   mLock->mOwningThread = savedOwningThread;
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| #endif  // ifdef DEBUG
 | |
| 
 | |
| }  // namespace mozilla
 |