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 void BlockingResourceBase::AssertSafeToProcessEventLoop() {
192 for (BlockingResourceBase
* res
= ResourceChainFront(); res
!= nullptr;
193 res
= res
->mChainPrev
) {
194 // It's more OK to hold reentrant/recursive types across nested event loops,
195 // as it should hopefully not lead to deadlocks, so allow them for now.
196 if (res
->mType
== eReentrantMonitor
|| res
->mType
== eRecursiveMutex
) {
200 // Allow specifically named mutexes to be held across a nested event loop,
201 // in order to get this check landed.
202 nsDependentCString
name(res
->mName
);
203 if (name
!= "GraphRunner::mMonitor"_ns
|| // Bug 1928770
204 name
!= "SourceSurfaceSkia::mChangeMutex"_ns
) { // Bug 1928772
208 // This is a potential deadlock, as ProcessNextEvent could process any
209 // runnable on the current thread, including one which could attempt to
210 // acquire the mutex.
212 "###!!! ERROR: Potential deadlock detected:\n"
213 "=== Holding blocking resource across call to ProcessNextEvent\n",
216 "Potential deadlock detected:\n"
217 "Holding blocking resource across call to ProcessNextEvent\n");
220 MOZ_CRASH_UNSAFE_PRINTF("Holding '%s' across call to ProcessNextEvent",
225 size_t BlockingResourceBase::SizeOfDeadlockDetector(
226 MallocSizeOf aMallocSizeOf
) {
227 return sDeadlockDetector
228 ? sDeadlockDetector
->SizeOfIncludingThis(aMallocSizeOf
)
232 PRStatus
BlockingResourceBase::InitStatics() {
233 MOZ_ASSERT(sResourceAcqnChainFront
.init());
234 sDeadlockDetector
= new DDT();
235 if (!sDeadlockDetector
) {
236 MOZ_CRASH("can't allocate deadlock detector");
241 void BlockingResourceBase::Shutdown() {
242 delete sDeadlockDetector
;
243 sDeadlockDetector
= 0;
246 MOZ_NEVER_INLINE
void BlockingResourceBase::CheckAcquire() {
247 if (mType
== eCondVar
) {
248 MOZ_ASSERT_UNREACHABLE(
249 "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
253 BlockingResourceBase
* chainFront
= ResourceChainFront();
254 mozilla::UniquePtr
<DDT::ResourceAcquisitionArray
> cycle(
255 sDeadlockDetector
->CheckAcquisition(chainFront
? chainFront
: 0, this));
260 # ifndef MOZ_CALLSTACK_DISABLED
261 // Update the current stack before printing.
262 GetStackTrace(mAcquired
, CallerPC());
265 fputs("###!!! ERROR: Potential deadlock detected:\n", stderr
);
266 nsAutoCString
out("Potential deadlock detected:\n");
267 bool maybeImminent
= PrintCycle(*cycle
, out
);
270 fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr
);
271 out
.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
273 fputs("\nDeadlock may happen for some other execution\n\n", stderr
);
274 out
.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
277 // Only error out if we think a deadlock is imminent.
281 NS_WARNING(out
.get());
285 MOZ_NEVER_INLINE
void BlockingResourceBase::Acquire() {
286 if (mType
== eCondVar
) {
287 MOZ_ASSERT_UNREACHABLE(
288 "FIXME bug 456272: annots. to allow Acquire()ing condvars");
291 NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");
293 ResourceChainAppend(ResourceChainFront());
295 # ifdef MOZ_CALLSTACK_DISABLED
298 // Take a stack snapshot.
299 GetStackTrace(mAcquired
, CallerPC());
300 MOZ_ASSERT(IsAcquired());
303 mFirstSeen
= mAcquired
.map(
304 [](AcquisitionState::ValueType
& state
) { return state
.Clone(); });
309 void BlockingResourceBase::Release() {
310 if (mType
== eCondVar
) {
311 MOZ_ASSERT_UNREACHABLE(
312 "FIXME bug 456272: annots. to allow Release()ing condvars");
316 BlockingResourceBase
* chainFront
= ResourceChainFront();
317 NS_ASSERTION(chainFront
&& IsAcquired(),
318 "Release()ing something that hasn't been Acquire()ed");
320 if (chainFront
== this) {
321 ResourceChainRemove();
323 // remove this resource from wherever it lives in the chain
324 // we walk backwards in order of acquisition:
325 // (1) ...node<-prev<-curr...
327 // (2) ...prev<-curr...
328 BlockingResourceBase
* curr
= chainFront
;
329 BlockingResourceBase
* prev
= nullptr;
330 while (curr
&& (prev
= curr
->mChainPrev
) && (prev
!= this)) {
334 curr
->mChainPrev
= prev
->mChainPrev
;
338 ClearAcquisitionState();
342 // Debug implementation of (OffTheBooks)Mutex
343 void OffTheBooksMutex::Lock() {
346 mOwningThread
= PR_GetCurrentThread();
350 bool OffTheBooksMutex::TryLock() {
351 bool locked
= this->tryLock();
353 mOwningThread
= PR_GetCurrentThread();
359 void OffTheBooksMutex::Unlock() {
361 mOwningThread
= nullptr;
365 void OffTheBooksMutex::AssertCurrentThreadOwns() const {
366 MOZ_ASSERT(IsAcquired() && mOwningThread
== PR_GetCurrentThread());
370 // Debug implementation of RWLock
373 bool RWLock::TryReadLock() {
374 bool locked
= this->detail::RWLockImpl::tryReadLock();
375 MOZ_ASSERT_IF(locked
, mOwningThread
== nullptr);
379 void RWLock::ReadLock() {
380 // All we want to ensure here is that we're not attempting to acquire the
381 // read lock while this thread is holding the write lock.
383 this->detail::RWLockImpl::readLock();
384 MOZ_ASSERT(mOwningThread
== nullptr);
387 void RWLock::ReadUnlock() {
388 MOZ_ASSERT(mOwningThread
== nullptr);
389 this->detail::RWLockImpl::readUnlock();
392 bool RWLock::TryWriteLock() {
393 bool locked
= this->detail::RWLockImpl::tryWriteLock();
395 mOwningThread
= PR_GetCurrentThread();
401 void RWLock::WriteLock() {
403 this->detail::RWLockImpl::writeLock();
404 mOwningThread
= PR_GetCurrentThread();
408 void RWLock::WriteUnlock() {
410 mOwningThread
= nullptr;
411 this->detail::RWLockImpl::writeUnlock();
415 // Debug implementation of ReentrantMonitor
416 void ReentrantMonitor::Enter() {
417 BlockingResourceBase
* chainFront
= ResourceChainFront();
419 // the code below implements monitor reentrancy semantics
421 if (this == chainFront
) {
422 // immediately re-entered the monitor: acceptable
423 PR_EnterMonitor(mReentrantMonitor
);
428 // this is sort of a hack around not recording the thread that
431 for (BlockingResourceBase
* br
= ResourceChainPrev(chainFront
); br
;
432 br
= ResourceChainPrev(br
)) {
435 "Re-entering ReentrantMonitor after acquiring other resources.");
437 // show the caller why this is potentially bad
440 PR_EnterMonitor(mReentrantMonitor
);
448 PR_EnterMonitor(mReentrantMonitor
);
449 NS_ASSERTION(mEntryCount
== 0, "ReentrantMonitor isn't free!");
450 Acquire(); // protected by mReentrantMonitor
454 void ReentrantMonitor::Exit() {
455 if (--mEntryCount
== 0) {
456 Release(); // protected by mReentrantMonitor
458 PRStatus status
= PR_ExitMonitor(mReentrantMonitor
);
459 NS_ASSERTION(PR_SUCCESS
== status
, "bad ReentrantMonitor::Exit()");
462 nsresult
ReentrantMonitor::Wait(PRIntervalTime aInterval
) {
463 AssertCurrentThreadIn();
465 // save monitor state and reset it to empty
466 int32_t savedEntryCount
= mEntryCount
;
467 AcquisitionState savedAcquisitionState
= TakeAcquisitionState();
468 BlockingResourceBase
* savedChainPrev
= mChainPrev
;
474 # if defined(MOZILLA_INTERNAL_API)
475 AUTO_PROFILER_THREAD_SLEEP
;
477 // give up the monitor until we're back from Wait()
478 rv
= PR_Wait(mReentrantMonitor
, aInterval
) == PR_SUCCESS
? NS_OK
482 // restore saved state
483 mEntryCount
= savedEntryCount
;
484 SetAcquisitionState(std::move(savedAcquisitionState
));
485 mChainPrev
= savedChainPrev
;
491 // Debug implementation of RecursiveMutex
492 void RecursiveMutex::Lock() {
493 BlockingResourceBase
* chainFront
= ResourceChainFront();
495 // the code below implements mutex reentrancy semantics
497 if (this == chainFront
) {
498 // immediately re-entered the mutex: acceptable
504 // this is sort of a hack around not recording the thread that
507 for (BlockingResourceBase
* br
= ResourceChainPrev(chainFront
); br
;
508 br
= ResourceChainPrev(br
)) {
511 "Re-entering RecursiveMutex after acquiring other resources.");
513 // show the caller why this is potentially bad
525 NS_ASSERTION(mEntryCount
== 0, "RecursiveMutex isn't free!");
526 Acquire(); // protected by us
527 mOwningThread
= PR_GetCurrentThread();
531 void RecursiveMutex::Unlock() {
532 if (--mEntryCount
== 0) {
533 Release(); // protected by us
534 mOwningThread
= nullptr;
539 void RecursiveMutex::AssertCurrentThreadIn() const {
540 MOZ_ASSERT(IsAcquired() && mOwningThread
== PR_GetCurrentThread());
544 // Debug implementation of CondVar
545 void OffTheBooksCondVar::Wait() {
546 // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code
548 CVStatus status
= Wait(TimeDuration::Forever());
549 MOZ_ASSERT(status
== CVStatus::NoTimeout
);
552 CVStatus
OffTheBooksCondVar::Wait(TimeDuration aDuration
) {
553 AssertCurrentThreadOwnsMutex();
555 // save mutex state and reset to empty
556 AcquisitionState savedAcquisitionState
= mLock
->TakeAcquisitionState();
557 BlockingResourceBase
* savedChainPrev
= mLock
->mChainPrev
;
558 PRThread
* savedOwningThread
= mLock
->mOwningThread
;
559 mLock
->mChainPrev
= 0;
560 mLock
->mOwningThread
= nullptr;
562 // give up mutex until we're back from Wait()
565 # if defined(MOZILLA_INTERNAL_API)
566 AUTO_PROFILER_THREAD_SLEEP
;
568 status
= mImpl
.wait_for(*mLock
, aDuration
);
571 // restore saved state
572 mLock
->SetAcquisitionState(std::move(savedAcquisitionState
));
573 mLock
->mChainPrev
= savedChainPrev
;
574 mLock
->mOwningThread
= savedOwningThread
;
579 #endif // ifdef DEBUG
581 } // namespace mozilla