Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / threads / BlockingResourceBase.cpp
blob2d5c9a9b63ce937014ad20d967e3dbf430c6845c
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"
9 #ifdef DEBUG
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"
17 # endif
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
32 #endif // ifdef DEBUG
34 namespace mozilla {
36 // BlockingResourceBase implementation
39 // static members
40 const char* const BlockingResourceBase::kResourceTypeName[] = {
41 // needs to be kept in sync with BlockingResourceType
42 "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"};
44 #ifdef DEBUG
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);
56 # endif
59 void BlockingResourceBase::GetStackTrace(AcquisitionState& aState,
60 const void* aFirstFramePC) {
61 # ifndef MOZ_CALLSTACK_DISABLED
62 // Clear the array...
63 aState.reset();
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.
66 aState.emplace();
68 MozStackWalk(StackWalkCallback, aFirstFramePC, kAcquisitionStateStackSize,
69 aState.ptr());
70 # endif
73 /**
74 * PrintCycle
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
79 * negative.
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,
89 nsACString& aOut) {
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 =
98 aCycle.ElementAt(0);
99 maybeImminent &= res->Print(aOut);
101 BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
102 BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
103 aCycle.Length();
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";
115 (*it)->Print(aOut);
117 return maybeImminent;
120 bool BlockingResourceBase::Print(nsACString& aOut) const {
121 fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName);
122 aOut += BlockingResourceBase::kResourceTypeName[mType];
123 aOut += " : ";
124 aOut += mName;
126 bool acquired = IsAcquired();
128 if (acquired) {
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);
136 # else
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(" ");
147 aOut.Append(buffer);
148 aOut.AppendLiteral("\n");
149 fprintf(stderr, fmt, buffer);
152 # endif
154 return acquired;
157 BlockingResourceBase::BlockingResourceBase(
158 const char* aName, BlockingResourceBase::BlockingResourceType aType)
159 : mName(aName),
160 mType(aType)
161 # ifdef MOZ_CALLSTACK_DISABLED
163 mAcquired(false)
164 # else
166 mAcquired()
167 # endif
169 MOZ_ASSERT(mName, "Name must be nonnull");
170 // PR_CallOnce guaranatees that InitStatics is called in a
171 // thread-safe way
172 if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
173 MOZ_CRASH("can't initialize blocking resource static members");
176 mChainPrev = 0;
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
184 // stupid mistakes.
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) {
197 continue;
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
205 continue;
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.
211 fputs(
212 "###!!! ERROR: Potential deadlock detected:\n"
213 "=== Holding blocking resource across call to ProcessNextEvent\n",
214 stderr);
215 nsAutoCString out(
216 "Potential deadlock detected:\n"
217 "Holding blocking resource across call to ProcessNextEvent\n");
218 res->Print(out);
219 NS_ERROR(out.get());
220 MOZ_CRASH_UNSAFE_PRINTF("Holding '%s' across call to ProcessNextEvent",
221 res->mName);
225 size_t BlockingResourceBase::SizeOfDeadlockDetector(
226 MallocSizeOf aMallocSizeOf) {
227 return sDeadlockDetector
228 ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf)
229 : 0;
232 PRStatus BlockingResourceBase::InitStatics() {
233 MOZ_ASSERT(sResourceAcqnChainFront.init());
234 sDeadlockDetector = new DDT();
235 if (!sDeadlockDetector) {
236 MOZ_CRASH("can't allocate deadlock detector");
238 return PR_SUCCESS;
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");
250 return;
253 BlockingResourceBase* chainFront = ResourceChainFront();
254 mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle(
255 sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this));
256 if (!cycle) {
257 return;
260 # ifndef MOZ_CALLSTACK_DISABLED
261 // Update the current stack before printing.
262 GetStackTrace(mAcquired, CallerPC());
263 # endif
265 fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
266 nsAutoCString out("Potential deadlock detected:\n");
267 bool maybeImminent = PrintCycle(*cycle, out);
269 if (maybeImminent) {
270 fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
271 out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
272 } else {
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.
278 if (maybeImminent) {
279 NS_ERROR(out.get());
280 } else {
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");
289 return;
291 NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");
293 ResourceChainAppend(ResourceChainFront());
295 # ifdef MOZ_CALLSTACK_DISABLED
296 mAcquired = true;
297 # else
298 // Take a stack snapshot.
299 GetStackTrace(mAcquired, CallerPC());
300 MOZ_ASSERT(IsAcquired());
302 if (!mFirstSeen) {
303 mFirstSeen = mAcquired.map(
304 [](AcquisitionState::ValueType& state) { return state.Clone(); });
306 # endif
309 void BlockingResourceBase::Release() {
310 if (mType == eCondVar) {
311 MOZ_ASSERT_UNREACHABLE(
312 "FIXME bug 456272: annots. to allow Release()ing condvars");
313 return;
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();
322 } else {
323 // remove this resource from wherever it lives in the chain
324 // we walk backwards in order of acquisition:
325 // (1) ...node<-prev<-curr...
326 // / /
327 // (2) ...prev<-curr...
328 BlockingResourceBase* curr = chainFront;
329 BlockingResourceBase* prev = nullptr;
330 while (curr && (prev = curr->mChainPrev) && (prev != this)) {
331 curr = prev;
333 if (prev == this) {
334 curr->mChainPrev = prev->mChainPrev;
338 ClearAcquisitionState();
342 // Debug implementation of (OffTheBooks)Mutex
343 void OffTheBooksMutex::Lock() {
344 CheckAcquire();
345 this->lock();
346 mOwningThread = PR_GetCurrentThread();
347 Acquire();
350 bool OffTheBooksMutex::TryLock() {
351 bool locked = this->tryLock();
352 if (locked) {
353 mOwningThread = PR_GetCurrentThread();
354 Acquire();
356 return locked;
359 void OffTheBooksMutex::Unlock() {
360 Release();
361 mOwningThread = nullptr;
362 this->unlock();
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);
376 return locked;
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.
382 CheckAcquire();
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();
394 if (locked) {
395 mOwningThread = PR_GetCurrentThread();
396 Acquire();
398 return locked;
401 void RWLock::WriteLock() {
402 CheckAcquire();
403 this->detail::RWLockImpl::writeLock();
404 mOwningThread = PR_GetCurrentThread();
405 Acquire();
408 void RWLock::WriteUnlock() {
409 Release();
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);
424 ++mEntryCount;
425 return;
428 // this is sort of a hack around not recording the thread that
429 // owns this monitor
430 if (chainFront) {
431 for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
432 br = ResourceChainPrev(br)) {
433 if (br == this) {
434 NS_WARNING(
435 "Re-entering ReentrantMonitor after acquiring other resources.");
437 // show the caller why this is potentially bad
438 CheckAcquire();
440 PR_EnterMonitor(mReentrantMonitor);
441 ++mEntryCount;
442 return;
447 CheckAcquire();
448 PR_EnterMonitor(mReentrantMonitor);
449 NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
450 Acquire(); // protected by mReentrantMonitor
451 mEntryCount = 1;
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;
469 mEntryCount = 0;
470 mChainPrev = 0;
472 nsresult rv;
474 # if defined(MOZILLA_INTERNAL_API)
475 AUTO_PROFILER_THREAD_SLEEP;
476 # endif
477 // give up the monitor until we're back from Wait()
478 rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK
479 : NS_ERROR_FAILURE;
482 // restore saved state
483 mEntryCount = savedEntryCount;
484 SetAcquisitionState(std::move(savedAcquisitionState));
485 mChainPrev = savedChainPrev;
487 return rv;
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
499 LockInternal();
500 ++mEntryCount;
501 return;
504 // this is sort of a hack around not recording the thread that
505 // owns this monitor
506 if (chainFront) {
507 for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
508 br = ResourceChainPrev(br)) {
509 if (br == this) {
510 NS_WARNING(
511 "Re-entering RecursiveMutex after acquiring other resources.");
513 // show the caller why this is potentially bad
514 CheckAcquire();
516 LockInternal();
517 ++mEntryCount;
518 return;
523 CheckAcquire();
524 LockInternal();
525 NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
526 Acquire(); // protected by us
527 mOwningThread = PR_GetCurrentThread();
528 mEntryCount = 1;
531 void RecursiveMutex::Unlock() {
532 if (--mEntryCount == 0) {
533 Release(); // protected by us
534 mOwningThread = nullptr;
536 UnlockInternal();
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
547 // duplication.
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()
563 CVStatus status;
565 # if defined(MOZILLA_INTERNAL_API)
566 AUTO_PROFILER_THREAD_SLEEP;
567 # endif
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;
576 return status;
579 #endif // ifdef DEBUG
581 } // namespace mozilla