|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: sw=4 ts=4 et : |
|
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 "nsAutoPtr.h" |
|
11 |
|
12 #include "mozilla/CondVar.h" |
|
13 #include "mozilla/ReentrantMonitor.h" |
|
14 #include "mozilla/Mutex.h" |
|
15 |
|
16 #ifdef MOZILLA_INTERNAL_API |
|
17 #include "GeckoProfiler.h" |
|
18 #endif //MOZILLA_INTERNAL_API |
|
19 |
|
20 #endif // ifdef DEBUG |
|
21 |
|
22 namespace mozilla { |
|
23 // |
|
24 // BlockingResourceBase implementation |
|
25 // |
|
26 |
|
27 // static members |
|
28 const char* const BlockingResourceBase::kResourceTypeName[] = |
|
29 { |
|
30 // needs to be kept in sync with BlockingResourceType |
|
31 "Mutex", "ReentrantMonitor", "CondVar" |
|
32 }; |
|
33 |
|
34 #ifdef DEBUG |
|
35 |
|
36 PRCallOnceType BlockingResourceBase::sCallOnce; |
|
37 unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1; |
|
38 BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; |
|
39 |
|
40 bool |
|
41 BlockingResourceBase::DeadlockDetectorEntry::Print( |
|
42 const DDT::ResourceAcquisition& aFirstSeen, |
|
43 nsACString& out, |
|
44 bool aPrintFirstSeenCx) const |
|
45 { |
|
46 CallStack lastAcquisition = mAcquisitionContext; // RACY, but benign |
|
47 bool maybeCurrentlyAcquired = (CallStack::kNone != lastAcquisition); |
|
48 CallStack printAcquisition = |
|
49 (aPrintFirstSeenCx || !maybeCurrentlyAcquired) ? |
|
50 aFirstSeen.mCallContext : lastAcquisition; |
|
51 |
|
52 fprintf(stderr, "--- %s : %s", |
|
53 kResourceTypeName[mType], mName); |
|
54 out += BlockingResourceBase::kResourceTypeName[mType]; |
|
55 out += " : "; |
|
56 out += mName; |
|
57 |
|
58 if (maybeCurrentlyAcquired) { |
|
59 fputs(" (currently acquired)\n", stderr); |
|
60 out += " (currently acquired)\n"; |
|
61 } |
|
62 |
|
63 fputs(" calling context\n", stderr); |
|
64 printAcquisition.Print(stderr); |
|
65 |
|
66 return maybeCurrentlyAcquired; |
|
67 } |
|
68 |
|
69 |
|
70 BlockingResourceBase::BlockingResourceBase( |
|
71 const char* aName, |
|
72 BlockingResourceBase::BlockingResourceType aType) |
|
73 { |
|
74 // PR_CallOnce guaranatees that InitStatics is called in a |
|
75 // thread-safe way |
|
76 if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) |
|
77 NS_RUNTIMEABORT("can't initialize blocking resource static members"); |
|
78 |
|
79 mDDEntry = new BlockingResourceBase::DeadlockDetectorEntry(aName, aType); |
|
80 mChainPrev = 0; |
|
81 sDeadlockDetector->Add(mDDEntry); |
|
82 } |
|
83 |
|
84 |
|
85 BlockingResourceBase::~BlockingResourceBase() |
|
86 { |
|
87 // we don't check for really obviously bad things like freeing |
|
88 // Mutexes while they're still locked. it is assumed that the |
|
89 // base class, or its underlying primitive, will check for such |
|
90 // stupid mistakes. |
|
91 mChainPrev = 0; // racy only for stupidly buggy client code |
|
92 mDDEntry = 0; // owned by deadlock detector |
|
93 } |
|
94 |
|
95 |
|
96 void |
|
97 BlockingResourceBase::CheckAcquire(const CallStack& aCallContext) |
|
98 { |
|
99 if (eCondVar == mDDEntry->mType) { |
|
100 NS_NOTYETIMPLEMENTED( |
|
101 "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); |
|
102 return; |
|
103 } |
|
104 |
|
105 BlockingResourceBase* chainFront = ResourceChainFront(); |
|
106 nsAutoPtr<DDT::ResourceAcquisitionArray> cycle( |
|
107 sDeadlockDetector->CheckAcquisition( |
|
108 chainFront ? chainFront->mDDEntry : 0, mDDEntry, |
|
109 aCallContext)); |
|
110 if (!cycle) |
|
111 return; |
|
112 |
|
113 fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); |
|
114 nsAutoCString out("Potential deadlock detected:\n"); |
|
115 bool maybeImminent = PrintCycle(cycle, out); |
|
116 |
|
117 if (maybeImminent) { |
|
118 fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); |
|
119 out.Append("\n###!!! Deadlock may happen NOW!\n\n"); |
|
120 } else { |
|
121 fputs("\nDeadlock may happen for some other execution\n\n", |
|
122 stderr); |
|
123 out.Append("\nDeadlock may happen for some other execution\n\n"); |
|
124 } |
|
125 |
|
126 // XXX can customize behavior on whether we /think/ deadlock is |
|
127 // XXX about to happen. for example: |
|
128 // XXX if (maybeImminent) |
|
129 // NS_RUNTIMEABORT(out.get()); |
|
130 NS_ERROR(out.get()); |
|
131 } |
|
132 |
|
133 |
|
134 void |
|
135 BlockingResourceBase::Acquire(const CallStack& aCallContext) |
|
136 { |
|
137 if (eCondVar == mDDEntry->mType) { |
|
138 NS_NOTYETIMPLEMENTED( |
|
139 "FIXME bug 456272: annots. to allow Acquire()ing condvars"); |
|
140 return; |
|
141 } |
|
142 NS_ASSERTION(mDDEntry->mAcquisitionContext == CallStack::kNone, |
|
143 "reacquiring already acquired resource"); |
|
144 |
|
145 ResourceChainAppend(ResourceChainFront()); |
|
146 mDDEntry->mAcquisitionContext = aCallContext; |
|
147 } |
|
148 |
|
149 |
|
150 void |
|
151 BlockingResourceBase::Release() |
|
152 { |
|
153 if (eCondVar == mDDEntry->mType) { |
|
154 NS_NOTYETIMPLEMENTED( |
|
155 "FIXME bug 456272: annots. to allow Release()ing condvars"); |
|
156 return; |
|
157 } |
|
158 |
|
159 BlockingResourceBase* chainFront = ResourceChainFront(); |
|
160 NS_ASSERTION(chainFront |
|
161 && CallStack::kNone != mDDEntry->mAcquisitionContext, |
|
162 "Release()ing something that hasn't been Acquire()ed"); |
|
163 |
|
164 if (chainFront == this) { |
|
165 ResourceChainRemove(); |
|
166 } |
|
167 else { |
|
168 // not an error, but makes code hard to reason about. |
|
169 NS_WARNING("Resource acquired at calling context\n"); |
|
170 mDDEntry->mAcquisitionContext.Print(stderr); |
|
171 NS_WARNING("\nis being released in non-LIFO order; why?"); |
|
172 |
|
173 // remove this resource from wherever it lives in the chain |
|
174 // we walk backwards in order of acquisition: |
|
175 // (1) ...node<-prev<-curr... |
|
176 // / / |
|
177 // (2) ...prev<-curr... |
|
178 BlockingResourceBase* curr = chainFront; |
|
179 BlockingResourceBase* prev = nullptr; |
|
180 while (curr && (prev = curr->mChainPrev) && (prev != this)) |
|
181 curr = prev; |
|
182 if (prev == this) |
|
183 curr->mChainPrev = prev->mChainPrev; |
|
184 } |
|
185 |
|
186 mDDEntry->mAcquisitionContext = CallStack::kNone; |
|
187 } |
|
188 |
|
189 |
|
190 bool |
|
191 BlockingResourceBase::PrintCycle(const DDT::ResourceAcquisitionArray* aCycle, |
|
192 nsACString& out) |
|
193 { |
|
194 NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!"); |
|
195 |
|
196 bool maybeImminent = true; |
|
197 |
|
198 fputs("=== Cyclical dependency starts at\n", stderr); |
|
199 out += "Cyclical dependency starts at\n"; |
|
200 |
|
201 const DDT::ResourceAcquisition res = aCycle->ElementAt(0); |
|
202 maybeImminent &= res.mResource->Print(res, out); |
|
203 |
|
204 DDT::ResourceAcquisitionArray::index_type i; |
|
205 DDT::ResourceAcquisitionArray::size_type len = aCycle->Length(); |
|
206 const DDT::ResourceAcquisition* it = 1 + aCycle->Elements(); |
|
207 for (i = 1; i < len - 1; ++i, ++it) { |
|
208 fputs("\n--- Next dependency:\n", stderr); |
|
209 out += "\nNext dependency:\n"; |
|
210 |
|
211 maybeImminent &= it->mResource->Print(*it, out); |
|
212 } |
|
213 |
|
214 fputs("\n=== Cycle completed at\n", stderr); |
|
215 out += "Cycle completed at\n"; |
|
216 it->mResource->Print(*it, out, true); |
|
217 |
|
218 return maybeImminent; |
|
219 } |
|
220 |
|
221 |
|
222 // |
|
223 // Debug implementation of (OffTheBooks)Mutex |
|
224 void |
|
225 OffTheBooksMutex::Lock() |
|
226 { |
|
227 CallStack callContext = CallStack(); |
|
228 |
|
229 CheckAcquire(callContext); |
|
230 PR_Lock(mLock); |
|
231 Acquire(callContext); // protected by mLock |
|
232 } |
|
233 |
|
234 void |
|
235 OffTheBooksMutex::Unlock() |
|
236 { |
|
237 Release(); // protected by mLock |
|
238 PRStatus status = PR_Unlock(mLock); |
|
239 NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()"); |
|
240 } |
|
241 |
|
242 |
|
243 // |
|
244 // Debug implementation of ReentrantMonitor |
|
245 void |
|
246 ReentrantMonitor::Enter() |
|
247 { |
|
248 BlockingResourceBase* chainFront = ResourceChainFront(); |
|
249 |
|
250 // the code below implements monitor reentrancy semantics |
|
251 |
|
252 if (this == chainFront) { |
|
253 // immediately re-entered the monitor: acceptable |
|
254 PR_EnterMonitor(mReentrantMonitor); |
|
255 ++mEntryCount; |
|
256 return; |
|
257 } |
|
258 |
|
259 CallStack callContext = CallStack(); |
|
260 |
|
261 // this is sort of a hack around not recording the thread that |
|
262 // owns this monitor |
|
263 if (chainFront) { |
|
264 for (BlockingResourceBase* br = ResourceChainPrev(chainFront); |
|
265 br; |
|
266 br = ResourceChainPrev(br)) { |
|
267 if (br == this) { |
|
268 NS_WARNING( |
|
269 "Re-entering ReentrantMonitor after acquiring other resources.\n" |
|
270 "At calling context\n"); |
|
271 GetAcquisitionContext().Print(stderr); |
|
272 |
|
273 // show the caller why this is potentially bad |
|
274 CheckAcquire(callContext); |
|
275 |
|
276 PR_EnterMonitor(mReentrantMonitor); |
|
277 ++mEntryCount; |
|
278 return; |
|
279 } |
|
280 } |
|
281 } |
|
282 |
|
283 CheckAcquire(callContext); |
|
284 PR_EnterMonitor(mReentrantMonitor); |
|
285 NS_ASSERTION(0 == mEntryCount, "ReentrantMonitor isn't free!"); |
|
286 Acquire(callContext); // protected by mReentrantMonitor |
|
287 mEntryCount = 1; |
|
288 } |
|
289 |
|
290 void |
|
291 ReentrantMonitor::Exit() |
|
292 { |
|
293 if (0 == --mEntryCount) |
|
294 Release(); // protected by mReentrantMonitor |
|
295 PRStatus status = PR_ExitMonitor(mReentrantMonitor); |
|
296 NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); |
|
297 } |
|
298 |
|
299 nsresult |
|
300 ReentrantMonitor::Wait(PRIntervalTime interval) |
|
301 { |
|
302 AssertCurrentThreadIn(); |
|
303 |
|
304 // save monitor state and reset it to empty |
|
305 int32_t savedEntryCount = mEntryCount; |
|
306 CallStack savedAcquisitionContext = GetAcquisitionContext(); |
|
307 BlockingResourceBase* savedChainPrev = mChainPrev; |
|
308 mEntryCount = 0; |
|
309 SetAcquisitionContext(CallStack::kNone); |
|
310 mChainPrev = 0; |
|
311 |
|
312 nsresult rv; |
|
313 #ifdef MOZILLA_INTERNAL_API |
|
314 { |
|
315 GeckoProfilerSleepRAII profiler_sleep; |
|
316 #endif //MOZILLA_INTERNAL_API |
|
317 |
|
318 // give up the monitor until we're back from Wait() |
|
319 rv = PR_Wait(mReentrantMonitor, interval) == PR_SUCCESS ? |
|
320 NS_OK : NS_ERROR_FAILURE; |
|
321 |
|
322 #ifdef MOZILLA_INTERNAL_API |
|
323 } |
|
324 #endif //MOZILLA_INTERNAL_API |
|
325 |
|
326 // restore saved state |
|
327 mEntryCount = savedEntryCount; |
|
328 SetAcquisitionContext(savedAcquisitionContext); |
|
329 mChainPrev = savedChainPrev; |
|
330 |
|
331 return rv; |
|
332 } |
|
333 |
|
334 |
|
335 // |
|
336 // Debug implementation of CondVar |
|
337 nsresult |
|
338 CondVar::Wait(PRIntervalTime interval) |
|
339 { |
|
340 AssertCurrentThreadOwnsMutex(); |
|
341 |
|
342 // save mutex state and reset to empty |
|
343 CallStack savedAcquisitionContext = mLock->GetAcquisitionContext(); |
|
344 BlockingResourceBase* savedChainPrev = mLock->mChainPrev; |
|
345 mLock->SetAcquisitionContext(CallStack::kNone); |
|
346 mLock->mChainPrev = 0; |
|
347 |
|
348 // give up mutex until we're back from Wait() |
|
349 nsresult rv = |
|
350 PR_WaitCondVar(mCvar, interval) == PR_SUCCESS ? |
|
351 NS_OK : NS_ERROR_FAILURE; |
|
352 |
|
353 // restore saved state |
|
354 mLock->SetAcquisitionContext(savedAcquisitionContext); |
|
355 mLock->mChainPrev = savedChainPrev; |
|
356 |
|
357 return rv; |
|
358 } |
|
359 |
|
360 #endif // ifdef DEBUG |
|
361 |
|
362 |
|
363 } // namespace mozilla |