gecko-dev/xpcom/threads/BlockingResourceBase.cpp
Nathan Froyd 5fd1d453a9 Bug 1312087 - part 1 - move mozilla::{Mutex,CondVar} to use mozglue locking primitives; r=erahm
This change moves us away from NSPR primitives for our primary
synchronization primitives.  We're still using PRMonitor for
ReentrantMonitor, however.

The benefits of this change:

* Slightly faster, as we don't have to deal with some of NSPR's overhead;

* Smaller datatypes.  On POSIX platforms in particular, PRLock is
  enormous. PRCondVar also has some unnecessary overhead.

* Less dynamic memory allocation.  Out of necessity, Mutex and CondVar
  allocated the NSPR data structures they needed, which lead to
  unnecessary checks for failure.

  While sizeof(Mutex) and sizeof(CondVar) may get bigger, since they're
  embedding structures now, the total memory usage should be less.

* Less NSPR usage.  This shouldn't need any explanation.
2017-03-21 10:20:36 -05:00

524 lines
13 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"
#include "nsAutoPtr.h"
#ifndef MOZ_CALLSTACK_DISABLED
#include "CodeAddressService.h"
#include "nsHashKeys.h"
#include "mozilla/StackWalk.h"
#include "nsTHashtable.h"
#endif
#include "mozilla/CondVar.h"
#include "mozilla/DeadlockDetector.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Mutex.h"
#if defined(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;
void
BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
void* aSp, void* aClosure)
{
#ifndef MOZ_CALLSTACK_DISABLED
AcquisitionState* state = (AcquisitionState*)aClosure;
state->AppendElement(aPc);
#endif
}
void
BlockingResourceBase::GetStackTrace(AcquisitionState& aState)
{
#ifndef MOZ_CALLSTACK_DISABLED
// Skip this function and the calling function.
const uint32_t kSkipFrames = 2;
aState.Clear();
// NB: Ignore the return value, there's nothing useful we can do if this
// this fails.
MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
#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|
*/
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::elem_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::elem_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;
}
#ifndef MOZ_CALLSTACK_DISABLED
struct CodeAddressServiceLock final
{
static void Unlock() { }
static void Lock() { }
static bool IsLocked() { return true; }
};
struct CodeAddressServiceStringAlloc final
{
static char* copy(const char* aString) { return ::strdup(aString); }
static void free(char* aString) { ::free(aString); }
};
class CodeAddressServiceStringTable final
{
public:
CodeAddressServiceStringTable() : mSet(32) {}
const char* Intern(const char* aString)
{
nsCharPtrHashKey* e = mSet.PutEntry(aString);
return e->GetKey();
}
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
return mSet.SizeOfExcludingThis(aMallocSizeOf);
}
private:
typedef nsTHashtable<nsCharPtrHashKey> StringSet;
StringSet mSet;
};
typedef CodeAddressService<CodeAddressServiceStringTable,
CodeAddressServiceStringAlloc,
CodeAddressServiceLock> WalkTheStackCodeAddressService;
#endif
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;
WalkTheStackCodeAddressService addressService;
for (uint32_t i = 0; i < state.Length(); i++) {
const size_t kMaxLength = 1024;
char buffer[kMaxLength];
addressService.GetLocation(i + 1, state[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()
{
PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0);
sDeadlockDetector = new DDT();
if (!sDeadlockDetector) {
MOZ_CRASH("can't allocate deadlock detector");
}
return PR_SUCCESS;
}
void
BlockingResourceBase::Shutdown()
{
delete sDeadlockDetector;
sDeadlockDetector = 0;
}
void
BlockingResourceBase::CheckAcquire()
{
if (mType == eCondVar) {
NS_NOTYETIMPLEMENTED(
"FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
return;
}
BlockingResourceBase* chainFront = ResourceChainFront();
nsAutoPtr<DDT::ResourceAcquisitionArray> cycle(
sDeadlockDetector->CheckAcquisition(
chainFront ? chainFront : 0, this));
if (!cycle) {
return;
}
#ifndef MOZ_CALLSTACK_DISABLED
// Update the current stack before printing.
GetStackTrace(mAcquired);
#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");
}
// 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()
{
if (mType == eCondVar) {
NS_NOTYETIMPLEMENTED(
"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);
if (mFirstSeen.IsEmpty()) {
mFirstSeen = mAcquired;
}
#endif
}
void
BlockingResourceBase::Release()
{
if (mType == eCondVar) {
NS_NOTYETIMPLEMENTED(
"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 {
// not an error, but makes code hard to reason about.
NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
nsCString tmp;
Print(tmp);
// 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();
}
void
OffTheBooksMutex::Unlock()
{
Release();
mOwningThread = nullptr;
this->unlock();
}
void
OffTheBooksMutex::AssertCurrentThreadOwns() const
{
MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
}
//
// 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 = GetAcquisitionState();
BlockingResourceBase* savedChainPrev = mChainPrev;
mEntryCount = 0;
ClearAcquisitionState();
mChainPrev = 0;
nsresult rv;
#if defined(MOZILLA_INTERNAL_API)
{
GeckoProfilerThreadSleepRAII sleep;
#endif //MOZILLA_INTERNAL_API
// give up the monitor until we're back from Wait()
rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK :
NS_ERROR_FAILURE;
#if defined(MOZILLA_INTERNAL_API)
}
#endif //MOZILLA_INTERNAL_API
// restore saved state
mEntryCount = savedEntryCount;
SetAcquisitionState(savedAcquisitionState);
mChainPrev = savedChainPrev;
return rv;
}
//
// Debug implementation of CondVar
nsresult
CondVar::Wait(PRIntervalTime aInterval)
{
AssertCurrentThreadOwnsMutex();
// save mutex state and reset to empty
AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
PRThread* savedOwningThread = mLock->mOwningThread;
mLock->ClearAcquisitionState();
mLock->mChainPrev = 0;
mLock->mOwningThread = nullptr;
// give up mutex until we're back from Wait()
if (aInterval == PR_INTERVAL_NO_TIMEOUT) {
mImpl.wait(*mLock);
} else {
mImpl.wait_for(*mLock, TimeDuration::FromMilliseconds(double(aInterval)));
}
// restore saved state
mLock->SetAcquisitionState(savedAcquisitionState);
mLock->mChainPrev = savedChainPrev;
mLock->mOwningThread = savedOwningThread;
return NS_OK;
}
#endif // ifdef DEBUG
} // namespace mozilla