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