1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: sw=4 ts=4 et :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 2009
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Chris Jones <jones.chris.g@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/Util.h"
41 :
42 : #include "prenv.h"
43 : #include "prerror.h"
44 : #include "prio.h"
45 : #include "prproces.h"
46 :
47 : #include "nsMemory.h"
48 :
49 : #include "mozilla/CondVar.h"
50 : #include "mozilla/ReentrantMonitor.h"
51 : #include "mozilla/Mutex.h"
52 :
53 : #include "TestHarness.h"
54 :
55 : using namespace mozilla;
56 :
57 : static PRThread*
58 3 : spawn(void (*run)(void*), void* arg)
59 : {
60 : return PR_CreateThread(PR_SYSTEM_THREAD,
61 : run,
62 : arg,
63 : PR_PRIORITY_NORMAL,
64 : PR_GLOBAL_THREAD,
65 : PR_JOINABLE_THREAD,
66 3 : 0);
67 : }
68 :
69 : #define PASS() \
70 : do { \
71 : passed(__FUNCTION__); \
72 : return NS_OK; \
73 : } while (0)
74 :
75 : #define FAIL(why) \
76 : do { \
77 : fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
78 : return NS_ERROR_FAILURE; \
79 : } while (0)
80 :
81 : //-----------------------------------------------------------------------------
82 :
83 : static const char* sPathToThisBinary;
84 : static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort";
85 :
86 : class Subprocess
87 6 : {
88 : public:
89 : // not available until process finishes
90 : PRInt32 mExitCode;
91 : nsCString mStdout;
92 : nsCString mStderr;
93 :
94 6 : Subprocess(const char* aTestName) {
95 : // set up stdio redirection
96 : PRFileDesc* readStdin; PRFileDesc* writeStdin;
97 : PRFileDesc* readStdout; PRFileDesc* writeStdout;
98 : PRFileDesc* readStderr; PRFileDesc* writeStderr;
99 6 : PRProcessAttr* pattr = PR_NewProcessAttr();
100 :
101 6 : NS_ASSERTION(pattr, "couldn't allocate process attrs");
102 :
103 6 : NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdin, &writeStdin),
104 : "couldn't create child stdin pipe");
105 6 : NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(readStdin, true),
106 : "couldn't set child stdin inheritable");
107 6 : PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardInput, readStdin);
108 :
109 6 : NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdout, &writeStdout),
110 : "couldn't create child stdout pipe");
111 6 : NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStdout, true),
112 : "couldn't set child stdout inheritable");
113 6 : PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardOutput, writeStdout);
114 :
115 6 : NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStderr, &writeStderr),
116 : "couldn't create child stderr pipe");
117 6 : NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStderr, true),
118 : "couldn't set child stderr inheritable");
119 6 : PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardError, writeStderr);
120 :
121 : // set up argv with test name to run
122 : char* const newArgv[3] = {
123 6 : strdup(sPathToThisBinary),
124 6 : strdup(aTestName),
125 : 0
126 18 : };
127 :
128 : // make sure the child will abort if an assertion fails
129 6 : NS_ASSERTION(PR_SUCCESS == PR_SetEnv(sAssertBehaviorEnv),
130 : "couldn't set XPCOM_DEBUG_BREAK env var");
131 :
132 : PRProcess* proc;
133 6 : NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary,
134 : newArgv,
135 : 0, // inherit environment
136 : pattr),
137 : "couldn't create process");
138 6 : PR_Close(readStdin);
139 6 : PR_Close(writeStdout);
140 6 : PR_Close(writeStderr);
141 :
142 6 : mProc = proc;
143 6 : mStdinfd = writeStdin;
144 6 : mStdoutfd = readStdout;
145 6 : mStderrfd = readStderr;
146 :
147 6 : free(newArgv[0]);
148 6 : free(newArgv[1]);
149 6 : PR_DestroyProcessAttr(pattr);
150 6 : }
151 :
152 6 : void RunToCompletion(PRUint32 aWaitMs)
153 : {
154 6 : PR_Close(mStdinfd);
155 :
156 : PRPollDesc pollfds[2];
157 : PRInt32 nfds;
158 6 : bool stdoutOpen = true, stderrOpen = true;
159 : char buf[4096];
160 : PRInt32 len;
161 :
162 6 : PRIntervalTime now = PR_IntervalNow();
163 6 : PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
164 :
165 80 : while ((stdoutOpen || stderrOpen) && now < deadline) {
166 68 : nfds = 0;
167 68 : if (stdoutOpen) {
168 66 : pollfds[nfds].fd = mStdoutfd;
169 66 : pollfds[nfds].in_flags = PR_POLL_READ;
170 66 : pollfds[nfds].out_flags = 0;
171 66 : ++nfds;
172 : }
173 68 : if (stderrOpen) {
174 68 : pollfds[nfds].fd = mStderrfd;
175 68 : pollfds[nfds].in_flags = PR_POLL_READ;
176 68 : pollfds[nfds].out_flags = 0;
177 68 : ++nfds;
178 : }
179 :
180 68 : PRInt32 rv = PR_Poll(pollfds, nfds, deadline - now);
181 68 : NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError()));
182 :
183 68 : if (0 == rv) { // timeout
184 0 : fputs("(timed out!)\n", stderr);
185 0 : Finish(false); // abnormal
186 0 : return;
187 : }
188 :
189 202 : for (PRInt32 i = 0; i < nfds; ++i) {
190 134 : if (!pollfds[i].out_flags)
191 62 : continue;
192 :
193 72 : bool isStdout = mStdoutfd == pollfds[i].fd;
194 :
195 72 : if (PR_POLL_READ & pollfds[i].out_flags) {
196 60 : len = PR_Read(pollfds[i].fd, buf, sizeof(buf) - 1);
197 60 : NS_ASSERTION(0 <= len, PR_ErrorToName(PR_GetError()));
198 : }
199 12 : else if (PR_POLL_HUP & pollfds[i].out_flags) {
200 12 : len = 0;
201 : }
202 : else {
203 0 : NS_ERROR(PR_ErrorToName(PR_GetError()));
204 : }
205 :
206 72 : if (0 < len) {
207 60 : buf[len] = '\0';
208 60 : if (isStdout)
209 1 : mStdout += buf;
210 : else
211 59 : mStderr += buf;
212 : }
213 12 : else if (isStdout) {
214 6 : stdoutOpen = false;
215 : }
216 : else {
217 6 : stderrOpen = false;
218 : }
219 : }
220 :
221 68 : now = PR_IntervalNow();
222 : }
223 :
224 6 : if (stdoutOpen)
225 0 : fputs("(stdout still open!)\n", stderr);
226 6 : if (stderrOpen)
227 0 : fputs("(stderr still open!)\n", stderr);
228 6 : if (now > deadline)
229 0 : fputs("(timed out!)\n", stderr);
230 :
231 6 : Finish(!stdoutOpen && !stderrOpen && now <= deadline);
232 : }
233 :
234 : private:
235 6 : void Finish(bool normalExit) {
236 6 : if (!normalExit) {
237 0 : PR_KillProcess(mProc);
238 0 : mExitCode = -1;
239 : PRInt32 dummy;
240 0 : PR_WaitProcess(mProc, &dummy);
241 : }
242 : else {
243 6 : PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
244 : }
245 :
246 6 : PR_Close(mStdoutfd);
247 6 : PR_Close(mStderrfd);
248 6 : }
249 :
250 : PRProcess* mProc;
251 : PRFileDesc* mStdinfd; // writeable
252 : PRFileDesc* mStdoutfd; // readable
253 : PRFileDesc* mStderrfd; // readable
254 : };
255 :
256 : //-----------------------------------------------------------------------------
257 : // Harness for checking detector errors
258 : bool
259 5 : CheckForDeadlock(const char* test, const char* const* findTokens)
260 : {
261 10 : Subprocess proc(test);
262 5 : proc.RunToCompletion(5000);
263 :
264 5 : if (0 == proc.mExitCode)
265 0 : return false;
266 :
267 5 : PRInt32 idx = 0;
268 34 : for (const char* const* tp = findTokens; *tp; ++tp) {
269 29 : const char* const token = *tp;
270 : #ifdef MOZILLA_INTERNAL_API
271 : idx = proc.mStderr.Find(token, false, idx);
272 : #else
273 58 : nsCString tokenCString(token);
274 29 : idx = proc.mStderr.Find(tokenCString, idx);
275 : #endif
276 29 : if (-1 == idx) {
277 0 : printf("(missed token '%s' in output)\n", token);
278 0 : puts("----------------------------------\n");
279 0 : puts(proc.mStderr.get());
280 0 : puts("----------------------------------\n");
281 0 : return false;
282 : }
283 58 : idx += strlen(token);
284 : }
285 :
286 5 : return true;
287 : }
288 :
289 : //-----------------------------------------------------------------------------
290 : // Single-threaded sanity tests
291 :
292 : // Stupidest possible deadlock.
293 : nsresult
294 0 : Sanity_Child()
295 : {
296 0 : mozilla::Mutex m1("dd.sanity.m1");
297 0 : m1.Lock();
298 0 : m1.Lock();
299 0 : return 0; // not reached
300 : }
301 :
302 : nsresult
303 1 : Sanity()
304 : {
305 : const char* const tokens[] = {
306 : "###!!! ERROR: Potential deadlock detected",
307 : "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
308 : "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
309 : "###!!! Deadlock may happen NOW!", // better catch these easy cases...
310 : "###!!! ASSERTION: Potential deadlock detected",
311 : 0
312 1 : };
313 1 : if (CheckForDeadlock("Sanity", tokens)) {
314 1 : PASS();
315 : } else {
316 0 : FAIL("deadlock not detected");
317 : }
318 : }
319 :
320 : // Slightly less stupid deadlock.
321 : nsresult
322 0 : Sanity2_Child()
323 : {
324 0 : mozilla::Mutex m1("dd.sanity2.m1");
325 0 : mozilla::Mutex m2("dd.sanity2.m2");
326 0 : m1.Lock();
327 0 : m2.Lock();
328 0 : m1.Lock();
329 0 : return 0; // not reached
330 : }
331 :
332 : nsresult
333 1 : Sanity2()
334 : {
335 : const char* const tokens[] = {
336 : "###!!! ERROR: Potential deadlock detected",
337 : "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
338 : "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
339 : "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
340 : "###!!! Deadlock may happen NOW!", // better catch these easy cases...
341 : "###!!! ASSERTION: Potential deadlock detected",
342 : 0
343 1 : };
344 1 : if (CheckForDeadlock("Sanity2", tokens)) {
345 1 : PASS();
346 : } else {
347 0 : FAIL("deadlock not detected");
348 : }
349 : }
350 :
351 :
352 : nsresult
353 0 : Sanity3_Child()
354 : {
355 0 : mozilla::Mutex m1("dd.sanity3.m1");
356 0 : mozilla::Mutex m2("dd.sanity3.m2");
357 0 : mozilla::Mutex m3("dd.sanity3.m3");
358 0 : mozilla::Mutex m4("dd.sanity3.m4");
359 :
360 0 : m1.Lock();
361 0 : m2.Lock();
362 0 : m3.Lock();
363 0 : m4.Lock();
364 0 : m4.Unlock();
365 0 : m3.Unlock();
366 0 : m2.Unlock();
367 0 : m1.Unlock();
368 :
369 0 : m4.Lock();
370 0 : m1.Lock();
371 0 : return 0;
372 : }
373 :
374 : nsresult
375 1 : Sanity3()
376 : {
377 : const char* const tokens[] = {
378 : "###!!! ERROR: Potential deadlock detected",
379 : "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
380 : "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
381 : "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
382 : "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
383 : "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
384 : "###!!! ASSERTION: Potential deadlock detected",
385 : 0
386 1 : };
387 1 : if (CheckForDeadlock("Sanity3", tokens)) {
388 1 : PASS();
389 : } else {
390 0 : FAIL("deadlock not detected");
391 : }
392 : }
393 :
394 :
395 : nsresult
396 0 : Sanity4_Child()
397 : {
398 0 : mozilla::ReentrantMonitor m1("dd.sanity4.m1");
399 0 : mozilla::Mutex m2("dd.sanity4.m2");
400 0 : m1.Enter();
401 0 : m2.Lock();
402 0 : m1.Enter();
403 0 : return 0;
404 : }
405 :
406 : nsresult
407 1 : Sanity4()
408 : {
409 : const char* const tokens[] = {
410 : "Re-entering ReentrantMonitor after acquiring other resources",
411 : "###!!! ERROR: Potential deadlock detected",
412 : "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
413 : "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
414 : "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
415 : "###!!! ASSERTION: Potential deadlock detected",
416 : 0
417 1 : };
418 1 : if (CheckForDeadlock("Sanity4", tokens)) {
419 1 : PASS();
420 : } else {
421 0 : FAIL("deadlock not detected");
422 : }
423 : }
424 :
425 : //-----------------------------------------------------------------------------
426 : // Multithreaded tests
427 :
428 : mozilla::Mutex* ttM1;
429 : mozilla::Mutex* ttM2;
430 :
431 : static void
432 0 : TwoThreads_thread(void* arg)
433 : {
434 0 : PRInt32 m1First = NS_PTR_TO_INT32(arg);
435 0 : if (m1First) {
436 0 : ttM1->Lock();
437 0 : ttM2->Lock();
438 0 : ttM2->Unlock();
439 0 : ttM1->Unlock();
440 : }
441 : else {
442 0 : ttM2->Lock();
443 0 : ttM1->Lock();
444 0 : ttM1->Unlock();
445 0 : ttM2->Unlock();
446 : }
447 0 : }
448 :
449 : nsresult
450 0 : TwoThreads_Child()
451 : {
452 0 : ttM1 = new mozilla::Mutex("dd.twothreads.m1");
453 0 : ttM2 = new mozilla::Mutex("dd.twothreads.m2");
454 0 : if (!ttM1 || !ttM2)
455 0 : NS_RUNTIMEABORT("couldn't allocate mutexes");
456 :
457 0 : PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
458 0 : PR_JoinThread(t1);
459 :
460 0 : PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
461 0 : PR_JoinThread(t2);
462 :
463 0 : return 0;
464 : }
465 :
466 : nsresult
467 1 : TwoThreads()
468 : {
469 : const char* const tokens[] = {
470 : "###!!! ERROR: Potential deadlock detected",
471 : "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
472 : "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
473 : "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
474 : "###!!! ASSERTION: Potential deadlock detected",
475 : 0
476 1 : };
477 :
478 1 : if (CheckForDeadlock("TwoThreads", tokens)) {
479 1 : PASS();
480 : } else {
481 0 : FAIL("deadlock not detected");
482 : }
483 : }
484 :
485 :
486 : mozilla::Mutex* cndMs[4];
487 : const PRUint32 K = 100000;
488 :
489 : static void
490 3 : ContentionNoDeadlock_thread(void* arg)
491 : {
492 3 : PRInt32 starti = NS_PTR_TO_INT32(arg);
493 :
494 300003 : for (PRUint32 k = 0; k < K; ++k) {
495 1199963 : for (PRInt32 i = starti; i < (PRInt32) ArrayLength(cndMs); ++i)
496 899974 : cndMs[i]->Lock();
497 : // comment out the next two lines for deadlocking fun!
498 1200000 : for (PRInt32 i = ArrayLength(cndMs) - 1; i >= starti; --i)
499 900000 : cndMs[i]->Unlock();
500 :
501 300000 : starti = (starti + 1) % 3;
502 : }
503 3 : }
504 :
505 : nsresult
506 1 : ContentionNoDeadlock_Child()
507 : {
508 : PRThread* threads[3];
509 :
510 5 : for (PRUint32 i = 0; i < ArrayLength(cndMs); ++i)
511 4 : cndMs[i] = new mozilla::Mutex("dd.cnd.ms");
512 :
513 4 : for (PRInt32 i = 0; i < (PRInt32) ArrayLength(threads); ++i)
514 3 : threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i));
515 :
516 4 : for (PRUint32 i = 0; i < ArrayLength(threads); ++i)
517 3 : PR_JoinThread(threads[i]);
518 :
519 5 : for (PRUint32 i = 0; i < ArrayLength(cndMs); ++i)
520 4 : delete cndMs[i];
521 :
522 1 : return 0;
523 : }
524 :
525 : nsresult
526 1 : ContentionNoDeadlock()
527 : {
528 1 : const char * func = __func__;
529 2 : Subprocess proc(func);
530 1 : proc.RunToCompletion(60000);
531 1 : if (0 != proc.mExitCode) {
532 0 : printf("(expected 0 == return code, got %d)\n", proc.mExitCode);
533 0 : puts("(output)\n----------------------------------\n");
534 0 : puts(proc.mStdout.get());
535 0 : puts("----------------------------------\n");
536 0 : puts("(error output)\n----------------------------------\n");
537 0 : puts(proc.mStderr.get());
538 0 : puts("----------------------------------\n");
539 :
540 0 : FAIL("deadlock");
541 : }
542 1 : PASS();
543 : }
544 :
545 :
546 :
547 : //-----------------------------------------------------------------------------
548 :
549 : int
550 2 : main(int argc, char** argv)
551 : {
552 2 : if (1 < argc) {
553 : // XXX can we run w/o scoped XPCOM?
554 1 : const char* test = argv[1];
555 2 : ScopedXPCOM xpcom(test);
556 1 : if (xpcom.failed())
557 0 : return 1;
558 :
559 : // running in a spawned process. call the specificed child function.
560 1 : if (!strcmp("Sanity", test))
561 0 : return Sanity_Child();
562 1 : if (!strcmp("Sanity2", test))
563 0 : return Sanity2_Child();
564 1 : if (!strcmp("Sanity3", test))
565 0 : return Sanity3_Child();
566 1 : if (!strcmp("Sanity4", test))
567 0 : return Sanity4_Child();
568 :
569 1 : if (!strcmp("TwoThreads", test))
570 0 : return TwoThreads_Child();
571 1 : if (!strcmp("ContentionNoDeadlock", test))
572 1 : return ContentionNoDeadlock_Child();
573 :
574 0 : FAIL("unknown child test");
575 : }
576 :
577 2 : ScopedXPCOM xpcom("XPCOM deadlock detector correctness (" __FILE__ ")");
578 1 : if (xpcom.failed())
579 0 : return 1;
580 :
581 : // in the first invocation of this process. we will be the "driver".
582 1 : int rv = 0;
583 :
584 1 : sPathToThisBinary = argv[0];
585 :
586 1 : if (NS_FAILED(Sanity()))
587 0 : rv = 1;
588 1 : if (NS_FAILED(Sanity2()))
589 0 : rv = 1;
590 1 : if (NS_FAILED(Sanity3()))
591 0 : rv = 1;
592 1 : if (NS_FAILED(Sanity4()))
593 0 : rv = 1;
594 :
595 1 : if (NS_FAILED(TwoThreads()))
596 0 : rv = 1;
597 1 : if (NS_FAILED(ContentionNoDeadlock()))
598 0 : rv = 1;
599 :
600 1 : return rv;
601 : }
|