xpcom/glue/BlockingResourceBase.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/xpcom/glue/BlockingResourceBase.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,363 @@
     1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: sw=4 ts=4 et :
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/BlockingResourceBase.h"
    1.11 +
    1.12 +#ifdef DEBUG
    1.13 +#include "nsAutoPtr.h"
    1.14 +
    1.15 +#include "mozilla/CondVar.h"
    1.16 +#include "mozilla/ReentrantMonitor.h"
    1.17 +#include "mozilla/Mutex.h"
    1.18 +
    1.19 +#ifdef MOZILLA_INTERNAL_API
    1.20 +#include "GeckoProfiler.h"
    1.21 +#endif //MOZILLA_INTERNAL_API
    1.22 +
    1.23 +#endif // ifdef DEBUG
    1.24 +
    1.25 +namespace mozilla {
    1.26 +//
    1.27 +// BlockingResourceBase implementation
    1.28 +//
    1.29 +
    1.30 +// static members
    1.31 +const char* const BlockingResourceBase::kResourceTypeName[] =
    1.32 +{
    1.33 +    // needs to be kept in sync with BlockingResourceType
    1.34 +    "Mutex", "ReentrantMonitor", "CondVar"
    1.35 +};
    1.36 +
    1.37 +#ifdef DEBUG
    1.38 +
    1.39 +PRCallOnceType BlockingResourceBase::sCallOnce;
    1.40 +unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
    1.41 +BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
    1.42 +
    1.43 +bool
    1.44 +BlockingResourceBase::DeadlockDetectorEntry::Print(
    1.45 +    const DDT::ResourceAcquisition& aFirstSeen,
    1.46 +    nsACString& out,
    1.47 +    bool aPrintFirstSeenCx) const
    1.48 +{
    1.49 +    CallStack lastAcquisition = mAcquisitionContext; // RACY, but benign
    1.50 +    bool maybeCurrentlyAcquired = (CallStack::kNone != lastAcquisition);
    1.51 +    CallStack printAcquisition =
    1.52 +        (aPrintFirstSeenCx || !maybeCurrentlyAcquired) ?
    1.53 +            aFirstSeen.mCallContext : lastAcquisition;
    1.54 +
    1.55 +    fprintf(stderr, "--- %s : %s",
    1.56 +            kResourceTypeName[mType], mName);
    1.57 +    out += BlockingResourceBase::kResourceTypeName[mType];
    1.58 +    out += " : ";
    1.59 +    out += mName;
    1.60 +
    1.61 +    if (maybeCurrentlyAcquired) {
    1.62 +        fputs(" (currently acquired)\n", stderr);
    1.63 +        out += " (currently acquired)\n";
    1.64 +    }
    1.65 +
    1.66 +    fputs(" calling context\n", stderr);
    1.67 +    printAcquisition.Print(stderr);
    1.68 +
    1.69 +    return maybeCurrentlyAcquired;
    1.70 +}
    1.71 +
    1.72 +
    1.73 +BlockingResourceBase::BlockingResourceBase(
    1.74 +    const char* aName,
    1.75 +    BlockingResourceBase::BlockingResourceType aType)
    1.76 +{
    1.77 +    // PR_CallOnce guaranatees that InitStatics is called in a
    1.78 +    // thread-safe way
    1.79 +    if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics))
    1.80 +        NS_RUNTIMEABORT("can't initialize blocking resource static members");
    1.81 +
    1.82 +    mDDEntry = new BlockingResourceBase::DeadlockDetectorEntry(aName, aType);
    1.83 +    mChainPrev = 0;
    1.84 +    sDeadlockDetector->Add(mDDEntry);
    1.85 +}
    1.86 +
    1.87 +
    1.88 +BlockingResourceBase::~BlockingResourceBase()
    1.89 +{
    1.90 +    // we don't check for really obviously bad things like freeing
    1.91 +    // Mutexes while they're still locked.  it is assumed that the
    1.92 +    // base class, or its underlying primitive, will check for such
    1.93 +    // stupid mistakes.
    1.94 +    mChainPrev = 0;             // racy only for stupidly buggy client code
    1.95 +    mDDEntry = 0;               // owned by deadlock detector
    1.96 +}
    1.97 +
    1.98 +
    1.99 +void
   1.100 +BlockingResourceBase::CheckAcquire(const CallStack& aCallContext)
   1.101 +{
   1.102 +    if (eCondVar == mDDEntry->mType) {
   1.103 +        NS_NOTYETIMPLEMENTED(
   1.104 +            "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
   1.105 +        return;
   1.106 +    }
   1.107 +
   1.108 +    BlockingResourceBase* chainFront = ResourceChainFront();
   1.109 +    nsAutoPtr<DDT::ResourceAcquisitionArray> cycle(
   1.110 +        sDeadlockDetector->CheckAcquisition(
   1.111 +            chainFront ? chainFront->mDDEntry : 0, mDDEntry,
   1.112 +            aCallContext));
   1.113 +    if (!cycle)
   1.114 +        return;
   1.115 +
   1.116 +    fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
   1.117 +    nsAutoCString out("Potential deadlock detected:\n");
   1.118 +    bool maybeImminent = PrintCycle(cycle, out);
   1.119 +
   1.120 +    if (maybeImminent) {
   1.121 +        fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
   1.122 +        out.Append("\n###!!! Deadlock may happen NOW!\n\n");
   1.123 +    } else {
   1.124 +        fputs("\nDeadlock may happen for some other execution\n\n",
   1.125 +              stderr);
   1.126 +        out.Append("\nDeadlock may happen for some other execution\n\n");
   1.127 +    }
   1.128 +
   1.129 +    // XXX can customize behavior on whether we /think/ deadlock is
   1.130 +    // XXX about to happen.  for example:
   1.131 +    // XXX   if (maybeImminent)
   1.132 +    //           NS_RUNTIMEABORT(out.get());
   1.133 +    NS_ERROR(out.get());
   1.134 +}
   1.135 +
   1.136 +
   1.137 +void
   1.138 +BlockingResourceBase::Acquire(const CallStack& aCallContext)
   1.139 +{
   1.140 +    if (eCondVar == mDDEntry->mType) {
   1.141 +        NS_NOTYETIMPLEMENTED(
   1.142 +            "FIXME bug 456272: annots. to allow Acquire()ing condvars");
   1.143 +        return;
   1.144 +    }
   1.145 +    NS_ASSERTION(mDDEntry->mAcquisitionContext == CallStack::kNone,
   1.146 +                 "reacquiring already acquired resource");
   1.147 +
   1.148 +    ResourceChainAppend(ResourceChainFront());
   1.149 +    mDDEntry->mAcquisitionContext = aCallContext;
   1.150 +}
   1.151 +
   1.152 +
   1.153 +void
   1.154 +BlockingResourceBase::Release()
   1.155 +{
   1.156 +    if (eCondVar == mDDEntry->mType) {
   1.157 +        NS_NOTYETIMPLEMENTED(
   1.158 +            "FIXME bug 456272: annots. to allow Release()ing condvars");
   1.159 +        return;
   1.160 +    }
   1.161 +      
   1.162 +    BlockingResourceBase* chainFront = ResourceChainFront();
   1.163 +    NS_ASSERTION(chainFront
   1.164 +                 && CallStack::kNone != mDDEntry->mAcquisitionContext,
   1.165 +                 "Release()ing something that hasn't been Acquire()ed");
   1.166 +
   1.167 +    if (chainFront == this) {
   1.168 +        ResourceChainRemove();
   1.169 +    }
   1.170 +    else {
   1.171 +        // not an error, but makes code hard to reason about.
   1.172 +        NS_WARNING("Resource acquired at calling context\n");
   1.173 +        mDDEntry->mAcquisitionContext.Print(stderr);
   1.174 +        NS_WARNING("\nis being released in non-LIFO order; why?");
   1.175 +        
   1.176 +        // remove this resource from wherever it lives in the chain
   1.177 +        // we walk backwards in order of acquisition:
   1.178 +        //  (1)  ...node<-prev<-curr...
   1.179 +        //              /     /
   1.180 +        //  (2)  ...prev<-curr...
   1.181 +        BlockingResourceBase* curr = chainFront;
   1.182 +        BlockingResourceBase* prev = nullptr;
   1.183 +        while (curr && (prev = curr->mChainPrev) && (prev != this))
   1.184 +            curr = prev;
   1.185 +        if (prev == this)
   1.186 +            curr->mChainPrev = prev->mChainPrev;
   1.187 +    }
   1.188 +
   1.189 +    mDDEntry->mAcquisitionContext = CallStack::kNone;
   1.190 +}
   1.191 +
   1.192 +
   1.193 +bool
   1.194 +BlockingResourceBase::PrintCycle(const DDT::ResourceAcquisitionArray* aCycle,
   1.195 +                                 nsACString& out)
   1.196 +{
   1.197 +    NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!");
   1.198 +
   1.199 +    bool maybeImminent = true;
   1.200 +
   1.201 +    fputs("=== Cyclical dependency starts at\n", stderr);
   1.202 +    out += "Cyclical dependency starts at\n";
   1.203 +
   1.204 +    const DDT::ResourceAcquisition res = aCycle->ElementAt(0);
   1.205 +    maybeImminent &= res.mResource->Print(res, out);
   1.206 +
   1.207 +    DDT::ResourceAcquisitionArray::index_type i;
   1.208 +    DDT::ResourceAcquisitionArray::size_type len = aCycle->Length();
   1.209 +    const DDT::ResourceAcquisition* it = 1 + aCycle->Elements();
   1.210 +    for (i = 1; i < len - 1; ++i, ++it) {
   1.211 +        fputs("\n--- Next dependency:\n", stderr);
   1.212 +        out += "\nNext dependency:\n";
   1.213 +
   1.214 +        maybeImminent &= it->mResource->Print(*it, out);
   1.215 +    }
   1.216 +
   1.217 +    fputs("\n=== Cycle completed at\n", stderr);
   1.218 +    out += "Cycle completed at\n";
   1.219 +    it->mResource->Print(*it, out, true);
   1.220 +
   1.221 +    return maybeImminent;
   1.222 +}
   1.223 +
   1.224 +
   1.225 +//
   1.226 +// Debug implementation of (OffTheBooks)Mutex
   1.227 +void
   1.228 +OffTheBooksMutex::Lock()
   1.229 +{
   1.230 +    CallStack callContext = CallStack();
   1.231 +
   1.232 +    CheckAcquire(callContext);
   1.233 +    PR_Lock(mLock);
   1.234 +    Acquire(callContext);       // protected by mLock
   1.235 +}
   1.236 +
   1.237 +void
   1.238 +OffTheBooksMutex::Unlock()
   1.239 +{
   1.240 +    Release();                  // protected by mLock
   1.241 +    PRStatus status = PR_Unlock(mLock);
   1.242 +    NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()");
   1.243 +}
   1.244 +
   1.245 +
   1.246 +//
   1.247 +// Debug implementation of ReentrantMonitor
   1.248 +void
   1.249 +ReentrantMonitor::Enter()
   1.250 +{
   1.251 +    BlockingResourceBase* chainFront = ResourceChainFront();
   1.252 +
   1.253 +    // the code below implements monitor reentrancy semantics
   1.254 +
   1.255 +    if (this == chainFront) {
   1.256 +        // immediately re-entered the monitor: acceptable
   1.257 +        PR_EnterMonitor(mReentrantMonitor);
   1.258 +        ++mEntryCount;
   1.259 +        return;
   1.260 +    }
   1.261 +
   1.262 +    CallStack callContext = CallStack();
   1.263 +    
   1.264 +    // this is sort of a hack around not recording the thread that
   1.265 +    // owns this monitor
   1.266 +    if (chainFront) {
   1.267 +        for (BlockingResourceBase* br = ResourceChainPrev(chainFront);
   1.268 +             br;
   1.269 +             br = ResourceChainPrev(br)) {
   1.270 +            if (br == this) {
   1.271 +                NS_WARNING(
   1.272 +                    "Re-entering ReentrantMonitor after acquiring other resources.\n"
   1.273 +                    "At calling context\n");
   1.274 +                GetAcquisitionContext().Print(stderr);
   1.275 +
   1.276 +                // show the caller why this is potentially bad
   1.277 +                CheckAcquire(callContext);
   1.278 +
   1.279 +                PR_EnterMonitor(mReentrantMonitor);
   1.280 +                ++mEntryCount;
   1.281 +                return;
   1.282 +            }
   1.283 +        }
   1.284 +    }
   1.285 +
   1.286 +    CheckAcquire(callContext);
   1.287 +    PR_EnterMonitor(mReentrantMonitor);
   1.288 +    NS_ASSERTION(0 == mEntryCount, "ReentrantMonitor isn't free!");
   1.289 +    Acquire(callContext);       // protected by mReentrantMonitor
   1.290 +    mEntryCount = 1;
   1.291 +}
   1.292 +
   1.293 +void
   1.294 +ReentrantMonitor::Exit()
   1.295 +{
   1.296 +    if (0 == --mEntryCount)
   1.297 +        Release();              // protected by mReentrantMonitor
   1.298 +    PRStatus status = PR_ExitMonitor(mReentrantMonitor);
   1.299 +    NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
   1.300 +}
   1.301 +
   1.302 +nsresult
   1.303 +ReentrantMonitor::Wait(PRIntervalTime interval)
   1.304 +{
   1.305 +    AssertCurrentThreadIn();
   1.306 +
   1.307 +    // save monitor state and reset it to empty
   1.308 +    int32_t savedEntryCount = mEntryCount;
   1.309 +    CallStack savedAcquisitionContext = GetAcquisitionContext();
   1.310 +    BlockingResourceBase* savedChainPrev = mChainPrev;
   1.311 +    mEntryCount = 0;
   1.312 +    SetAcquisitionContext(CallStack::kNone);
   1.313 +    mChainPrev = 0;
   1.314 +
   1.315 +    nsresult rv;
   1.316 +#ifdef MOZILLA_INTERNAL_API
   1.317 +    {
   1.318 +        GeckoProfilerSleepRAII profiler_sleep;
   1.319 +#endif //MOZILLA_INTERNAL_API
   1.320 +
   1.321 +    // give up the monitor until we're back from Wait()
   1.322 +    rv = PR_Wait(mReentrantMonitor, interval) == PR_SUCCESS ?
   1.323 +            NS_OK : NS_ERROR_FAILURE;
   1.324 +
   1.325 +#ifdef MOZILLA_INTERNAL_API
   1.326 +    }
   1.327 +#endif //MOZILLA_INTERNAL_API
   1.328 +
   1.329 +    // restore saved state
   1.330 +    mEntryCount = savedEntryCount;
   1.331 +    SetAcquisitionContext(savedAcquisitionContext);
   1.332 +    mChainPrev = savedChainPrev;
   1.333 +
   1.334 +    return rv;
   1.335 +}
   1.336 +
   1.337 +
   1.338 +//
   1.339 +// Debug implementation of CondVar
   1.340 +nsresult
   1.341 +CondVar::Wait(PRIntervalTime interval)
   1.342 +{
   1.343 +    AssertCurrentThreadOwnsMutex();
   1.344 +
   1.345 +    // save mutex state and reset to empty
   1.346 +    CallStack savedAcquisitionContext = mLock->GetAcquisitionContext();
   1.347 +    BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
   1.348 +    mLock->SetAcquisitionContext(CallStack::kNone);
   1.349 +    mLock->mChainPrev = 0;
   1.350 +
   1.351 +    // give up mutex until we're back from Wait()
   1.352 +    nsresult rv =
   1.353 +        PR_WaitCondVar(mCvar, interval) == PR_SUCCESS ?
   1.354 +            NS_OK : NS_ERROR_FAILURE;
   1.355 +
   1.356 +    // restore saved state
   1.357 +    mLock->SetAcquisitionContext(savedAcquisitionContext);
   1.358 +    mLock->mChainPrev = savedChainPrev;
   1.359 +
   1.360 +    return rv;
   1.361 +}
   1.362 +
   1.363 +#endif // ifdef DEBUG
   1.364 +
   1.365 +
   1.366 +} // namespace mozilla

mercurial