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/. */
8 * Note: This file is a copy of xpcom/tests/TestDeadlockDetector.cpp, but all
9 * mutexes were turned into SQLiteMutexes.
19 #include "mozilla/CondVar.h"
20 #include "mozilla/ReentrantMonitor.h"
21 #include "SQLiteMutex.h"
23 #include "TestHarness.h"
25 using namespace mozilla
;
28 * Helper class to allocate a sqlite3_mutex for our SQLiteMutex. Also makes
29 * keeping the test files in sync easier.
31 class TestMutex
: public mozilla::storage::SQLiteMutex
34 TestMutex(const char* aName
)
35 : mozilla::storage::SQLiteMutex(aName
)
36 , mInner(sqlite3_mutex_alloc(SQLITE_MUTEX_FAST
))
38 NS_ASSERTION(mInner
, "could not allocate a sqlite3_mutex");
39 initWithMutex(mInner
);
44 sqlite3_mutex_free(mInner
);
57 sqlite3_mutex
*mInner
;
61 spawn(void (*run
)(void*), void* arg
)
63 return PR_CreateThread(PR_SYSTEM_THREAD
,
74 passed(__FUNCTION__); \
80 fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
81 return NS_ERROR_FAILURE; \
84 //-----------------------------------------------------------------------------
86 static const char* sPathToThisBinary
;
87 static const char* sAssertBehaviorEnv
= "XPCOM_DEBUG_BREAK=abort";
92 // not available until process finishes
97 Subprocess(const char* aTestName
) {
98 // set up stdio redirection
99 PRFileDesc
* readStdin
; PRFileDesc
* writeStdin
;
100 PRFileDesc
* readStdout
; PRFileDesc
* writeStdout
;
101 PRFileDesc
* readStderr
; PRFileDesc
* writeStderr
;
102 PRProcessAttr
* pattr
= PR_NewProcessAttr();
104 NS_ASSERTION(pattr
, "couldn't allocate process attrs");
106 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdin
, &writeStdin
),
107 "couldn't create child stdin pipe");
108 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(readStdin
, true),
109 "couldn't set child stdin inheritable");
110 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardInput
, readStdin
);
112 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStdout
, &writeStdout
),
113 "couldn't create child stdout pipe");
114 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStdout
, true),
115 "couldn't set child stdout inheritable");
116 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardOutput
, writeStdout
);
118 NS_ASSERTION(PR_SUCCESS
== PR_CreatePipe(&readStderr
, &writeStderr
),
119 "couldn't create child stderr pipe");
120 NS_ASSERTION(PR_SUCCESS
== PR_SetFDInheritable(writeStderr
, true),
121 "couldn't set child stderr inheritable");
122 PR_ProcessAttrSetStdioRedirect(pattr
, PR_StandardError
, writeStderr
);
124 // set up argv with test name to run
125 char* const newArgv
[3] = {
126 strdup(sPathToThisBinary
),
131 // make sure the child will abort if an assertion fails
132 NS_ASSERTION(PR_SUCCESS
== PR_SetEnv(sAssertBehaviorEnv
),
133 "couldn't set XPCOM_DEBUG_BREAK env var");
136 NS_ASSERTION(proc
= PR_CreateProcess(sPathToThisBinary
,
138 0, // inherit environment
140 "couldn't create process");
142 PR_Close(writeStdout
);
143 PR_Close(writeStderr
);
146 mStdinfd
= writeStdin
;
147 mStdoutfd
= readStdout
;
148 mStderrfd
= readStderr
;
152 PR_DestroyProcessAttr(pattr
);
155 void RunToCompletion(uint32_t aWaitMs
)
159 PRPollDesc pollfds
[2];
161 bool stdoutOpen
= true, stderrOpen
= true;
165 PRIntervalTime now
= PR_IntervalNow();
166 PRIntervalTime deadline
= now
+ PR_MillisecondsToInterval(aWaitMs
);
168 while ((stdoutOpen
|| stderrOpen
) && now
< deadline
) {
171 pollfds
[nfds
].fd
= mStdoutfd
;
172 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
173 pollfds
[nfds
].out_flags
= 0;
177 pollfds
[nfds
].fd
= mStderrfd
;
178 pollfds
[nfds
].in_flags
= PR_POLL_READ
;
179 pollfds
[nfds
].out_flags
= 0;
183 int32_t rv
= PR_Poll(pollfds
, nfds
, deadline
- now
);
184 NS_ASSERTION(0 <= rv
, PR_ErrorToName(PR_GetError()));
186 if (0 == rv
) { // timeout
187 fputs("(timed out!)\n", stderr
);
188 Finish(false); // abnormal
192 for (int32_t i
= 0; i
< nfds
; ++i
) {
193 if (!pollfds
[i
].out_flags
)
196 bool isStdout
= mStdoutfd
== pollfds
[i
].fd
;
198 if (PR_POLL_READ
& pollfds
[i
].out_flags
) {
199 len
= PR_Read(pollfds
[i
].fd
, buf
, sizeof(buf
) - 1);
200 NS_ASSERTION(0 <= len
, PR_ErrorToName(PR_GetError()));
202 else if (PR_POLL_HUP
& pollfds
[i
].out_flags
) {
206 NS_ERROR(PR_ErrorToName(PR_GetError()));
224 now
= PR_IntervalNow();
228 fputs("(stdout still open!)\n", stderr
);
230 fputs("(stderr still open!)\n", stderr
);
232 fputs("(timed out!)\n", stderr
);
234 Finish(!stdoutOpen
&& !stderrOpen
&& now
<= deadline
);
238 void Finish(bool normalExit
) {
240 PR_KillProcess(mProc
);
243 PR_WaitProcess(mProc
, &dummy
);
246 PR_WaitProcess(mProc
, &mExitCode
); // this had better not block ...
254 PRFileDesc
* mStdinfd
; // writeable
255 PRFileDesc
* mStdoutfd
; // readable
256 PRFileDesc
* mStderrfd
; // readable
259 //-----------------------------------------------------------------------------
260 // Harness for checking detector errors
262 CheckForDeadlock(const char* test
, const char* const* findTokens
)
264 Subprocess
proc(test
);
265 proc
.RunToCompletion(5000);
267 if (0 == proc
.mExitCode
)
271 for (const char* const* tp
= findTokens
; *tp
; ++tp
) {
272 const char* const token
= *tp
;
273 #ifdef MOZILLA_INTERNAL_API
274 idx
= proc
.mStderr
.Find(token
, false, idx
);
276 nsCString
tokenCString(token
);
277 idx
= proc
.mStderr
.Find(tokenCString
, idx
);
280 printf("(missed token '%s' in output)\n", token
);
281 puts("----------------------------------\n");
282 puts(proc
.mStderr
.get());
283 puts("----------------------------------\n");
286 idx
+= strlen(token
);
292 //-----------------------------------------------------------------------------
293 // Single-threaded sanity tests
295 // Stupidest possible deadlock.
299 TestMutex
m1("dd.sanity.m1");
302 return 0; // not reached
308 const char* const tokens
[] = {
309 "###!!! ERROR: Potential deadlock detected",
310 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
311 "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
312 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
313 "###!!! ASSERTION: Potential deadlock detected",
316 if (CheckForDeadlock("Sanity", tokens
)) {
319 FAIL("deadlock not detected");
323 // Slightly less stupid deadlock.
327 TestMutex
m1("dd.sanity2.m1");
328 TestMutex
m2("dd.sanity2.m2");
332 return 0; // not reached
338 const char* const tokens
[] = {
339 "###!!! ERROR: Potential deadlock detected",
340 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
341 "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
342 "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
343 "###!!! Deadlock may happen NOW!", // better catch these easy cases...
344 "###!!! ASSERTION: Potential deadlock detected",
347 if (CheckForDeadlock("Sanity2", tokens
)) {
350 FAIL("deadlock not detected");
358 TestMutex
m1("dd.sanity3.m1");
359 TestMutex
m2("dd.sanity3.m2");
360 TestMutex
m3("dd.sanity3.m3");
361 TestMutex
m4("dd.sanity3.m4");
380 const char* const tokens
[] = {
381 "###!!! ERROR: Potential deadlock detected",
382 "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
383 "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
384 "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
385 "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
386 "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
387 "###!!! ASSERTION: Potential deadlock detected",
390 if (CheckForDeadlock("Sanity3", tokens
)) {
393 FAIL("deadlock not detected");
401 mozilla::ReentrantMonitor
m1("dd.sanity4.m1");
402 TestMutex
m2("dd.sanity4.m2");
412 const char* const tokens
[] = {
413 "Re-entering ReentrantMonitor after acquiring other resources",
414 "###!!! ERROR: Potential deadlock detected",
415 "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
416 "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
417 "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
418 "###!!! ASSERTION: Potential deadlock detected",
421 if (CheckForDeadlock("Sanity4", tokens
)) {
424 FAIL("deadlock not detected");
428 //-----------------------------------------------------------------------------
429 // Multithreaded tests
435 TwoThreads_thread(void* arg
)
437 int32_t m1First
= NS_PTR_TO_INT32(arg
);
455 ttM1
= new TestMutex("dd.twothreads.m1");
456 ttM2
= new TestMutex("dd.twothreads.m2");
458 NS_RUNTIMEABORT("couldn't allocate mutexes");
460 PRThread
* t1
= spawn(TwoThreads_thread
, (void*) 0);
463 PRThread
* t2
= spawn(TwoThreads_thread
, (void*) 1);
472 const char* const tokens
[] = {
473 "###!!! ERROR: Potential deadlock detected",
474 "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
475 "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
476 "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
477 "###!!! ASSERTION: Potential deadlock detected",
481 if (CheckForDeadlock("TwoThreads", tokens
)) {
484 FAIL("deadlock not detected");
490 const uint32_t K
= 100000;
493 ContentionNoDeadlock_thread(void* arg
)
495 int32_t starti
= NS_PTR_TO_INT32(arg
);
497 for (uint32_t k
= 0; k
< K
; ++k
) {
498 for (int32_t i
= starti
; i
< (int32_t) ArrayLength(cndMs
); ++i
)
500 // comment out the next two lines for deadlocking fun!
501 for (int32_t i
= ArrayLength(cndMs
) - 1; i
>= starti
; --i
)
504 starti
= (starti
+ 1) % 3;
509 ContentionNoDeadlock_Child()
511 PRThread
* threads
[3];
513 for (uint32_t i
= 0; i
< ArrayLength(cndMs
); ++i
)
514 cndMs
[i
] = new TestMutex("dd.cnd.ms");
516 for (int32_t i
= 0; i
< (int32_t) ArrayLength(threads
); ++i
)
517 threads
[i
] = spawn(ContentionNoDeadlock_thread
, NS_INT32_TO_PTR(i
));
519 for (uint32_t i
= 0; i
< ArrayLength(threads
); ++i
)
520 PR_JoinThread(threads
[i
]);
522 for (uint32_t i
= 0; i
< ArrayLength(cndMs
); ++i
)
529 ContentionNoDeadlock()
531 const char * func
= __func__
;
532 Subprocess
proc(func
);
533 proc
.RunToCompletion(60000);
534 if (0 != proc
.mExitCode
) {
535 printf("(expected 0 == return code, got %d)\n", proc
.mExitCode
);
536 puts("(output)\n----------------------------------\n");
537 puts(proc
.mStdout
.get());
538 puts("----------------------------------\n");
539 puts("(error output)\n----------------------------------\n");
540 puts(proc
.mStderr
.get());
541 puts("----------------------------------\n");
550 //-----------------------------------------------------------------------------
553 main(int argc
, char** argv
)
556 // XXX can we run w/o scoped XPCOM?
557 const char* test
= argv
[1];
558 ScopedXPCOM
xpcom(test
);
562 // running in a spawned process. call the specificed child function.
563 if (!strcmp("Sanity", test
))
564 return Sanity_Child();
565 if (!strcmp("Sanity2", test
))
566 return Sanity2_Child();
567 if (!strcmp("Sanity3", test
))
568 return Sanity3_Child();
569 if (!strcmp("Sanity4", test
))
570 return Sanity4_Child();
572 if (!strcmp("TwoThreads", test
))
573 return TwoThreads_Child();
574 if (!strcmp("ContentionNoDeadlock", test
))
575 return ContentionNoDeadlock_Child();
577 fail("%s | %s - unknown child test", __FILE__
, __FUNCTION__
);
581 ScopedXPCOM
xpcom("Storage deadlock detector correctness (" __FILE__
")");
585 // in the first invocation of this process. we will be the "driver".
588 sPathToThisBinary
= argv
[0];
590 if (NS_FAILED(Sanity()))
592 if (NS_FAILED(Sanity2()))
594 if (NS_FAILED(Sanity3()))
596 if (NS_FAILED(Sanity4()))
599 if (NS_FAILED(TwoThreads()))
601 if (NS_FAILED(ContentionNoDeadlock()))