xpcom/tests/TestDeadlockDetector.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/ArrayUtils.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)
    26 {
    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);
    34 }
    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
    54 {
    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),
    92             0
    93         };
    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);
   117     }
   119     void RunToCompletion(uint32_t aWaitMs)
   120     {
   121         PR_Close(mStdinfd);
   123         PRPollDesc pollfds[2];
   124         int32_t nfds;
   125         bool stdoutOpen = true, stderrOpen = true;
   126         char buf[4096];
   128         PRIntervalTime now = PR_IntervalNow();
   129         PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
   131         while ((stdoutOpen || stderrOpen) && now < deadline) {
   132             nfds = 0;
   133             if (stdoutOpen) {
   134                 pollfds[nfds].fd = mStdoutfd;
   135                 pollfds[nfds].in_flags = PR_POLL_READ;
   136                 pollfds[nfds].out_flags = 0;
   137                 ++nfds;
   138             }
   139             if (stderrOpen) {
   140                 pollfds[nfds].fd = mStderrfd;
   141                 pollfds[nfds].in_flags = PR_POLL_READ;
   142                 pollfds[nfds].out_flags = 0;
   143                 ++nfds;
   144             }
   146             int32_t rv = PR_Poll(pollfds, nfds, deadline - now);
   147             NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError()));
   149             if (0 == rv) {      // timeout
   150                 fputs("(timed out!)\n", stderr);
   151                 Finish(false); // abnormal
   152                 return;
   153             }
   155             for (int32_t i = 0; i < nfds; ++i) {
   156                 if (!pollfds[i].out_flags)
   157                     continue;
   159                 bool isStdout = mStdoutfd == pollfds[i].fd;
   160                 int32_t len = 0;
   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()));
   165                 }
   166                 else if (!(PR_POLL_HUP & pollfds[i].out_flags)) {
   167                     NS_ERROR(PR_ErrorToName(PR_GetError()));
   168                 }
   170                 if (0 < len) {
   171                     buf[len] = '\0';
   172                     if (isStdout)
   173                         mStdout += buf;
   174                     else
   175                         mStderr += buf;
   176                 }
   177                 else if (isStdout) {
   178                     stdoutOpen = false;
   179                 }
   180                 else {
   181                     stderrOpen = false;
   182                 }
   183             }
   185             now = PR_IntervalNow();
   186         }
   188         if (stdoutOpen)
   189             fputs("(stdout still open!)\n", stderr);
   190         if (stderrOpen)
   191             fputs("(stderr still open!)\n", stderr);
   192         if (now > deadline)
   193             fputs("(timed out!)\n", stderr);
   195         Finish(!stdoutOpen && !stderrOpen && now <= deadline);
   196     }
   198 private:
   199     void Finish(bool normalExit) {
   200         if (!normalExit) {
   201             PR_KillProcess(mProc);
   202             mExitCode = -1;
   203             int32_t dummy;
   204             PR_WaitProcess(mProc, &dummy);
   205         }
   206         else {
   207             PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
   208         }
   210         PR_Close(mStdoutfd);
   211         PR_Close(mStderrfd);
   212     }
   214     PRProcess* mProc;
   215     PRFileDesc* mStdinfd;         // writeable
   216     PRFileDesc* mStdoutfd;        // readable
   217     PRFileDesc* mStderrfd;        // readable
   218 };
   220 //-----------------------------------------------------------------------------
   221 // Harness for checking detector errors
   222 bool
   223 CheckForDeadlock(const char* test, const char* const* findTokens)
   224 {
   225     Subprocess proc(test);
   226     proc.RunToCompletion(5000);
   228     if (0 == proc.mExitCode)
   229         return false;
   231     int32_t idx = 0;
   232     for (const char* const* tp = findTokens; *tp; ++tp) {
   233         const char* const token = *tp;
   234 #ifdef MOZILLA_INTERNAL_API
   235         idx = proc.mStderr.Find(token, false, idx);
   236 #else
   237         nsCString tokenCString(token);
   238         idx = proc.mStderr.Find(tokenCString, idx);
   239 #endif
   240         if (-1 == idx) {
   241             printf("(missed token '%s' in output)\n", token);
   242             puts("----------------------------------\n");
   243             puts(proc.mStderr.get());
   244             puts("----------------------------------\n");
   245             return false;
   246         }
   247         idx += strlen(token);
   248     }
   250     return true;
   251 }
   253 //-----------------------------------------------------------------------------
   254 // Single-threaded sanity tests
   256 // Stupidest possible deadlock.
   257 int
   258 Sanity_Child()
   259 {
   260     mozilla::Mutex m1("dd.sanity.m1");
   261     m1.Lock();
   262     m1.Lock();
   263     return 0;                  // not reached
   264 }
   266 nsresult
   267 Sanity()
   268 {
   269     const char* const tokens[] = {
   270         "###!!! ERROR: Potential deadlock detected",
   271         "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
   272         "=== Cycle completed at\n--- Mutex : dd.sanity.m1",
   273         "###!!! Deadlock may happen NOW!", // better catch these easy cases...
   274         "###!!! ASSERTION: Potential deadlock detected",
   275         0
   276     };
   277     if (CheckForDeadlock("Sanity", tokens)) {
   278         PASS();
   279     } else {
   280         FAIL("deadlock not detected");
   281     }
   282 }
   284 // Slightly less stupid deadlock.
   285 int
   286 Sanity2_Child()
   287 {
   288     mozilla::Mutex m1("dd.sanity2.m1");
   289     mozilla::Mutex m2("dd.sanity2.m2");
   290     m1.Lock();
   291     m2.Lock();
   292     m1.Lock();
   293     return 0;                  // not reached
   294 }
   296 nsresult
   297 Sanity2()
   298 {
   299     const char* const tokens[] = {
   300         "###!!! ERROR: Potential deadlock detected",
   301         "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
   302         "--- Next dependency:\n--- Mutex : dd.sanity2.m2",
   303         "=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
   304         "###!!! Deadlock may happen NOW!", // better catch these easy cases...
   305         "###!!! ASSERTION: Potential deadlock detected",
   306         0
   307     };
   308     if (CheckForDeadlock("Sanity2", tokens)) {
   309         PASS();
   310     } else {
   311         FAIL("deadlock not detected");
   312     }
   313 }
   316 int
   317 Sanity3_Child()
   318 {
   319     mozilla::Mutex m1("dd.sanity3.m1");
   320     mozilla::Mutex m2("dd.sanity3.m2");
   321     mozilla::Mutex m3("dd.sanity3.m3");
   322     mozilla::Mutex m4("dd.sanity3.m4");
   324     m1.Lock();
   325     m2.Lock();
   326     m3.Lock();
   327     m4.Lock();
   328     m4.Unlock();
   329     m3.Unlock();
   330     m2.Unlock();
   331     m1.Unlock();
   333     m4.Lock();
   334     m1.Lock();
   335     return 0;
   336 }
   338 nsresult
   339 Sanity3()
   340 {
   341     const char* const tokens[] = {
   342         "###!!! ERROR: Potential deadlock detected",
   343         "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
   344         "--- Next dependency:\n--- Mutex : dd.sanity3.m2",
   345         "--- Next dependency:\n--- Mutex : dd.sanity3.m3",
   346         "--- Next dependency:\n--- Mutex : dd.sanity3.m4",
   347         "=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
   348         "###!!! ASSERTION: Potential deadlock detected",
   349         0
   350     };
   351     if (CheckForDeadlock("Sanity3", tokens)) {
   352         PASS();
   353     } else {
   354         FAIL("deadlock not detected");
   355     }
   356 }
   359 int
   360 Sanity4_Child()
   361 {
   362     mozilla::ReentrantMonitor m1("dd.sanity4.m1");
   363     mozilla::Mutex m2("dd.sanity4.m2");
   364     m1.Enter();
   365     m2.Lock();
   366     m1.Enter();
   367     return 0;
   368 }
   370 nsresult
   371 Sanity4()
   372 {
   373     const char* const tokens[] = {
   374         "Re-entering ReentrantMonitor after acquiring other resources",
   375         "###!!! ERROR: Potential deadlock detected",
   376         "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
   377         "--- Next dependency:\n--- Mutex : dd.sanity4.m2",
   378         "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
   379         "###!!! ASSERTION: Potential deadlock detected",
   380         0
   381     };
   382     if (CheckForDeadlock("Sanity4", tokens)) {
   383         PASS();
   384     } else {
   385         FAIL("deadlock not detected");
   386     }
   387 }
   389 //-----------------------------------------------------------------------------
   390 // Multithreaded tests
   392 mozilla::Mutex* ttM1;
   393 mozilla::Mutex* ttM2;
   395 static void
   396 TwoThreads_thread(void* arg)
   397 {
   398     int32_t m1First = NS_PTR_TO_INT32(arg);
   399     if (m1First) {
   400         ttM1->Lock();
   401         ttM2->Lock();
   402         ttM2->Unlock();
   403         ttM1->Unlock();
   404     }
   405     else {
   406         ttM2->Lock();
   407         ttM1->Lock();
   408         ttM1->Unlock();
   409         ttM2->Unlock();
   410     }
   411 }
   413 int
   414 TwoThreads_Child()
   415 {
   416     ttM1 = new mozilla::Mutex("dd.twothreads.m1");
   417     ttM2 = new mozilla::Mutex("dd.twothreads.m2");
   418     if (!ttM1 || !ttM2)
   419         NS_RUNTIMEABORT("couldn't allocate mutexes");
   421     PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
   422     PR_JoinThread(t1);
   424     PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
   425     PR_JoinThread(t2);
   427     return 0;
   428 }
   430 nsresult
   431 TwoThreads()
   432 {
   433     const char* const tokens[] = {
   434         "###!!! ERROR: Potential deadlock detected",
   435         "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
   436         "--- Next dependency:\n--- Mutex : dd.twothreads.m1",
   437         "=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
   438         "###!!! ASSERTION: Potential deadlock detected",
   439         0
   440     };
   442     if (CheckForDeadlock("TwoThreads", tokens)) {
   443         PASS();
   444     } else {
   445         FAIL("deadlock not detected");
   446     }
   447 }
   450 mozilla::Mutex* cndMs[4];
   451 const uint32_t K = 100000;
   453 static void
   454 ContentionNoDeadlock_thread(void* arg)
   455 {
   456     int32_t starti = NS_PTR_TO_INT32(arg);
   458     for (uint32_t k = 0; k < K; ++k) {
   459         for (int32_t i = starti; i < (int32_t) ArrayLength(cndMs); ++i)
   460             cndMs[i]->Lock();
   461         // comment out the next two lines for deadlocking fun!
   462         for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i)
   463             cndMs[i]->Unlock();
   465         starti = (starti + 1) % 3;
   466     }
   467 }
   469 int
   470 ContentionNoDeadlock_Child()
   471 {
   472     PRThread* threads[3];
   474     for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
   475         cndMs[i] = new mozilla::Mutex("dd.cnd.ms");
   477     for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
   478         threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i));
   480     for (uint32_t i = 0; i < ArrayLength(threads); ++i)
   481         PR_JoinThread(threads[i]);
   483     for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
   484         delete cndMs[i];
   486     return 0;
   487 }
   489 nsresult
   490 ContentionNoDeadlock()
   491 {
   492     const char * func = __func__;
   493     Subprocess proc(func);
   494     proc.RunToCompletion(60000);
   495     if (0 != proc.mExitCode) {
   496         printf("(expected 0 == return code, got %d)\n", proc.mExitCode);
   497         puts("(output)\n----------------------------------\n");
   498         puts(proc.mStdout.get());
   499         puts("----------------------------------\n");
   500         puts("(error output)\n----------------------------------\n");
   501         puts(proc.mStderr.get());
   502         puts("----------------------------------\n");
   504         FAIL("deadlock");
   505     }
   506     PASS();
   507 }
   511 //-----------------------------------------------------------------------------
   513 int
   514 main(int argc, char** argv)
   515 {
   516     if (1 < argc) {
   517         // XXX can we run w/o scoped XPCOM?
   518         const char* test = argv[1];
   519         ScopedXPCOM xpcom(test);
   520         if (xpcom.failed())
   521             return 1;
   523         // running in a spawned process.  call the specificed child function.
   524         if (!strcmp("Sanity", test))
   525             return Sanity_Child();
   526         if (!strcmp("Sanity2", test))
   527             return Sanity2_Child();
   528         if (!strcmp("Sanity3", test))
   529             return Sanity3_Child();
   530         if (!strcmp("Sanity4", test))
   531             return Sanity4_Child();
   533         if (!strcmp("TwoThreads", test))
   534             return TwoThreads_Child();
   535         if (!strcmp("ContentionNoDeadlock", test))
   536             return ContentionNoDeadlock_Child();
   538         fail("%s | %s - unknown child test", __FILE__, __FUNCTION__);
   539         return 2;
   540     }
   542     ScopedXPCOM xpcom("XPCOM deadlock detector correctness (" __FILE__ ")");
   543     if (xpcom.failed())
   544         return 1;
   546     // in the first invocation of this process.  we will be the "driver".
   547     int rv = 0;
   549     sPathToThisBinary = argv[0];
   551     if (NS_FAILED(Sanity()))
   552         rv = 1;
   553     if (NS_FAILED(Sanity2()))
   554         rv = 1;
   555     if (NS_FAILED(Sanity3()))
   556         rv = 1;
   557     if (NS_FAILED(Sanity4()))
   558         rv = 1;
   560     if (NS_FAILED(TwoThreads()))
   561         rv = 1;
   562     if (NS_FAILED(ContentionNoDeadlock()))
   563         rv = 1;
   565     return rv;
   566 }

mercurial