Backed out 3 changesets (bug 1854934) for causing build bustages on widget/windows...
[gecko.git] / xpcom / threads / BlockingResourceBase.cpp
blobc2ba82e07aede7995834458ba39e607c0f328632
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 size_t BlockingResourceBase::SizeOfDeadlockDetector(
192 MallocSizeOf aMallocSizeOf) {
193 return sDeadlockDetector
194 ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf)
195 : 0;
198 PRStatus BlockingResourceBase::InitStatics() {
199 MOZ_ASSERT(sResourceAcqnChainFront.init());
200 sDeadlockDetector = new DDT();
201 if (!sDeadlockDetector) {
202 MOZ_CRASH("can't allocate deadlock detector");
204 return PR_SUCCESS;
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");
216 return;
219 BlockingResourceBase* chainFront = ResourceChainFront();
220 mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle(
221 sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this));
222 if (!cycle) {
223 return;
226 # ifndef MOZ_CALLSTACK_DISABLED
227 // Update the current stack before printing.
228 GetStackTrace(mAcquired, CallerPC());
229 # endif
231 fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
232 nsAutoCString out("Potential deadlock detected:\n");
233 bool maybeImminent = PrintCycle(*cycle, out);
235 if (maybeImminent) {
236 fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
237 out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
238 } else {
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.
244 if (maybeImminent) {
245 NS_ERROR(out.get());
246 } else {
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");
255 return;
257 NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");
259 ResourceChainAppend(ResourceChainFront());
261 # ifdef MOZ_CALLSTACK_DISABLED
262 mAcquired = true;
263 # else
264 // Take a stack snapshot.
265 GetStackTrace(mAcquired, CallerPC());
266 MOZ_ASSERT(IsAcquired());
268 if (!mFirstSeen) {
269 mFirstSeen = mAcquired.map(
270 [](AcquisitionState::ValueType& state) { return state.Clone(); });
272 # endif
275 void BlockingResourceBase::Release() {
276 if (mType == eCondVar) {
277 MOZ_ASSERT_UNREACHABLE(
278 "FIXME bug 456272: annots. to allow Release()ing condvars");
279 return;
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();
288 } else {
289 // remove this resource from wherever it lives in the chain
290 // we walk backwards in order of acquisition:
291 // (1) ...node<-prev<-curr...
292 // / /
293 // (2) ...prev<-curr...
294 BlockingResourceBase* curr = chainFront;
295 BlockingResourceBase* prev = nullptr;
296 while (curr && (prev = curr->mChainPrev) && (prev != this)) {
297 curr = prev;
299 if (prev == this) {
300 curr->mChainPrev = prev->mChainPrev;
304 ClearAcquisitionState();
308 // Debug implementation of (OffTheBooks)Mutex
309 void OffTheBooksMutex::Lock() {
310 CheckAcquire();
311 this->lock();
312 mOwningThread = PR_GetCurrentThread();
313 Acquire();
316 bool OffTheBooksMutex::TryLock() {
317 bool locked = this->tryLock();
318 if (locked) {
319 mOwningThread = PR_GetCurrentThread();
320 Acquire();
322 return locked;
325 void OffTheBooksMutex::Unlock() {
326 Release();
327 mOwningThread = nullptr;
328 this->unlock();
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);
342 return locked;
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.
348 CheckAcquire();
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();
360 if (locked) {
361 mOwningThread = PR_GetCurrentThread();
362 Acquire();
364 return locked;
367 void RWLock::WriteLock() {
368 CheckAcquire();
369 this->detail::RWLockImpl::writeLock();
370 mOwningThread = PR_GetCurrentThread();
371 Acquire();
374 void RWLock::WriteUnlock() {
375 Release();
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);
390 ++mEntryCount;
391 return;
394 // this is sort of a hack around not recording the thread that
395 // owns this monitor
396 if (chainFront) {
397 for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
398 br = ResourceChainPrev(br)) {
399 if (br == this) {
400 NS_WARNING(
401 "Re-entering ReentrantMonitor after acquiring other resources.");
403 // show the caller why this is potentially bad
404 CheckAcquire();
406 PR_EnterMonitor(mReentrantMonitor);
407 ++mEntryCount;
408 return;
413 CheckAcquire();
414 PR_EnterMonitor(mReentrantMonitor);
415 NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
416 Acquire(); // protected by mReentrantMonitor
417 mEntryCount = 1;
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;
435 mEntryCount = 0;
436 mChainPrev = 0;
438 nsresult rv;
440 # if defined(MOZILLA_INTERNAL_API)
441 AUTO_PROFILER_THREAD_SLEEP;
442 # endif
443 // give up the monitor until we're back from Wait()
444 rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK
445 : NS_ERROR_FAILURE;
448 // restore saved state
449 mEntryCount = savedEntryCount;
450 SetAcquisitionState(std::move(savedAcquisitionState));
451 mChainPrev = savedChainPrev;
453 return rv;
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
465 LockInternal();
466 ++mEntryCount;
467 return;
470 // this is sort of a hack around not recording the thread that
471 // owns this monitor
472 if (chainFront) {
473 for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
474 br = ResourceChainPrev(br)) {
475 if (br == this) {
476 NS_WARNING(
477 "Re-entering RecursiveMutex after acquiring other resources.");
479 // show the caller why this is potentially bad
480 CheckAcquire();
482 LockInternal();
483 ++mEntryCount;
484 return;
489 CheckAcquire();
490 LockInternal();
491 NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
492 Acquire(); // protected by us
493 mOwningThread = PR_GetCurrentThread();
494 mEntryCount = 1;
497 void RecursiveMutex::Unlock() {
498 if (--mEntryCount == 0) {
499 Release(); // protected by us
500 mOwningThread = nullptr;
502 UnlockInternal();
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
513 // duplication.
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()
529 CVStatus status;
531 # if defined(MOZILLA_INTERNAL_API)
532 AUTO_PROFILER_THREAD_SLEEP;
533 # endif
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;
542 return status;
545 #endif // ifdef DEBUG
547 } // namespace mozilla