1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/Util.h"
16 #include "mozilla/CondVar.h"
17 #include "mozilla/ReentrantMonitor.h"
18 #include "mozilla/Mutex.h"
20 #include "TestHarness.h"
22 using namespace mozilla
;
25 spawn(void (*run
)(void*), void* arg
)
27 return PR_CreateThread(PR_SYSTEM_THREAD
,
38 passed(__FUNCTION__); \
44 fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
45 return NS_ERROR_FAILURE; \
48 //-----------------------------------------------------------------------------
50 static const char* sPathToThisBinary
;
51 static const char* sAssertBehaviorEnv
= "XPCOM_DEBUG_BREAK=abort";
56 // not available until process finishes
61 Subprocess(const char* aTestName
) {
62 // set up stdio redirection
63 PRFileDesc
* readStdin
; PRFileDesc
* writeStdin
;
64 PRFileDesc
* readStdout
; PRFileDesc
* writeStdout
;
65 PRFileDesc
* readStderr
; PRFileDesc
* writeStderr
;
66 PRProcessAttr
* pattr
= PR_NewProcessAttr();
68 NS_ASSERTION(pattr
, "couldn't allocate process attrs");
70 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdin
, &writeStdin
),
71 "couldn't create child stdin pipe");
72 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(readStdin
, true),
73 "couldn't set child stdin inheritable");
74 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardInput
, readStdin
);
76 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdout
, &writeStdout
),
77 "couldn't create child stdout pipe");
78 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStdout
, true),
79 "couldn't set child stdout inheritable");
80 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardOutput
, writeStdout
);
82 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStderr
, &writeStderr
),
83 "couldn't create child stderr pipe");
84 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStderr
, true),
85 "couldn't set child stderr inheritable");
86 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardError
, writeStderr
);
88 // set up argv with test name to run
89 char* const newArgv
[3] = {
90 strdup(sPathToThisBinary
),
95 // make sure the child will abort if an assertion fails
96 NS_ASSERTION(PR_SUCCESS
== PR_SetEnv(sAssertBehaviorEnv
),
97 "couldn't set XPCOM_DEBUG_BREAK env var");
100 NS_ASSERTION(proc
= PR_CreateProcess(sPathToThisBinary
,
102 0, // inherit environment
104 "couldn't create process");
106 PR_Close(writeStdout
);
107 PR_Close(writeStderr
);
110 mStdinfd
= writeStdin
;
111 mStdoutfd
= readStdout
;
112 mStderrfd
= readStderr
;
116 PR_DestroyProcessAttr(pattr
);
119 void RunToCompletion(uint32_t aWaitMs
)
123 PRPollDesc pollfds
[2];
125 bool stdoutOpen
= true, stderrOpen
= true;
129 PRIntervalTime now
= PR_IntervalNow();
130 PRIntervalTime deadline
= now
+ PR_MillisecondsToInterval(aWaitMs
);
132 while ((stdoutOpen
|| stderrOpen
) && now
< deadline
) {
135 pollfds
[nfds
].fd
= mStdoutfd
;
136 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
137 pollfds
[nfds
].out_flags
= 0;
141 pollfds
[nfds
].fd
= mStderrfd
;
142 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
143 pollfds
[nfds
].out_flags
= 0;
147 int32_t rv
= PR_Poll(pollfds
, nfds
, deadline
- now
);
148 NS_ASSERTION(0 <= rv
, PR_ErrorToName(PR_GetError()));
150 if (0 == rv
) { // timeout
151 fputs("(timed out!)\n", stderr
);
152 Finish(false); // abnormal
156 for (int32_t i
= 0; i
< nfds
; ++i
) {
157 if (!pollfds
[i
].out_flags
)
160 bool isStdout
= mStdoutfd
== pollfds
[i
].fd
;
162 if (PR_POLL_READ
& pollfds
[i
].out_flags
) {
163 len
= PR_Read(pollfds
[i
].fd
, buf
, sizeof(buf
) - 1);
164 NS_ASSERTION(0 <= len
, PR_ErrorToName(PR_GetError()));
166 else if (PR_POLL_HUP
& pollfds
[i
].out_flags
) {
170 NS_ERROR(PR_ErrorToName(PR_GetError()));
188 now
= PR_IntervalNow();
192 fputs("(stdout still open!)\n", stderr
);
194 fputs("(stderr still open!)\n", stderr
);
196 fputs("(timed out!)\n", stderr
);
198 Finish(!stdoutOpen
&& !stderrOpen
&& now
<= deadline
);
202 void Finish(bool normalExit
) {
204 PR_KillProcess(mProc
);
207 PR_WaitProcess(mProc
, &dummy
);
210 PR_WaitProcess(mProc
, &mExitCode
); // this had better not block ...
218 PRFileDesc
* mStdinfd
; // writeable
219 PRFileDesc
* mStdoutfd
; // readable
220 PRFileDesc
* mStderrfd
; // readable
223 //-----------------------------------------------------------------------------
224 // Harness for checking detector errors
226 CheckForDeadlock(const char* test
, const char* const* findTokens
)
228 Subprocess
proc(test
);
229 proc
.RunToCompletion(5000);
231 if (0 == proc
.mExitCode
)
235 for (const char* const* tp
= findTokens
; *tp
; ++tp
) {
236 const char* const token
= *tp
;
237 #ifdef MOZILLA_INTERNAL_API
238 idx
= proc
.mStderr
.Find(token
, false, idx
);
240 nsCString
tokenCString(token
);
241 idx
= proc
.mStderr
.Find(tokenCString
, idx
);
244 printf("(missed token '%s' in output)\n", token
);
245 puts("----------------------------------\n");
246 puts(proc
.mStderr
.get());
247 puts("----------------------------------\n");
250 idx
+= strlen(token
);
256 //-----------------------------------------------------------------------------
257 // Single-threaded sanity tests
259 // Stupidest possible deadlock.
263 mozilla::Mutex
m1("dd.sanity.m1");
266 return 0; // not reached
272 const char* const tokens
[] = {
273 "###!!! ERROR: Potential deadlock detected",
274 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
275 "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
276 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
277 "###!!! ASSERTION: Potential deadlock detected",
280 if (CheckForDeadlock("Sanity", tokens
)) {
283 FAIL("deadlock not detected");
287 // Slightly less stupid deadlock.
291 mozilla::Mutex
m1("dd.sanity2.m1");
292 mozilla::Mutex
m2("dd.sanity2.m2");
296 return 0; // not reached
302 const char* const tokens
[] = {
303 "###!!! ERROR: Potential deadlock detected",
304 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
305 "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
306 "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
307 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
308 "###!!! ASSERTION: Potential deadlock detected",
311 if (CheckForDeadlock("Sanity2", tokens
)) {
314 FAIL("deadlock not detected");
322 mozilla::Mutex
m1("dd.sanity3.m1");
323 mozilla::Mutex
m2("dd.sanity3.m2");
324 mozilla::Mutex
m3("dd.sanity3.m3");
325 mozilla::Mutex
m4("dd.sanity3.m4");
344 const char* const tokens
[] = {
345 "###!!! ERROR: Potential deadlock detected",
346 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
347 "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
348 "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
349 "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
350 "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
351 "###!!! ASSERTION: Potential deadlock detected",
354 if (CheckForDeadlock("Sanity3", tokens
)) {
357 FAIL("deadlock not detected");
365 mozilla::ReentrantMonitor
m1("dd.sanity4.m1");
366 mozilla::Mutex
m2("dd.sanity4.m2");
376 const char* const tokens
[] = {
377 "Re-entering ReentrantMonitor after acquiring other resources",
378 "###!!! ERROR: Potential deadlock detected",
379 "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
380 "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
381 "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
382 "###!!! ASSERTION: Potential deadlock detected",
385 if (CheckForDeadlock("Sanity4", tokens
)) {
388 FAIL("deadlock not detected");
392 //-----------------------------------------------------------------------------
393 // Multithreaded tests
395 mozilla::Mutex
* ttM1
;
396 mozilla::Mutex
* ttM2
;
399 TwoThreads_thread(void* arg
)
401 int32_t m1First
= NS_PTR_TO_INT32(arg
);
419 ttM1
= new mozilla::Mutex("dd.twothreads.m1");
420 ttM2
= new mozilla::Mutex("dd.twothreads.m2");
422 NS_RUNTIMEABORT("couldn't allocate mutexes");
424 PRThread
* t1
= spawn(TwoThreads_thread
, (void*) 0);
427 PRThread
* t2
= spawn(TwoThreads_thread
, (void*) 1);
436 const char* const tokens
[] = {
437 "###!!! ERROR: Potential deadlock detected",
438 "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
439 "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
440 "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
441 "###!!! ASSERTION: Potential deadlock detected",
445 if (CheckForDeadlock("TwoThreads", tokens
)) {
448 FAIL("deadlock not detected");
453 mozilla::Mutex
* cndMs
[4];
454 const uint32_t K
= 100000;
457 ContentionNoDeadlock_thread(void* arg
)
459 int32_t starti
= NS_PTR_TO_INT32(arg
);
461 for (uint32_t k
= 0; k
< K
; ++k
) {
462 for (int32_t i
= starti
; i
< (int32_t) ArrayLength(cndMs
); ++i
)
464 // comment out the next two lines for deadlocking fun!
465 for (int32_t i
= ArrayLength(cndMs
) - 1; i
>= starti
; --i
)
468 starti
= (starti
+ 1) % 3;
473 ContentionNoDeadlock_Child()
475 PRThread
* threads
[3];
477 for (uint32_t i
= 0; i
< ArrayLength(cndMs
); ++i
)
478 cndMs
[i
] = new mozilla::Mutex("dd.cnd.ms");
480 for (int32_t i
= 0; i
< (int32_t) ArrayLength(threads
); ++i
)
481 threads
[i
] = spawn(ContentionNoDeadlock_thread
, NS_INT32_TO_PTR(i
));
483 for (uint32_t i
= 0; i
< ArrayLength(threads
); ++i
)
484 PR_JoinThread(threads
[i
]);
486 for (uint32_t i
= 0; i
< ArrayLength(cndMs
); ++i
)
493 ContentionNoDeadlock()
495 const char * func
= __func__
;
496 Subprocess
proc(func
);
497 proc
.RunToCompletion(60000);
498 if (0 != proc
.mExitCode
) {
499 printf("(expected 0 == return code, got %d)\n", proc
.mExitCode
);
500 puts("(output)\n----------------------------------\n");
501 puts(proc
.mStdout
.get());
502 puts("----------------------------------\n");
503 puts("(error output)\n----------------------------------\n");
504 puts(proc
.mStderr
.get());
505 puts("----------------------------------\n");
514 //-----------------------------------------------------------------------------
517 main(int argc
, char** argv
)
520 // XXX can we run w/o scoped XPCOM?
521 const char* test
= argv
[1];
522 ScopedXPCOM
xpcom(test
);
526 // running in a spawned process. call the specificed child function.
527 if (!strcmp("Sanity", test
))
528 return Sanity_Child();
529 if (!strcmp("Sanity2", test
))
530 return Sanity2_Child();
531 if (!strcmp("Sanity3", test
))
532 return Sanity3_Child();
533 if (!strcmp("Sanity4", test
))
534 return Sanity4_Child();
536 if (!strcmp("TwoThreads", test
))
537 return TwoThreads_Child();
538 if (!strcmp("ContentionNoDeadlock", test
))
539 return ContentionNoDeadlock_Child();
541 fail("%s | %s - unknown child test", __FILE__
, __FUNCTION__
);
545 ScopedXPCOM
xpcom("XPCOM deadlock detector correctness (" __FILE__
")");
549 // in the first invocation of this process. we will be the "driver".
552 sPathToThisBinary
= argv
[0];
554 if (NS_FAILED(Sanity()))
556 if (NS_FAILED(Sanity2()))
558 if (NS_FAILED(Sanity3()))
560 if (NS_FAILED(Sanity4()))
563 if (NS_FAILED(TwoThreads()))
565 if (NS_FAILED(ContentionNoDeadlock()))