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