1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/BlockingResourceBase.h"
10 # include "prthread.h"
12 # ifndef MOZ_CALLSTACK_DISABLED
13 # include "CodeAddressService.h"
14 # include "nsHashKeys.h"
15 # include "mozilla/StackWalk.h"
16 # include "nsTHashtable.h"
19 # include "mozilla/Attributes.h"
20 # include "mozilla/CondVar.h"
21 # include "mozilla/DeadlockDetector.h"
22 # include "mozilla/RecursiveMutex.h"
23 # include "mozilla/ReentrantMonitor.h"
24 # include "mozilla/Mutex.h"
25 # include "mozilla/RWLock.h"
26 # include "mozilla/UniquePtr.h"
28 # if defined(MOZILLA_INTERNAL_API)
29 # include "mozilla/ProfilerThreadSleep.h"
30 # endif // MOZILLA_INTERNAL_API
36 // BlockingResourceBase implementation
40 const char* const BlockingResourceBase::kResourceTypeName
[] = {
41 // needs to be kept in sync with BlockingResourceType
42 "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"};
46 PRCallOnceType
BlockingResourceBase::sCallOnce
;
47 MOZ_THREAD_LOCAL(BlockingResourceBase
*)
48 BlockingResourceBase::sResourceAcqnChainFront
;
49 BlockingResourceBase::DDT
* BlockingResourceBase::sDeadlockDetector
;
51 void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber
, void* aPc
,
52 void* aSp
, void* aClosure
) {
53 # ifndef MOZ_CALLSTACK_DISABLED
54 AcquisitionState
* state
= (AcquisitionState
*)aClosure
;
55 state
->ref().AppendElement(aPc
);
59 void BlockingResourceBase::GetStackTrace(AcquisitionState
& aState
,
60 const void* aFirstFramePC
) {
61 # ifndef MOZ_CALLSTACK_DISABLED
64 // ...and create a new one; this also puts the state to 'acquired' status
65 // regardless of whether we obtain a stack trace or not.
68 MozStackWalk(StackWalkCallback
, aFirstFramePC
, kAcquisitionStateStackSize
,
75 * Append to |aOut| detailed information about the circular
76 * dependency in |aCycle|. Returns true if it *appears* that this
77 * cycle may represent an imminent deadlock, but this is merely a
78 * heuristic; the value returned may be a false positive or false
81 * *NOT* thread safe. Calls |Print()|.
83 * FIXME bug 456272 hack alert: because we can't write call
84 * contexts into strings, all info is written to stderr, but only
85 * some info is written into |aOut|
87 static bool PrintCycle(
88 const BlockingResourceBase::DDT::ResourceAcquisitionArray
& aCycle
,
90 NS_ASSERTION(aCycle
.Length() > 1, "need > 1 element for cycle!");
92 bool maybeImminent
= true;
94 fputs("=== Cyclical dependency starts at\n", stderr
);
95 aOut
+= "Cyclical dependency starts at\n";
97 const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type res
=
99 maybeImminent
&= res
->Print(aOut
);
101 BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i
;
102 BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len
=
104 const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type
* it
=
105 1 + aCycle
.Elements();
106 for (i
= 1; i
< len
- 1; ++i
, ++it
) {
107 fputs("\n--- Next dependency:\n", stderr
);
108 aOut
+= "\nNext dependency:\n";
110 maybeImminent
&= (*it
)->Print(aOut
);
113 fputs("\n=== Cycle completed at\n", stderr
);
114 aOut
+= "Cycle completed at\n";
117 return maybeImminent
;
120 bool BlockingResourceBase::Print(nsACString
& aOut
) const {
121 fprintf(stderr
, "--- %s : %s", kResourceTypeName
[mType
], mName
);
122 aOut
+= BlockingResourceBase::kResourceTypeName
[mType
];
126 bool acquired
= IsAcquired();
129 fputs(" (currently acquired)\n", stderr
);
130 aOut
+= " (currently acquired)\n";
133 fputs(" calling context\n", stderr
);
134 # ifdef MOZ_CALLSTACK_DISABLED
135 fputs(" [stack trace unavailable]\n", stderr
);
137 const AcquisitionState
& state
= acquired
? mAcquired
: mFirstSeen
;
139 CodeAddressService
<> addressService
;
141 for (uint32_t i
= 0; i
< state
.ref().Length(); i
++) {
142 const size_t kMaxLength
= 1024;
143 char buffer
[kMaxLength
];
144 addressService
.GetLocation(i
+ 1, state
.ref()[i
], buffer
, kMaxLength
);
145 const char* fmt
= " %s\n";
146 aOut
.AppendLiteral(" ");
148 aOut
.AppendLiteral("\n");
149 fprintf(stderr
, fmt
, buffer
);
157 BlockingResourceBase::BlockingResourceBase(
158 const char* aName
, BlockingResourceBase::BlockingResourceType aType
)
161 # ifdef MOZ_CALLSTACK_DISABLED
169 MOZ_ASSERT(mName
, "Name must be nonnull");
170 // PR_CallOnce guaranatees that InitStatics is called in a
172 if (PR_SUCCESS
!= PR_CallOnce(&sCallOnce
, InitStatics
)) {
173 MOZ_CRASH("can't initialize blocking resource static members");
177 sDeadlockDetector
->Add(this);
180 BlockingResourceBase::~BlockingResourceBase() {
181 // we don't check for really obviously bad things like freeing
182 // Mutexes while they're still locked. it is assumed that the
183 // base class, or its underlying primitive, will check for such
185 mChainPrev
= 0; // racy only for stupidly buggy client code
186 if (sDeadlockDetector
) {
187 sDeadlockDetector
->Remove(this);
191 size_t BlockingResourceBase::SizeOfDeadlockDetector(
192 MallocSizeOf aMallocSizeOf
) {
193 return sDeadlockDetector
194 ? sDeadlockDetector
->SizeOfIncludingThis(aMallocSizeOf
)
198 PRStatus
BlockingResourceBase::InitStatics() {
199 MOZ_ASSERT(sResourceAcqnChainFront
.init());
200 sDeadlockDetector
= new DDT();
201 if (!sDeadlockDetector
) {
202 MOZ_CRASH("can't allocate deadlock detector");
207 void BlockingResourceBase::Shutdown() {
208 delete sDeadlockDetector
;
209 sDeadlockDetector
= 0;
212 MOZ_NEVER_INLINE
void BlockingResourceBase::CheckAcquire() {
213 if (mType
== eCondVar
) {
214 MOZ_ASSERT_UNREACHABLE(
215 "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
219 BlockingResourceBase
* chainFront
= ResourceChainFront();
220 mozilla::UniquePtr
<DDT::ResourceAcquisitionArray
> cycle(
221 sDeadlockDetector
->CheckAcquisition(chainFront
? chainFront
: 0, this));
226 # ifndef MOZ_CALLSTACK_DISABLED
227 // Update the current stack before printing.
228 GetStackTrace(mAcquired
, CallerPC());
231 fputs("###!!! ERROR: Potential deadlock detected:\n", stderr
);
232 nsAutoCString
out("Potential deadlock detected:\n");
233 bool maybeImminent
= PrintCycle(*cycle
, out
);
236 fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr
);
237 out
.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
239 fputs("\nDeadlock may happen for some other execution\n\n", stderr
);
240 out
.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
243 // Only error out if we think a deadlock is imminent.
247 NS_WARNING(out
.get());
251 MOZ_NEVER_INLINE
void BlockingResourceBase::Acquire() {
252 if (mType
== eCondVar
) {
253 MOZ_ASSERT_UNREACHABLE(
254 "FIXME bug 456272: annots. to allow Acquire()ing condvars");
257 NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");
259 ResourceChainAppend(ResourceChainFront());
261 # ifdef MOZ_CALLSTACK_DISABLED
264 // Take a stack snapshot.
265 GetStackTrace(mAcquired
, CallerPC());
266 MOZ_ASSERT(IsAcquired());
269 mFirstSeen
= mAcquired
.map(
270 [](AcquisitionState::ValueType
& state
) { return state
.Clone(); });
275 void BlockingResourceBase::Release() {
276 if (mType
== eCondVar
) {
277 MOZ_ASSERT_UNREACHABLE(
278 "FIXME bug 456272: annots. to allow Release()ing condvars");
282 BlockingResourceBase
* chainFront
= ResourceChainFront();
283 NS_ASSERTION(chainFront
&& IsAcquired(),
284 "Release()ing something that hasn't been Acquire()ed");
286 if (chainFront
== this) {
287 ResourceChainRemove();
289 // remove this resource from wherever it lives in the chain
290 // we walk backwards in order of acquisition:
291 // (1) ...node<-prev<-curr...
293 // (2) ...prev<-curr...
294 BlockingResourceBase
* curr
= chainFront
;
295 BlockingResourceBase
* prev
= nullptr;
296 while (curr
&& (prev
= curr
->mChainPrev
) && (prev
!= this)) {
300 curr
->mChainPrev
= prev
->mChainPrev
;
304 ClearAcquisitionState();
308 // Debug implementation of (OffTheBooks)Mutex
309 void OffTheBooksMutex::Lock() {
312 mOwningThread
= PR_GetCurrentThread();
316 bool OffTheBooksMutex::TryLock() {
317 bool locked
= this->tryLock();
319 mOwningThread
= PR_GetCurrentThread();
325 void OffTheBooksMutex::Unlock() {
327 mOwningThread
= nullptr;
331 void OffTheBooksMutex::AssertCurrentThreadOwns() const {
332 MOZ_ASSERT(IsAcquired() && mOwningThread
== PR_GetCurrentThread());
336 // Debug implementation of RWLock
339 bool RWLock::TryReadLock() {
340 bool locked
= this->detail::RWLockImpl::tryReadLock();
341 MOZ_ASSERT_IF(locked
, mOwningThread
== nullptr);
345 void RWLock::ReadLock() {
346 // All we want to ensure here is that we're not attempting to acquire the
347 // read lock while this thread is holding the write lock.
349 this->detail::RWLockImpl::readLock();
350 MOZ_ASSERT(mOwningThread
== nullptr);
353 void RWLock::ReadUnlock() {
354 MOZ_ASSERT(mOwningThread
== nullptr);
355 this->detail::RWLockImpl::readUnlock();
358 bool RWLock::TryWriteLock() {
359 bool locked
= this->detail::RWLockImpl::tryWriteLock();
361 mOwningThread
= PR_GetCurrentThread();
367 void RWLock::WriteLock() {
369 this->detail::RWLockImpl::writeLock();
370 mOwningThread
= PR_GetCurrentThread();
374 void RWLock::WriteUnlock() {
376 mOwningThread
= nullptr;
377 this->detail::RWLockImpl::writeUnlock();
381 // Debug implementation of ReentrantMonitor
382 void ReentrantMonitor::Enter() {
383 BlockingResourceBase
* chainFront
= ResourceChainFront();
385 // the code below implements monitor reentrancy semantics
387 if (this == chainFront
) {
388 // immediately re-entered the monitor: acceptable
389 PR_EnterMonitor(mReentrantMonitor
);
394 // this is sort of a hack around not recording the thread that
397 for (BlockingResourceBase
* br
= ResourceChainPrev(chainFront
); br
;
398 br
= ResourceChainPrev(br
)) {
401 "Re-entering ReentrantMonitor after acquiring other resources.");
403 // show the caller why this is potentially bad
406 PR_EnterMonitor(mReentrantMonitor
);
414 PR_EnterMonitor(mReentrantMonitor
);
415 NS_ASSERTION(mEntryCount
== 0, "ReentrantMonitor isn't free!");
416 Acquire(); // protected by mReentrantMonitor
420 void ReentrantMonitor::Exit() {
421 if (--mEntryCount
== 0) {
422 Release(); // protected by mReentrantMonitor
424 PRStatus status
= PR_ExitMonitor(mReentrantMonitor
);
425 NS_ASSERTION(PR_SUCCESS
== status
, "bad ReentrantMonitor::Exit()");
428 nsresult
ReentrantMonitor::Wait(PRIntervalTime aInterval
) {
429 AssertCurrentThreadIn();
431 // save monitor state and reset it to empty
432 int32_t savedEntryCount
= mEntryCount
;
433 AcquisitionState savedAcquisitionState
= TakeAcquisitionState();
434 BlockingResourceBase
* savedChainPrev
= mChainPrev
;
440 # if defined(MOZILLA_INTERNAL_API)
441 AUTO_PROFILER_THREAD_SLEEP
;
443 // give up the monitor until we're back from Wait()
444 rv
= PR_Wait(mReentrantMonitor
, aInterval
) == PR_SUCCESS
? NS_OK
448 // restore saved state
449 mEntryCount
= savedEntryCount
;
450 SetAcquisitionState(std::move(savedAcquisitionState
));
451 mChainPrev
= savedChainPrev
;
457 // Debug implementation of RecursiveMutex
458 void RecursiveMutex::Lock() {
459 BlockingResourceBase
* chainFront
= ResourceChainFront();
461 // the code below implements mutex reentrancy semantics
463 if (this == chainFront
) {
464 // immediately re-entered the mutex: acceptable
470 // this is sort of a hack around not recording the thread that
473 for (BlockingResourceBase
* br
= ResourceChainPrev(chainFront
); br
;
474 br
= ResourceChainPrev(br
)) {
477 "Re-entering RecursiveMutex after acquiring other resources.");
479 // show the caller why this is potentially bad
491 NS_ASSERTION(mEntryCount
== 0, "RecursiveMutex isn't free!");
492 Acquire(); // protected by us
493 mOwningThread
= PR_GetCurrentThread();
497 void RecursiveMutex::Unlock() {
498 if (--mEntryCount
== 0) {
499 Release(); // protected by us
500 mOwningThread
= nullptr;
505 void RecursiveMutex::AssertCurrentThreadIn() const {
506 MOZ_ASSERT(IsAcquired() && mOwningThread
== PR_GetCurrentThread());
510 // Debug implementation of CondVar
511 void OffTheBooksCondVar::Wait() {
512 // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code
514 CVStatus status
= Wait(TimeDuration::Forever());
515 MOZ_ASSERT(status
== CVStatus::NoTimeout
);
518 CVStatus
OffTheBooksCondVar::Wait(TimeDuration aDuration
) {
519 AssertCurrentThreadOwnsMutex();
521 // save mutex state and reset to empty
522 AcquisitionState savedAcquisitionState
= mLock
->TakeAcquisitionState();
523 BlockingResourceBase
* savedChainPrev
= mLock
->mChainPrev
;
524 PRThread
* savedOwningThread
= mLock
->mOwningThread
;
525 mLock
->mChainPrev
= 0;
526 mLock
->mOwningThread
= nullptr;
528 // give up mutex until we're back from Wait()
531 # if defined(MOZILLA_INTERNAL_API)
532 AUTO_PROFILER_THREAD_SLEEP
;
534 status
= mImpl
.wait_for(*mLock
, aDuration
);
537 // restore saved state
538 mLock
->SetAcquisitionState(std::move(savedAcquisitionState
));
539 mLock
->mChainPrev
= savedChainPrev
;
540 mLock
->mOwningThread
= savedOwningThread
;
545 #endif // ifdef DEBUG
547 } // namespace mozilla