gecko-dev/xpcom/glue/BlockingResourceBase.cpp
2009-05-04 21:57:15 -07:00

386 lines
12 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: sw=4 ts=4 et :
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Jones <jones.chris.g@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "mozilla/BlockingResourceBase.h"
#ifdef DEBUG
#include "nsAutoPtr.h"
#include "mozilla/CondVar.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#endif // ifdef DEBUG
namespace mozilla {
//
// BlockingResourceBase implementation
//
// static members
const char* const BlockingResourceBase::kResourceTypeName[] =
{
// needs to be kept in sync with BlockingResourceType
"Mutex", "Monitor", "CondVar"
};
#ifdef DEBUG
PRCallOnceType BlockingResourceBase::sCallOnce;
PRUintn BlockingResourceBase::sResourceAcqnChainFrontTPI = (PRUintn)-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)", 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);
if (!mDDEntry)
NS_RUNTIMEABORT("can't allocated deadlock detector entry");
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<DDT::ResourceAcquisitionArray> cycle(
sDeadlockDetector.CheckAcquisition(
chainFront ? chainFront->mDDEntry : 0, mDDEntry,
aCallContext));
if (!cycle)
return;
fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
nsCAutoString out("Potential deadlock detected:\n");
bool maybeImminent = PrintCycle(cycle, out);
if (maybeImminent) {
fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
out.Append("\n###!!! Deadlock may happen NOW!\n\n");
} else {
fputs("\nDeadlock may happen for some other execution\n\n",
stderr);
out.Append("\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 = nsnull;
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 += "Next 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 Mutex
void
Mutex::Lock()
{
CallStack callContext = CallStack();
CheckAcquire(callContext);
PR_Lock(mLock);
Acquire(callContext); // protected by mLock
}
void
Mutex::Unlock()
{
Release(); // protected by mLock
PRStatus status = PR_Unlock(mLock);
NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()");
}
//
// Debug implementation of Monitor
void
Monitor::Enter()
{
BlockingResourceBase* chainFront = ResourceChainFront();
// the code below implements monitor reentrancy semantics
if (this == chainFront) {
// immediately re-entered the monitor: acceptable
PR_EnterMonitor(mMonitor);
++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 Monitor after acquiring other resources.\n"
"At calling context\n");
GetAcquisitionContext().Print(stderr);
// show the caller why this is potentially bad
CheckAcquire(callContext);
PR_EnterMonitor(mMonitor);
++mEntryCount;
return;
}
}
}
CheckAcquire(callContext);
PR_EnterMonitor(mMonitor);
NS_ASSERTION(0 == mEntryCount, "Monitor isn't free!");
Acquire(callContext); // protected by mMonitor
mEntryCount = 1;
}
void
Monitor::Exit()
{
if (0 == --mEntryCount)
Release(); // protected by mMonitor
PRStatus status = PR_ExitMonitor(mMonitor);
NS_ASSERTION(PR_SUCCESS == status, "bad Monitor::Exit()");
}
nsresult
Monitor::Wait(PRIntervalTime interval)
{
AssertCurrentThreadIn();
// save monitor state and reset it to empty
PRInt32 savedEntryCount = mEntryCount;
CallStack savedAcquisitionContext = GetAcquisitionContext();
BlockingResourceBase* savedChainPrev = mChainPrev;
mEntryCount = 0;
SetAcquisitionContext(CallStack::kNone);
mChainPrev = 0;
// give up the monitor until we're back from Wait()
nsresult rv =
PR_Wait(mMonitor, interval) == PR_SUCCESS ?
NS_OK : NS_ERROR_FAILURE;
// 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