Bug 846687 - Set the transport as non-seekable if the server sends Accept-Ranges...
[gecko.git] / xpcom / tests / TestDeadlockDetector.cpp
blobf2e725d738a0e8ff9b9be898ae923a7692609245
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: sw=4 ts=4 et :
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"
9 #include "prenv.h"
10 #include "prerror.h"
11 #include "prio.h"
12 #include "prproces.h"
14 #include "nsMemory.h"
16 #include "mozilla/CondVar.h"
17 #include "mozilla/ReentrantMonitor.h"
18 #include "mozilla/Mutex.h"
20 #include "TestHarness.h"
22 using namespace mozilla;
24 static PRThread*
25 spawn(void (*run)(void*), void* arg)
27 return PR_CreateThread(PR_SYSTEM_THREAD,
28 run,
29 arg,
30 PR_PRIORITY_NORMAL,
31 PR_GLOBAL_THREAD,
32 PR_JOINABLE_THREAD,
33 0);
36 #define PASS() \
37 do { \
38 passed(__FUNCTION__); \
39 return NS_OK; \
40 } while (0)
42 #define FAIL(why) \
43 do { \
44 fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
45 return NS_ERROR_FAILURE; \
46 } while (0)
48 //-----------------------------------------------------------------------------
50 static const char* sPathToThisBinary;
51 static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort";
53 class Subprocess
55 public:
56 // not available until process finishes
57 int32_t mExitCode;
58 nsCString mStdout;
59 nsCString mStderr;
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),
91 strdup(aTestName),
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");
99 PRProcess* proc;
100 NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary,
101 newArgv,
102 0, // inherit environment
103 pattr),
104 "couldn't create process");
105 PR_Close(readStdin);
106 PR_Close(writeStdout);
107 PR_Close(writeStderr);
109 mProc = proc;
110 mStdinfd = writeStdin;
111 mStdoutfd = readStdout;
112 mStderrfd = readStderr;
114 free(newArgv[0]);
115 free(newArgv[1]);
116 PR_DestroyProcessAttr(pattr);
119 void RunToCompletion(uint32_t aWaitMs)
121 PR_Close(mStdinfd);
123 PRPollDesc pollfds[2];
124 int32_t nfds;
125 bool stdoutOpen = true, stderrOpen = true;
126 char buf[4096];
127 int32_t len;
129 PRIntervalTime now = PR_IntervalNow();
130 PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
132 while ((stdoutOpen || stderrOpen) && now < deadline) {
133 nfds = 0;
134 if (stdoutOpen) {
135 pollfds[nfds].fd = mStdoutfd;
136 pollfds[nfds].in_flags = PR_POLL_READ;
137 pollfds[nfds].out_flags = 0;
138 ++nfds;
140 if (stderrOpen) {
141 pollfds[nfds].fd = mStderrfd;
142 pollfds[nfds].in_flags = PR_POLL_READ;
143 pollfds[nfds].out_flags = 0;
144 ++nfds;
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
153 return;
156 for (int32_t i = 0; i < nfds; ++i) {
157 if (!pollfds[i].out_flags)
158 continue;
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) {
167 len = 0;
169 else {
170 NS_ERROR(PR_ErrorToName(PR_GetError()));
173 if (0 < len) {
174 buf[len] = '\0';
175 if (isStdout)
176 mStdout += buf;
177 else
178 mStderr += buf;
180 else if (isStdout) {
181 stdoutOpen = false;
183 else {
184 stderrOpen = false;
188 now = PR_IntervalNow();
191 if (stdoutOpen)
192 fputs("(stdout still open!)\n", stderr);
193 if (stderrOpen)
194 fputs("(stderr still open!)\n", stderr);
195 if (now > deadline)
196 fputs("(timed out!)\n", stderr);
198 Finish(!stdoutOpen && !stderrOpen && now <= deadline);
201 private:
202 void Finish(bool normalExit) {
203 if (!normalExit) {
204 PR_KillProcess(mProc);
205 mExitCode = -1;
206 int32_t dummy;
207 PR_WaitProcess(mProc, &dummy);
209 else {
210 PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
213 PR_Close(mStdoutfd);
214 PR_Close(mStderrfd);
217 PRProcess* mProc;
218 PRFileDesc* mStdinfd; // writeable
219 PRFileDesc* mStdoutfd; // readable
220 PRFileDesc* mStderrfd; // readable
223 //-----------------------------------------------------------------------------
224 // Harness for checking detector errors
225 bool
226 CheckForDeadlock(const char* test, const char* const* findTokens)
228 Subprocess proc(test);
229 proc.RunToCompletion(5000);
231 if (0 == proc.mExitCode)
232 return false;
234 int32_t idx = 0;
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);
239 #else
240 nsCString tokenCString(token);
241 idx = proc.mStderr.Find(tokenCString, idx);
242 #endif
243 if (-1 == idx) {
244 printf("(missed token '%s' in output)\n", token);
245 puts("----------------------------------\n");
246 puts(proc.mStderr.get());
247 puts("----------------------------------\n");
248 return false;
250 idx += strlen(token);
253 return true;
256 //-----------------------------------------------------------------------------
257 // Single-threaded sanity tests
259 // Stupidest possible deadlock.
261 Sanity_Child()
263 mozilla::Mutex m1("dd.sanity.m1");
264 m1.Lock();
265 m1.Lock();
266 return 0; // not reached
269 nsresult
270 Sanity()
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)) {
281 PASS();
282 } else {
283 FAIL("deadlock not detected");
287 // Slightly less stupid deadlock.
289 Sanity2_Child()
291 mozilla::Mutex m1("dd.sanity2.m1");
292 mozilla::Mutex m2("dd.sanity2.m2");
293 m1.Lock();
294 m2.Lock();
295 m1.Lock();
296 return 0; // not reached
299 nsresult
300 Sanity2()
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)) {
312 PASS();
313 } else {
314 FAIL("deadlock not detected");
320 Sanity3_Child()
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");
327 m1.Lock();
328 m2.Lock();
329 m3.Lock();
330 m4.Lock();
331 m4.Unlock();
332 m3.Unlock();
333 m2.Unlock();
334 m1.Unlock();
336 m4.Lock();
337 m1.Lock();
338 return 0;
341 nsresult
342 Sanity3()
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)) {
355 PASS();
356 } else {
357 FAIL("deadlock not detected");
363 Sanity4_Child()
365 mozilla::ReentrantMonitor m1("dd.sanity4.m1");
366 mozilla::Mutex m2("dd.sanity4.m2");
367 m1.Enter();
368 m2.Lock();
369 m1.Enter();
370 return 0;
373 nsresult
374 Sanity4()
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)) {
386 PASS();
387 } else {
388 FAIL("deadlock not detected");
392 //-----------------------------------------------------------------------------
393 // Multithreaded tests
395 mozilla::Mutex* ttM1;
396 mozilla::Mutex* ttM2;
398 static void
399 TwoThreads_thread(void* arg)
401 int32_t m1First = NS_PTR_TO_INT32(arg);
402 if (m1First) {
403 ttM1->Lock();
404 ttM2->Lock();
405 ttM2->Unlock();
406 ttM1->Unlock();
408 else {
409 ttM2->Lock();
410 ttM1->Lock();
411 ttM1->Unlock();
412 ttM2->Unlock();
417 TwoThreads_Child()
419 ttM1 = new mozilla::Mutex("dd.twothreads.m1");
420 ttM2 = new mozilla::Mutex("dd.twothreads.m2");
421 if (!ttM1 || !ttM2)
422 NS_RUNTIMEABORT("couldn't allocate mutexes");
424 PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
425 PR_JoinThread(t1);
427 PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
428 PR_JoinThread(t2);
430 return 0;
433 nsresult
434 TwoThreads()
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)) {
446 PASS();
447 } else {
448 FAIL("deadlock not detected");
453 mozilla::Mutex* cndMs[4];
454 const uint32_t K = 100000;
456 static void
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)
463 cndMs[i]->Lock();
464 // comment out the next two lines for deadlocking fun!
465 for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i)
466 cndMs[i]->Unlock();
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)
487 delete cndMs[i];
489 return 0;
492 nsresult
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");
507 FAIL("deadlock");
509 PASS();
514 //-----------------------------------------------------------------------------
517 main(int argc, char** argv)
519 if (1 < argc) {
520 // XXX can we run w/o scoped XPCOM?
521 const char* test = argv[1];
522 ScopedXPCOM xpcom(test);
523 if (xpcom.failed())
524 return 1;
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__);
542 return 2;
545 ScopedXPCOM xpcom("XPCOM deadlock detector correctness (" __FILE__ ")");
546 if (xpcom.failed())
547 return 1;
549 // in the first invocation of this process. we will be the "driver".
550 int rv = 0;
552 sPathToThisBinary = argv[0];
554 if (NS_FAILED(Sanity()))
555 rv = 1;
556 if (NS_FAILED(Sanity2()))
557 rv = 1;
558 if (NS_FAILED(Sanity3()))
559 rv = 1;
560 if (NS_FAILED(Sanity4()))
561 rv = 1;
563 if (NS_FAILED(TwoThreads()))
564 rv = 1;
565 if (NS_FAILED(ContentionNoDeadlock()))
566 rv = 1;
568 return rv;