mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
364 lines
10 KiB
C++
364 lines
10 KiB
C++
/* -*- 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<DDT::ResourceAcquisitionArray> 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
|