Line data Source code
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/. */
6 :
7 : #include "mozilla/BlockingResourceBase.h"
8 :
9 : #ifdef DEBUG
10 : #include "prthread.h"
11 :
12 : #include "nsAutoPtr.h"
13 :
14 : #ifndef MOZ_CALLSTACK_DISABLED
15 : #include "CodeAddressService.h"
16 : #include "nsHashKeys.h"
17 : #include "mozilla/StackWalk.h"
18 : #include "nsTHashtable.h"
19 : #endif
20 :
21 : #include "mozilla/CondVar.h"
22 : #include "mozilla/DeadlockDetector.h"
23 : #include "mozilla/ReentrantMonitor.h"
24 : #include "mozilla/Mutex.h"
25 : #include "mozilla/RWLock.h"
26 :
27 : #if defined(MOZILLA_INTERNAL_API)
28 : #include "GeckoProfiler.h"
29 : #endif //MOZILLA_INTERNAL_API
30 :
31 : #endif // ifdef DEBUG
32 :
33 : namespace mozilla {
34 : //
35 : // BlockingResourceBase implementation
36 : //
37 :
38 : // static members
39 : const char* const BlockingResourceBase::kResourceTypeName[] = {
40 : // needs to be kept in sync with BlockingResourceType
41 : "Mutex", "ReentrantMonitor", "CondVar"
42 : };
43 :
44 : #ifdef DEBUG
45 :
46 : PRCallOnceType BlockingResourceBase::sCallOnce;
47 : unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
48 : BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
49 :
50 :
51 : void
52 0 : BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
53 : void* aSp, void* aClosure)
54 : {
55 : #ifndef MOZ_CALLSTACK_DISABLED
56 : AcquisitionState* state = (AcquisitionState*)aClosure;
57 : state->AppendElement(aPc);
58 : #endif
59 0 : }
60 :
61 : void
62 0 : BlockingResourceBase::GetStackTrace(AcquisitionState& aState)
63 : {
64 : #ifndef MOZ_CALLSTACK_DISABLED
65 : // Skip this function and the calling function.
66 : const uint32_t kSkipFrames = 2;
67 :
68 : aState.Clear();
69 :
70 : // NB: Ignore the return value, there's nothing useful we can do if this
71 : // this fails.
72 : MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
73 : #endif
74 0 : }
75 :
76 : /**
77 : * PrintCycle
78 : * Append to |aOut| detailed information about the circular
79 : * dependency in |aCycle|. Returns true if it *appears* that this
80 : * cycle may represent an imminent deadlock, but this is merely a
81 : * heuristic; the value returned may be a false positive or false
82 : * negative.
83 : *
84 : * *NOT* thread safe. Calls |Print()|.
85 : *
86 : * FIXME bug 456272 hack alert: because we can't write call
87 : * contexts into strings, all info is written to stderr, but only
88 : * some info is written into |aOut|
89 : */
90 : bool
91 0 : PrintCycle(const BlockingResourceBase::DDT::ResourceAcquisitionArray* aCycle,
92 : nsACString& aOut)
93 : {
94 0 : NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!");
95 :
96 0 : bool maybeImminent = true;
97 :
98 0 : fputs("=== Cyclical dependency starts at\n", stderr);
99 0 : aOut += "Cyclical dependency starts at\n";
100 :
101 : const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res =
102 0 : aCycle->ElementAt(0);
103 0 : maybeImminent &= res->Print(aOut);
104 :
105 : BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
106 : BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
107 0 : aCycle->Length();
108 : const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it =
109 0 : 1 + aCycle->Elements();
110 0 : for (i = 1; i < len - 1; ++i, ++it) {
111 0 : fputs("\n--- Next dependency:\n", stderr);
112 0 : aOut += "\nNext dependency:\n";
113 :
114 0 : maybeImminent &= (*it)->Print(aOut);
115 : }
116 :
117 0 : fputs("\n=== Cycle completed at\n", stderr);
118 0 : aOut += "Cycle completed at\n";
119 0 : (*it)->Print(aOut);
120 :
121 0 : return maybeImminent;
122 : }
123 :
124 : #ifndef MOZ_CALLSTACK_DISABLED
125 : struct CodeAddressServiceLock final
126 : {
127 : static void Unlock() { }
128 : static void Lock() { }
129 : static bool IsLocked() { return true; }
130 : };
131 :
132 : struct CodeAddressServiceStringAlloc final
133 : {
134 : static char* copy(const char* aString) { return ::strdup(aString); }
135 : static void free(char* aString) { ::free(aString); }
136 : };
137 :
138 : class CodeAddressServiceStringTable final
139 : {
140 : public:
141 : CodeAddressServiceStringTable() : mSet(32) {}
142 :
143 : const char* Intern(const char* aString)
144 : {
145 : nsCharPtrHashKey* e = mSet.PutEntry(aString);
146 : return e->GetKey();
147 : }
148 :
149 : size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
150 : {
151 : return mSet.SizeOfExcludingThis(aMallocSizeOf);
152 : }
153 :
154 : private:
155 : typedef nsTHashtable<nsCharPtrHashKey> StringSet;
156 : StringSet mSet;
157 : };
158 :
159 : typedef CodeAddressService<CodeAddressServiceStringTable,
160 : CodeAddressServiceStringAlloc,
161 : CodeAddressServiceLock> WalkTheStackCodeAddressService;
162 : #endif
163 :
164 : bool
165 0 : BlockingResourceBase::Print(nsACString& aOut) const
166 : {
167 : fprintf(stderr, "--- %s : %s",
168 0 : kResourceTypeName[mType], mName);
169 0 : aOut += BlockingResourceBase::kResourceTypeName[mType];
170 0 : aOut += " : ";
171 0 : aOut += mName;
172 :
173 0 : bool acquired = IsAcquired();
174 :
175 0 : if (acquired) {
176 0 : fputs(" (currently acquired)\n", stderr);
177 0 : aOut += " (currently acquired)\n";
178 : }
179 :
180 0 : fputs(" calling context\n", stderr);
181 : #ifdef MOZ_CALLSTACK_DISABLED
182 0 : fputs(" [stack trace unavailable]\n", stderr);
183 : #else
184 : const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
185 :
186 : WalkTheStackCodeAddressService addressService;
187 :
188 : for (uint32_t i = 0; i < state.Length(); i++) {
189 : const size_t kMaxLength = 1024;
190 : char buffer[kMaxLength];
191 : addressService.GetLocation(i + 1, state[i], buffer, kMaxLength);
192 : const char* fmt = " %s\n";
193 : aOut.AppendLiteral(" ");
194 : aOut.Append(buffer);
195 : aOut.AppendLiteral("\n");
196 : fprintf(stderr, fmt, buffer);
197 : }
198 :
199 : #endif
200 :
201 0 : return acquired;
202 : }
203 :
204 :
205 1932 : BlockingResourceBase::BlockingResourceBase(
206 : const char* aName,
207 1932 : BlockingResourceBase::BlockingResourceType aType)
208 : : mName(aName)
209 : , mType(aType)
210 : #ifdef MOZ_CALLSTACK_DISABLED
211 1932 : , mAcquired(false)
212 : #else
213 : , mAcquired()
214 : #endif
215 : {
216 1932 : MOZ_ASSERT(mName, "Name must be nonnull");
217 : // PR_CallOnce guaranatees that InitStatics is called in a
218 : // thread-safe way
219 1932 : if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
220 0 : MOZ_CRASH("can't initialize blocking resource static members");
221 : }
222 :
223 1932 : mChainPrev = 0;
224 1932 : sDeadlockDetector->Add(this);
225 1934 : }
226 :
227 :
228 1000 : BlockingResourceBase::~BlockingResourceBase()
229 : {
230 : // we don't check for really obviously bad things like freeing
231 : // Mutexes while they're still locked. it is assumed that the
232 : // base class, or its underlying primitive, will check for such
233 : // stupid mistakes.
234 500 : mChainPrev = 0; // racy only for stupidly buggy client code
235 500 : if (sDeadlockDetector) {
236 500 : sDeadlockDetector->Remove(this);
237 : }
238 500 : }
239 :
240 :
241 : size_t
242 0 : BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf)
243 : {
244 0 : return sDeadlockDetector ?
245 0 : sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) : 0;
246 : }
247 :
248 :
249 : PRStatus
250 3 : BlockingResourceBase::InitStatics()
251 : {
252 3 : PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0);
253 3 : sDeadlockDetector = new DDT();
254 3 : if (!sDeadlockDetector) {
255 0 : MOZ_CRASH("can't allocate deadlock detector");
256 : }
257 3 : return PR_SUCCESS;
258 : }
259 :
260 :
261 : void
262 0 : BlockingResourceBase::Shutdown()
263 : {
264 0 : delete sDeadlockDetector;
265 0 : sDeadlockDetector = 0;
266 0 : }
267 :
268 :
269 : void
270 93714 : BlockingResourceBase::CheckAcquire()
271 : {
272 93714 : if (mType == eCondVar) {
273 0 : NS_NOTYETIMPLEMENTED(
274 : "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
275 0 : return;
276 : }
277 :
278 93714 : BlockingResourceBase* chainFront = ResourceChainFront();
279 : nsAutoPtr<DDT::ResourceAcquisitionArray> cycle(
280 : sDeadlockDetector->CheckAcquisition(
281 93705 : chainFront ? chainFront : 0, this));
282 93719 : if (!cycle) {
283 93720 : return;
284 : }
285 :
286 : #ifndef MOZ_CALLSTACK_DISABLED
287 : // Update the current stack before printing.
288 : GetStackTrace(mAcquired);
289 : #endif
290 :
291 0 : fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
292 0 : nsAutoCString out("Potential deadlock detected:\n");
293 0 : bool maybeImminent = PrintCycle(cycle, out);
294 :
295 0 : if (maybeImminent) {
296 0 : fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
297 0 : out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
298 : } else {
299 : fputs("\nDeadlock may happen for some other execution\n\n",
300 0 : stderr);
301 0 : out.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
302 : }
303 :
304 : // Only error out if we think a deadlock is imminent.
305 0 : if (maybeImminent) {
306 0 : NS_ERROR(out.get());
307 : } else {
308 0 : NS_WARNING(out.get());
309 : }
310 : }
311 :
312 :
313 : void
314 93740 : BlockingResourceBase::Acquire()
315 : {
316 93740 : if (mType == eCondVar) {
317 0 : NS_NOTYETIMPLEMENTED(
318 : "FIXME bug 456272: annots. to allow Acquire()ing condvars");
319 0 : return;
320 : }
321 93740 : NS_ASSERTION(!IsAcquired(),
322 : "reacquiring already acquired resource");
323 :
324 93741 : ResourceChainAppend(ResourceChainFront());
325 :
326 : #ifdef MOZ_CALLSTACK_DISABLED
327 93718 : mAcquired = true;
328 : #else
329 : // Take a stack snapshot.
330 : GetStackTrace(mAcquired);
331 : if (mFirstSeen.IsEmpty()) {
332 : mFirstSeen = mAcquired;
333 : }
334 : #endif
335 : }
336 :
337 :
338 : void
339 93662 : BlockingResourceBase::Release()
340 : {
341 93662 : if (mType == eCondVar) {
342 0 : NS_NOTYETIMPLEMENTED(
343 : "FIXME bug 456272: annots. to allow Release()ing condvars");
344 0 : return;
345 : }
346 :
347 93662 : BlockingResourceBase* chainFront = ResourceChainFront();
348 93660 : NS_ASSERTION(chainFront && IsAcquired(),
349 : "Release()ing something that hasn't been Acquire()ed");
350 :
351 93660 : if (chainFront == this) {
352 93660 : ResourceChainRemove();
353 : } else {
354 : // not an error, but makes code hard to reason about.
355 0 : NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
356 0 : nsCString tmp;
357 0 : Print(tmp);
358 :
359 : // remove this resource from wherever it lives in the chain
360 : // we walk backwards in order of acquisition:
361 : // (1) ...node<-prev<-curr...
362 : // / /
363 : // (2) ...prev<-curr...
364 0 : BlockingResourceBase* curr = chainFront;
365 0 : BlockingResourceBase* prev = nullptr;
366 0 : while (curr && (prev = curr->mChainPrev) && (prev != this)) {
367 0 : curr = prev;
368 : }
369 0 : if (prev == this) {
370 0 : curr->mChainPrev = prev->mChainPrev;
371 : }
372 : }
373 :
374 93652 : ClearAcquisitionState();
375 : }
376 :
377 :
378 : //
379 : // Debug implementation of (OffTheBooks)Mutex
380 : void
381 85468 : OffTheBooksMutex::Lock()
382 : {
383 85468 : CheckAcquire();
384 85474 : this->lock();
385 85492 : mOwningThread = PR_GetCurrentThread();
386 85493 : Acquire();
387 85470 : }
388 :
389 : void
390 85415 : OffTheBooksMutex::Unlock()
391 : {
392 85415 : Release();
393 85405 : mOwningThread = nullptr;
394 85405 : this->unlock();
395 85435 : }
396 :
397 : void
398 52271 : OffTheBooksMutex::AssertCurrentThreadOwns() const
399 : {
400 52271 : MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
401 52273 : }
402 :
403 : //
404 : // Debug implementation of RWLock
405 : //
406 :
407 : void
408 0 : RWLock::ReadLock()
409 : {
410 : // All we want to ensure here is that we're not attempting to acquire the
411 : // read lock while this thread is holding the write lock.
412 0 : CheckAcquire();
413 0 : this->ReadLockInternal();
414 0 : MOZ_ASSERT(mOwningThread == nullptr);
415 0 : }
416 :
417 : void
418 0 : RWLock::ReadUnlock()
419 : {
420 0 : MOZ_ASSERT(mOwningThread == nullptr);
421 0 : this->ReadUnlockInternal();
422 0 : }
423 :
424 : void
425 0 : RWLock::WriteLock()
426 : {
427 0 : CheckAcquire();
428 0 : this->WriteLockInternal();
429 0 : mOwningThread = PR_GetCurrentThread();
430 0 : Acquire();
431 0 : }
432 :
433 : void
434 0 : RWLock::WriteUnlock()
435 : {
436 0 : Release();
437 0 : mOwningThread = nullptr;
438 0 : this->WriteUnlockInternal();
439 0 : }
440 :
441 : //
442 : // Debug implementation of ReentrantMonitor
443 : void
444 8615 : ReentrantMonitor::Enter()
445 : {
446 8615 : BlockingResourceBase* chainFront = ResourceChainFront();
447 :
448 : // the code below implements monitor reentrancy semantics
449 :
450 8615 : if (this == chainFront) {
451 : // immediately re-entered the monitor: acceptable
452 404 : PR_EnterMonitor(mReentrantMonitor);
453 404 : ++mEntryCount;
454 404 : return;
455 : }
456 :
457 : // this is sort of a hack around not recording the thread that
458 : // owns this monitor
459 8211 : if (chainFront) {
460 814 : for (BlockingResourceBase* br = ResourceChainPrev(chainFront);
461 814 : br;
462 : br = ResourceChainPrev(br)) {
463 8 : if (br == this) {
464 : NS_WARNING(
465 0 : "Re-entering ReentrantMonitor after acquiring other resources.");
466 :
467 : // show the caller why this is potentially bad
468 0 : CheckAcquire();
469 :
470 0 : PR_EnterMonitor(mReentrantMonitor);
471 0 : ++mEntryCount;
472 0 : return;
473 : }
474 : }
475 : }
476 :
477 8211 : CheckAcquire();
478 8211 : PR_EnterMonitor(mReentrantMonitor);
479 8211 : NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
480 8211 : Acquire(); // protected by mReentrantMonitor
481 8211 : mEntryCount = 1;
482 : }
483 :
484 : void
485 8615 : ReentrantMonitor::Exit()
486 : {
487 8615 : if (--mEntryCount == 0) {
488 8211 : Release(); // protected by mReentrantMonitor
489 : }
490 8615 : PRStatus status = PR_ExitMonitor(mReentrantMonitor);
491 8615 : NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
492 8615 : }
493 :
494 : nsresult
495 56 : ReentrantMonitor::Wait(PRIntervalTime aInterval)
496 : {
497 56 : AssertCurrentThreadIn();
498 :
499 : // save monitor state and reset it to empty
500 56 : int32_t savedEntryCount = mEntryCount;
501 56 : AcquisitionState savedAcquisitionState = GetAcquisitionState();
502 56 : BlockingResourceBase* savedChainPrev = mChainPrev;
503 56 : mEntryCount = 0;
504 56 : ClearAcquisitionState();
505 56 : mChainPrev = 0;
506 :
507 : nsresult rv;
508 : #if defined(MOZILLA_INTERNAL_API)
509 : {
510 112 : AutoProfilerThreadSleep sleep;
511 : #endif //MOZILLA_INTERNAL_API
512 :
513 : // give up the monitor until we're back from Wait()
514 56 : rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK :
515 : NS_ERROR_FAILURE;
516 :
517 : #if defined(MOZILLA_INTERNAL_API)
518 : }
519 : #endif //MOZILLA_INTERNAL_API
520 :
521 : // restore saved state
522 56 : mEntryCount = savedEntryCount;
523 56 : SetAcquisitionState(savedAcquisitionState);
524 56 : mChainPrev = savedChainPrev;
525 :
526 56 : return rv;
527 : }
528 :
529 :
530 : //
531 : // Debug implementation of CondVar
532 : nsresult
533 696 : CondVar::Wait(PRIntervalTime aInterval)
534 : {
535 696 : AssertCurrentThreadOwnsMutex();
536 :
537 : // save mutex state and reset to empty
538 696 : AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
539 696 : BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
540 696 : PRThread* savedOwningThread = mLock->mOwningThread;
541 696 : mLock->ClearAcquisitionState();
542 696 : mLock->mChainPrev = 0;
543 696 : mLock->mOwningThread = nullptr;
544 :
545 : // give up mutex until we're back from Wait()
546 696 : if (aInterval == PR_INTERVAL_NO_TIMEOUT) {
547 317 : mImpl.wait(*mLock);
548 : } else {
549 379 : mImpl.wait_for(*mLock, TimeDuration::FromMilliseconds(double(aInterval)));
550 : }
551 :
552 : // restore saved state
553 639 : mLock->SetAcquisitionState(savedAcquisitionState);
554 638 : mLock->mChainPrev = savedChainPrev;
555 638 : mLock->mOwningThread = savedOwningThread;
556 :
557 638 : return NS_OK;
558 : }
559 :
560 : #endif // ifdef DEBUG
561 :
562 :
563 : } // namespace mozilla
|