1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
16 * The Original Code is mozilla.org code.
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.
24 * Chris Jones <jones.chris.g@gmail.com>
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.
38 * ***** END LICENSE BLOCK ***** */
47 #include "mozilla/CondVar.h"
48 #include "mozilla/Monitor.h"
49 #include "mozilla/Mutex.h"
51 #include "TestHarness.h"
53 using namespace mozilla
;
56 spawn(void (*run
)(void*), void* arg
)
58 return PR_CreateThread(PR_SYSTEM_THREAD
,
69 passed(__FUNCTION__); \
77 return NS_ERROR_FAILURE; \
80 //-----------------------------------------------------------------------------
82 static const char* sPathToThisBinary
;
83 static const char* sAssertBehaviorEnv
= "XPCOM_DEBUG_BREAK=abort";
88 // not available until process finishes
93 Subprocess(const char* aTestName
) {
94 // set up stdio redirection
95 PRFileDesc
* readStdin
; PRFileDesc
* writeStdin
;
96 PRFileDesc
* readStdout
; PRFileDesc
* writeStdout
;
97 PRFileDesc
* readStderr
; PRFileDesc
* writeStderr
;
98 PRProcessAttr
* pattr
= PR_NewProcessAttr();
100 NS_ASSERTION(pattr
, "couldn't allocate process attrs");
102 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdin
, &writeStdin
),
103 "couldn't create child stdin pipe");
104 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(readStdin
, PR_TRUE
),
105 "couldn't set child stdin inheritable");
106 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardInput
, readStdin
);
108 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdout
, &writeStdout
),
109 "couldn't create child stdout pipe");
110 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStdout
, PR_TRUE
),
111 "couldn't set child stdout inheritable");
112 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardOutput
, writeStdout
);
114 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStderr
, &writeStderr
),
115 "couldn't create child stderr pipe");
116 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStderr
, PR_TRUE
),
117 "couldn't set child stderr inheritable");
118 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardError
, writeStderr
);
120 // set up argv with test name to run
121 char* const newArgv
[3] = {
122 strdup(sPathToThisBinary
),
127 // make sure the child will abort if an assertion fails
128 NS_ASSERTION(PR_SUCCESS
== PR_SetEnv(sAssertBehaviorEnv
),
129 "couldn't set XPCOM_DEBUG_BREAK env var");
132 NS_ASSERTION(proc
= PR_CreateProcess(sPathToThisBinary
,
134 0, // inherit environment
136 "couldn't create process");
138 PR_Close(writeStdout
);
139 PR_Close(writeStderr
);
142 mStdinfd
= writeStdin
;
143 mStdoutfd
= readStdout
;
144 mStderrfd
= readStderr
;
148 PR_DestroyProcessAttr(pattr
);
151 void RunToCompletion(PRUint32 aWaitMs
)
155 PRPollDesc pollfds
[2];
157 PRBool stdoutOpen
= PR_TRUE
, stderrOpen
= PR_TRUE
;
161 PRIntervalTime now
= PR_IntervalNow();
162 PRIntervalTime deadline
= now
+ PR_MillisecondsToInterval(aWaitMs
);
164 while ((stdoutOpen
|| stderrOpen
) && now
< deadline
) {
167 pollfds
[nfds
].fd
= mStdoutfd
;
168 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
169 pollfds
[nfds
].out_flags
= 0;
173 pollfds
[nfds
].fd
= mStderrfd
;
174 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
175 pollfds
[nfds
].out_flags
= 0;
179 PRInt32 rv
= PR_Poll(pollfds
, nfds
, deadline
- now
);
180 NS_ASSERTION(0 <= rv
, PR_ErrorToName(PR_GetError()));
182 if (0 == rv
) { // timeout
183 fputs("(timed out!)\n", stderr
);
184 Finish(PR_FALSE
); // abnormal
188 for (PRInt32 i
= 0; i
< nfds
; ++i
) {
189 if (!pollfds
[i
].out_flags
)
192 PRBool isStdout
= mStdoutfd
== pollfds
[i
].fd
;
194 if (PR_POLL_READ
& pollfds
[i
].out_flags
) {
195 len
= PR_Read(pollfds
[i
].fd
, buf
, sizeof(buf
) - 1);
196 NS_ASSERTION(0 <= len
, PR_ErrorToName(PR_GetError()));
198 else if (PR_POLL_HUP
& pollfds
[i
].out_flags
) {
202 NS_ERROR(PR_ErrorToName(PR_GetError()));
213 stdoutOpen
= PR_FALSE
;
216 stderrOpen
= PR_FALSE
;
220 now
= PR_IntervalNow();
224 fputs("(stdout still open!)\n", stderr
);
226 fputs("(stderr still open!)\n", stderr
);
228 fputs("(timed out!)\n", stderr
);
230 Finish(!stdoutOpen
&& !stderrOpen
&& now
<= deadline
);
234 void Finish(PRBool normalExit
) {
236 PR_KillProcess(mProc
);
239 PR_WaitProcess(mProc
, &dummy
);
242 PR_WaitProcess(mProc
, &mExitCode
); // this had better not block ...
250 PRFileDesc
* mStdinfd
; // writeable
251 PRFileDesc
* mStdoutfd
; // readable
252 PRFileDesc
* mStderrfd
; // readable
255 //-----------------------------------------------------------------------------
256 // Harness for checking detector errors
258 CheckForDeadlock(const char* test
, const char* const* findTokens
)
260 Subprocess
proc(test
);
261 proc
.RunToCompletion(1000);
263 if (0 == proc
.mExitCode
)
267 for (const char* const* tp
= findTokens
; *tp
; ++tp
) {
268 const char* const token
= *tp
;
269 #ifdef MOZILLA_INTERNAL_API
270 idx
= proc
.mStderr
.Find(token
, PR_FALSE
, idx
);
272 nsCString
tokenCString(token
);
273 idx
= proc
.mStderr
.Find(tokenCString
, idx
);
276 printf("(missed token '%s' in output)\n", token
);
277 puts("----------------------------------\n");
278 puts(proc
.mStderr
.get());
279 puts("----------------------------------\n");
282 idx
+= strlen(token
);
288 //-----------------------------------------------------------------------------
289 // Single-threaded sanity tests
291 // Stupidest possible deadlock.
295 mozilla::Mutex
m1("dd.sanity.m1");
298 return 0; // not reached
304 const char* const tokens
[] = {
305 "###!!! ERROR: Potential deadlock detected",
306 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
307 "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
308 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
309 "###!!! ASSERTION: Potential deadlock detected",
312 if (CheckForDeadlock("Sanity", tokens
)) {
315 FAIL("deadlock not detected");
319 // Slightly less stupid deadlock.
323 mozilla::Mutex
m1("dd.sanity2.m1");
324 mozilla::Mutex
m2("dd.sanity2.m2");
328 return 0; // not reached
334 const char* const tokens
[] = {
335 "###!!! ERROR: Potential deadlock detected",
336 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
337 "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
338 "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
339 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
340 "###!!! ASSERTION: Potential deadlock detected",
343 if (CheckForDeadlock("Sanity2", tokens
)) {
346 FAIL("deadlock not detected");
354 mozilla::Mutex
m1("dd.sanity3.m1");
355 mozilla::Mutex
m2("dd.sanity3.m2");
356 mozilla::Mutex
m3("dd.sanity3.m3");
357 mozilla::Mutex
m4("dd.sanity3.m4");
376 const char* const tokens
[] = {
377 "###!!! ERROR: Potential deadlock detected",
378 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
379 "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
380 "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
381 "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
382 "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
383 "###!!! ASSERTION: Potential deadlock detected",
386 if (CheckForDeadlock("Sanity3", tokens
)) {
389 FAIL("deadlock not detected");
397 mozilla::Monitor
m1("dd.sanity4.m1");
398 mozilla::Mutex
m2("dd.sanity4.m2");
408 const char* const tokens
[] = {
409 "Re-entering Monitor after acquiring other resources",
410 "###!!! ERROR: Potential deadlock detected",
411 "=== Cyclical dependency starts at\n--- Monitor : dd.sanity4.m1",
412 "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
413 "=== Cycle completed at\n--- Monitor : dd.sanity4.m1",
414 "###!!! ASSERTION: Potential deadlock detected",
417 if (CheckForDeadlock("Sanity4", tokens
)) {
420 FAIL("deadlock not detected");
424 //-----------------------------------------------------------------------------
425 // Multithreaded tests
427 mozilla::Mutex
* ttM1
;
428 mozilla::Mutex
* ttM2
;
431 TwoThreads_thread(void* arg
)
433 PRInt32 m1First
= NS_PTR_TO_INT32(arg
);
451 ttM1
= new mozilla::Mutex("dd.twothreads.m1");
452 ttM2
= new mozilla::Mutex("dd.twothreads.m2");
454 NS_RUNTIMEABORT("couldn't allocate mutexes");
456 PRThread
* t1
= spawn(TwoThreads_thread
, (void*) 0);
459 PRThread
* t2
= spawn(TwoThreads_thread
, (void*) 1);
468 const char* const tokens
[] = {
469 "###!!! ERROR: Potential deadlock detected",
470 "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
471 "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
472 "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
473 "###!!! ASSERTION: Potential deadlock detected",
477 if (CheckForDeadlock("TwoThreads", tokens
)) {
480 FAIL("deadlock not detected");
485 mozilla::Mutex
* cndMs
[4];
486 const PRUint32 K
= 100000;
489 ContentionNoDeadlock_thread(void* arg
)
491 PRInt32 starti
= NS_PTR_TO_INT32(arg
);
493 for (PRUint32 k
= 0; k
< K
; ++k
) {
494 for (PRInt32 i
= starti
; i
< (PRInt32
) NS_ARRAY_LENGTH(cndMs
); ++i
)
496 // comment out the next two lines for deadlocking fun!
497 for (PRInt32 i
= NS_ARRAY_LENGTH(cndMs
) - 1; i
>= starti
; --i
)
500 starti
= (starti
+ 1) % 3;
505 ContentionNoDeadlock_Child()
507 PRThread
* threads
[3];
509 for (PRUint32 i
= 0; i
< NS_ARRAY_LENGTH(cndMs
); ++i
)
510 cndMs
[i
] = new mozilla::Mutex("dd.cnd.ms");
512 for (PRInt32 i
= 0; i
< (PRInt32
) NS_ARRAY_LENGTH(threads
); ++i
)
513 threads
[i
] = spawn(ContentionNoDeadlock_thread
, NS_INT32_TO_PTR(i
));
515 for (PRUint32 i
= 0; i
< NS_ARRAY_LENGTH(threads
); ++i
)
516 PR_JoinThread(threads
[i
]);
518 for (PRUint32 i
= 0; i
< NS_ARRAY_LENGTH(cndMs
); ++i
)
525 ContentionNoDeadlock()
527 const char * func
= __func__
;
528 Subprocess
proc(func
);
529 proc
.RunToCompletion(60000);
530 if (0 != proc
.mExitCode
) {
531 printf("(expected 0 == return code, got %d)\n", proc
.mExitCode
);
532 puts("(output)\n----------------------------------\n");
533 puts(proc
.mStdout
.get());
534 puts("----------------------------------\n");
535 puts("(error output)\n----------------------------------\n");
536 puts(proc
.mStderr
.get());
537 puts("----------------------------------\n");
546 //-----------------------------------------------------------------------------
549 main(int argc
, char** argv
)
552 // XXX can we run w/o scoped XPCOM?
553 const char* test
= argv
[1];
554 ScopedXPCOM
xpcom(test
);
558 // running in a spawned process. call the specificed child function.
559 if (!strcmp("Sanity", test
))
560 return Sanity_Child();
561 if (!strcmp("Sanity2", test
))
562 return Sanity2_Child();
563 if (!strcmp("Sanity3", test
))
564 return Sanity3_Child();
565 if (!strcmp("Sanity4", test
))
566 return Sanity4_Child();
568 if (!strcmp("TwoThreads", test
))
569 return TwoThreads_Child();
570 if (!strcmp("ContentionNoDeadlock", test
))
571 return ContentionNoDeadlock_Child();
573 FAIL("unknown child test");
576 ScopedXPCOM
xpcom("Deadlock detector correctness");
580 // in the first invocation of this process. we will be the "driver".
583 sPathToThisBinary
= argv
[0];
585 if (NS_FAILED(Sanity()))
587 if (NS_FAILED(Sanity2()))
589 if (NS_FAILED(Sanity3()))
591 if (NS_FAILED(Sanity4()))
594 if (NS_FAILED(TwoThreads()))
596 if (NS_FAILED(ContentionNoDeadlock()))