michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: sw=4 ts=4 et : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Note: This file is a copy of xpcom/tests/TestDeadlockDetector.cpp, but all michael@0: * mutexes were turned into SQLiteMutexes. michael@0: */ michael@0: michael@0: #include "prenv.h" michael@0: #include "prerror.h" michael@0: #include "prio.h" michael@0: #include "prproces.h" michael@0: michael@0: #include "nsMemory.h" michael@0: michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/ReentrantMonitor.h" michael@0: #include "SQLiteMutex.h" michael@0: michael@0: #include "TestHarness.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /** michael@0: * Helper class to allocate a sqlite3_mutex for our SQLiteMutex. Also makes michael@0: * keeping the test files in sync easier. michael@0: */ michael@0: class TestMutex : public mozilla::storage::SQLiteMutex michael@0: { michael@0: public: michael@0: TestMutex(const char* aName) michael@0: : mozilla::storage::SQLiteMutex(aName) michael@0: , mInner(sqlite3_mutex_alloc(SQLITE_MUTEX_FAST)) michael@0: { michael@0: NS_ASSERTION(mInner, "could not allocate a sqlite3_mutex"); michael@0: initWithMutex(mInner); michael@0: } michael@0: michael@0: ~TestMutex() michael@0: { michael@0: sqlite3_mutex_free(mInner); michael@0: } michael@0: michael@0: void Lock() michael@0: { michael@0: lock(); michael@0: } michael@0: michael@0: void Unlock() michael@0: { michael@0: unlock(); michael@0: } michael@0: private: michael@0: sqlite3_mutex *mInner; michael@0: }; michael@0: michael@0: static PRThread* michael@0: spawn(void (*run)(void*), void* arg) michael@0: { michael@0: return PR_CreateThread(PR_SYSTEM_THREAD, michael@0: run, michael@0: arg, michael@0: PR_PRIORITY_NORMAL, michael@0: PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, michael@0: 0); michael@0: } michael@0: michael@0: #define PASS() \ michael@0: do { \ michael@0: passed(__FUNCTION__); \ michael@0: return NS_OK; \ michael@0: } while (0) michael@0: michael@0: #define FAIL(why) \ michael@0: do { \ michael@0: fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \ michael@0: return NS_ERROR_FAILURE; \ michael@0: } while (0) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static const char* sPathToThisBinary; michael@0: static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort"; michael@0: michael@0: class Subprocess michael@0: { michael@0: public: michael@0: // not available until process finishes michael@0: int32_t mExitCode; michael@0: nsCString mStdout; michael@0: nsCString mStderr; michael@0: michael@0: Subprocess(const char* aTestName) { michael@0: // set up stdio redirection michael@0: PRFileDesc* readStdin; PRFileDesc* writeStdin; michael@0: PRFileDesc* readStdout; PRFileDesc* writeStdout; michael@0: PRFileDesc* readStderr; PRFileDesc* writeStderr; michael@0: PRProcessAttr* pattr = PR_NewProcessAttr(); michael@0: michael@0: NS_ASSERTION(pattr, "couldn't allocate process attrs"); michael@0: michael@0: NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdin, &writeStdin), michael@0: "couldn't create child stdin pipe"); michael@0: NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(readStdin, true), michael@0: "couldn't set child stdin inheritable"); michael@0: PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardInput, readStdin); michael@0: michael@0: NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdout, &writeStdout), michael@0: "couldn't create child stdout pipe"); michael@0: NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStdout, true), michael@0: "couldn't set child stdout inheritable"); michael@0: PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardOutput, writeStdout); michael@0: michael@0: NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStderr, &writeStderr), michael@0: "couldn't create child stderr pipe"); michael@0: NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStderr, true), michael@0: "couldn't set child stderr inheritable"); michael@0: PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardError, writeStderr); michael@0: michael@0: // set up argv with test name to run michael@0: char* const newArgv[3] = { michael@0: strdup(sPathToThisBinary), michael@0: strdup(aTestName), michael@0: 0 michael@0: }; michael@0: michael@0: // make sure the child will abort if an assertion fails michael@0: NS_ASSERTION(PR_SUCCESS == PR_SetEnv(sAssertBehaviorEnv), michael@0: "couldn't set XPCOM_DEBUG_BREAK env var"); michael@0: michael@0: PRProcess* proc; michael@0: NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary, michael@0: newArgv, michael@0: 0, // inherit environment michael@0: pattr), michael@0: "couldn't create process"); michael@0: PR_Close(readStdin); michael@0: PR_Close(writeStdout); michael@0: PR_Close(writeStderr); michael@0: michael@0: mProc = proc; michael@0: mStdinfd = writeStdin; michael@0: mStdoutfd = readStdout; michael@0: mStderrfd = readStderr; michael@0: michael@0: free(newArgv[0]); michael@0: free(newArgv[1]); michael@0: PR_DestroyProcessAttr(pattr); michael@0: } michael@0: michael@0: void RunToCompletion(uint32_t aWaitMs) michael@0: { michael@0: PR_Close(mStdinfd); michael@0: michael@0: PRPollDesc pollfds[2]; michael@0: int32_t nfds; michael@0: bool stdoutOpen = true, stderrOpen = true; michael@0: char buf[4096]; michael@0: int32_t len; michael@0: michael@0: PRIntervalTime now = PR_IntervalNow(); michael@0: PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs); michael@0: michael@0: while ((stdoutOpen || stderrOpen) && now < deadline) { michael@0: nfds = 0; michael@0: if (stdoutOpen) { michael@0: pollfds[nfds].fd = mStdoutfd; michael@0: pollfds[nfds].in_flags = PR_POLL_READ; michael@0: pollfds[nfds].out_flags = 0; michael@0: ++nfds; michael@0: } michael@0: if (stderrOpen) { michael@0: pollfds[nfds].fd = mStderrfd; michael@0: pollfds[nfds].in_flags = PR_POLL_READ; michael@0: pollfds[nfds].out_flags = 0; michael@0: ++nfds; michael@0: } michael@0: michael@0: int32_t rv = PR_Poll(pollfds, nfds, deadline - now); michael@0: NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError())); michael@0: michael@0: if (0 == rv) { // timeout michael@0: fputs("(timed out!)\n", stderr); michael@0: Finish(false); // abnormal michael@0: return; michael@0: } michael@0: michael@0: for (int32_t i = 0; i < nfds; ++i) { michael@0: if (!pollfds[i].out_flags) michael@0: continue; michael@0: michael@0: bool isStdout = mStdoutfd == pollfds[i].fd; michael@0: michael@0: if (PR_POLL_READ & pollfds[i].out_flags) { michael@0: len = PR_Read(pollfds[i].fd, buf, sizeof(buf) - 1); michael@0: NS_ASSERTION(0 <= len, PR_ErrorToName(PR_GetError())); michael@0: } michael@0: else if (PR_POLL_HUP & pollfds[i].out_flags) { michael@0: len = 0; michael@0: } michael@0: else { michael@0: NS_ERROR(PR_ErrorToName(PR_GetError())); michael@0: } michael@0: michael@0: if (0 < len) { michael@0: buf[len] = '\0'; michael@0: if (isStdout) michael@0: mStdout += buf; michael@0: else michael@0: mStderr += buf; michael@0: } michael@0: else if (isStdout) { michael@0: stdoutOpen = false; michael@0: } michael@0: else { michael@0: stderrOpen = false; michael@0: } michael@0: } michael@0: michael@0: now = PR_IntervalNow(); michael@0: } michael@0: michael@0: if (stdoutOpen) michael@0: fputs("(stdout still open!)\n", stderr); michael@0: if (stderrOpen) michael@0: fputs("(stderr still open!)\n", stderr); michael@0: if (now > deadline) michael@0: fputs("(timed out!)\n", stderr); michael@0: michael@0: Finish(!stdoutOpen && !stderrOpen && now <= deadline); michael@0: } michael@0: michael@0: private: michael@0: void Finish(bool normalExit) { michael@0: if (!normalExit) { michael@0: PR_KillProcess(mProc); michael@0: mExitCode = -1; michael@0: int32_t dummy; michael@0: PR_WaitProcess(mProc, &dummy); michael@0: } michael@0: else { michael@0: PR_WaitProcess(mProc, &mExitCode); // this had better not block ... michael@0: } michael@0: michael@0: PR_Close(mStdoutfd); michael@0: PR_Close(mStderrfd); michael@0: } michael@0: michael@0: PRProcess* mProc; michael@0: PRFileDesc* mStdinfd; // writeable michael@0: PRFileDesc* mStdoutfd; // readable michael@0: PRFileDesc* mStderrfd; // readable michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Harness for checking detector errors michael@0: bool michael@0: CheckForDeadlock(const char* test, const char* const* findTokens) michael@0: { michael@0: Subprocess proc(test); michael@0: proc.RunToCompletion(5000); michael@0: michael@0: if (0 == proc.mExitCode) michael@0: return false; michael@0: michael@0: int32_t idx = 0; michael@0: for (const char* const* tp = findTokens; *tp; ++tp) { michael@0: const char* const token = *tp; michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: idx = proc.mStderr.Find(token, false, idx); michael@0: #else michael@0: nsCString tokenCString(token); michael@0: idx = proc.mStderr.Find(tokenCString, idx); michael@0: #endif michael@0: if (-1 == idx) { michael@0: printf("(missed token '%s' in output)\n", token); michael@0: puts("----------------------------------\n"); michael@0: puts(proc.mStderr.get()); michael@0: puts("----------------------------------\n"); michael@0: return false; michael@0: } michael@0: idx += strlen(token); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Single-threaded sanity tests michael@0: michael@0: // Stupidest possible deadlock. michael@0: int michael@0: Sanity_Child() michael@0: { michael@0: TestMutex m1("dd.sanity.m1"); michael@0: m1.Lock(); michael@0: m1.Lock(); michael@0: return 0; // not reached michael@0: } michael@0: michael@0: nsresult michael@0: Sanity() michael@0: { michael@0: const char* const tokens[] = { michael@0: "###!!! ERROR: Potential deadlock detected", michael@0: "=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1", michael@0: "=== Cycle completed at\n--- Mutex : dd.sanity.m1", michael@0: "###!!! Deadlock may happen NOW!", // better catch these easy cases... michael@0: "###!!! ASSERTION: Potential deadlock detected", michael@0: 0 michael@0: }; michael@0: if (CheckForDeadlock("Sanity", tokens)) { michael@0: PASS(); michael@0: } else { michael@0: FAIL("deadlock not detected"); michael@0: } michael@0: } michael@0: michael@0: // Slightly less stupid deadlock. michael@0: int michael@0: Sanity2_Child() michael@0: { michael@0: TestMutex m1("dd.sanity2.m1"); michael@0: TestMutex m2("dd.sanity2.m2"); michael@0: m1.Lock(); michael@0: m2.Lock(); michael@0: m1.Lock(); michael@0: return 0; // not reached michael@0: } michael@0: michael@0: nsresult michael@0: Sanity2() michael@0: { michael@0: const char* const tokens[] = { michael@0: "###!!! ERROR: Potential deadlock detected", michael@0: "=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1", michael@0: "--- Next dependency:\n--- Mutex : dd.sanity2.m2", michael@0: "=== Cycle completed at\n--- Mutex : dd.sanity2.m1", michael@0: "###!!! Deadlock may happen NOW!", // better catch these easy cases... michael@0: "###!!! ASSERTION: Potential deadlock detected", michael@0: 0 michael@0: }; michael@0: if (CheckForDeadlock("Sanity2", tokens)) { michael@0: PASS(); michael@0: } else { michael@0: FAIL("deadlock not detected"); michael@0: } michael@0: } michael@0: michael@0: michael@0: int michael@0: Sanity3_Child() michael@0: { michael@0: TestMutex m1("dd.sanity3.m1"); michael@0: TestMutex m2("dd.sanity3.m2"); michael@0: TestMutex m3("dd.sanity3.m3"); michael@0: TestMutex m4("dd.sanity3.m4"); michael@0: michael@0: m1.Lock(); michael@0: m2.Lock(); michael@0: m3.Lock(); michael@0: m4.Lock(); michael@0: m4.Unlock(); michael@0: m3.Unlock(); michael@0: m2.Unlock(); michael@0: m1.Unlock(); michael@0: michael@0: m4.Lock(); michael@0: m1.Lock(); michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: Sanity3() michael@0: { michael@0: const char* const tokens[] = { michael@0: "###!!! ERROR: Potential deadlock detected", michael@0: "=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1", michael@0: "--- Next dependency:\n--- Mutex : dd.sanity3.m2", michael@0: "--- Next dependency:\n--- Mutex : dd.sanity3.m3", michael@0: "--- Next dependency:\n--- Mutex : dd.sanity3.m4", michael@0: "=== Cycle completed at\n--- Mutex : dd.sanity3.m1", michael@0: "###!!! ASSERTION: Potential deadlock detected", michael@0: 0 michael@0: }; michael@0: if (CheckForDeadlock("Sanity3", tokens)) { michael@0: PASS(); michael@0: } else { michael@0: FAIL("deadlock not detected"); michael@0: } michael@0: } michael@0: michael@0: michael@0: int michael@0: Sanity4_Child() michael@0: { michael@0: mozilla::ReentrantMonitor m1("dd.sanity4.m1"); michael@0: TestMutex m2("dd.sanity4.m2"); michael@0: m1.Enter(); michael@0: m2.Lock(); michael@0: m1.Enter(); michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: Sanity4() michael@0: { michael@0: const char* const tokens[] = { michael@0: "Re-entering ReentrantMonitor after acquiring other resources", michael@0: "###!!! ERROR: Potential deadlock detected", michael@0: "=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1", michael@0: "--- Next dependency:\n--- Mutex : dd.sanity4.m2", michael@0: "=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1", michael@0: "###!!! ASSERTION: Potential deadlock detected", michael@0: 0 michael@0: }; michael@0: if (CheckForDeadlock("Sanity4", tokens)) { michael@0: PASS(); michael@0: } else { michael@0: FAIL("deadlock not detected"); michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Multithreaded tests michael@0: michael@0: TestMutex* ttM1; michael@0: TestMutex* ttM2; michael@0: michael@0: static void michael@0: TwoThreads_thread(void* arg) michael@0: { michael@0: int32_t m1First = NS_PTR_TO_INT32(arg); michael@0: if (m1First) { michael@0: ttM1->Lock(); michael@0: ttM2->Lock(); michael@0: ttM2->Unlock(); michael@0: ttM1->Unlock(); michael@0: } michael@0: else { michael@0: ttM2->Lock(); michael@0: ttM1->Lock(); michael@0: ttM1->Unlock(); michael@0: ttM2->Unlock(); michael@0: } michael@0: } michael@0: michael@0: int michael@0: TwoThreads_Child() michael@0: { michael@0: ttM1 = new TestMutex("dd.twothreads.m1"); michael@0: ttM2 = new TestMutex("dd.twothreads.m2"); michael@0: if (!ttM1 || !ttM2) michael@0: NS_RUNTIMEABORT("couldn't allocate mutexes"); michael@0: michael@0: PRThread* t1 = spawn(TwoThreads_thread, (void*) 0); michael@0: PR_JoinThread(t1); michael@0: michael@0: PRThread* t2 = spawn(TwoThreads_thread, (void*) 1); michael@0: PR_JoinThread(t2); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: TwoThreads() michael@0: { michael@0: const char* const tokens[] = { michael@0: "###!!! ERROR: Potential deadlock detected", michael@0: "=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2", michael@0: "--- Next dependency:\n--- Mutex : dd.twothreads.m1", michael@0: "=== Cycle completed at\n--- Mutex : dd.twothreads.m2", michael@0: "###!!! ASSERTION: Potential deadlock detected", michael@0: 0 michael@0: }; michael@0: michael@0: if (CheckForDeadlock("TwoThreads", tokens)) { michael@0: PASS(); michael@0: } else { michael@0: FAIL("deadlock not detected"); michael@0: } michael@0: } michael@0: michael@0: michael@0: TestMutex* cndMs[4]; michael@0: const uint32_t K = 100000; michael@0: michael@0: static void michael@0: ContentionNoDeadlock_thread(void* arg) michael@0: { michael@0: int32_t starti = NS_PTR_TO_INT32(arg); michael@0: michael@0: for (uint32_t k = 0; k < K; ++k) { michael@0: for (int32_t i = starti; i < (int32_t) ArrayLength(cndMs); ++i) michael@0: cndMs[i]->Lock(); michael@0: // comment out the next two lines for deadlocking fun! michael@0: for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i) michael@0: cndMs[i]->Unlock(); michael@0: michael@0: starti = (starti + 1) % 3; michael@0: } michael@0: } michael@0: michael@0: int michael@0: ContentionNoDeadlock_Child() michael@0: { michael@0: PRThread* threads[3]; michael@0: michael@0: for (uint32_t i = 0; i < ArrayLength(cndMs); ++i) michael@0: cndMs[i] = new TestMutex("dd.cnd.ms"); michael@0: michael@0: for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i) michael@0: threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i)); michael@0: michael@0: for (uint32_t i = 0; i < ArrayLength(threads); ++i) michael@0: PR_JoinThread(threads[i]); michael@0: michael@0: for (uint32_t i = 0; i < ArrayLength(cndMs); ++i) michael@0: delete cndMs[i]; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: ContentionNoDeadlock() michael@0: { michael@0: const char * func = __func__; michael@0: Subprocess proc(func); michael@0: proc.RunToCompletion(60000); michael@0: if (0 != proc.mExitCode) { michael@0: printf("(expected 0 == return code, got %d)\n", proc.mExitCode); michael@0: puts("(output)\n----------------------------------\n"); michael@0: puts(proc.mStdout.get()); michael@0: puts("----------------------------------\n"); michael@0: puts("(error output)\n----------------------------------\n"); michael@0: puts(proc.mStderr.get()); michael@0: puts("----------------------------------\n"); michael@0: michael@0: FAIL("deadlock"); michael@0: } michael@0: PASS(); michael@0: } michael@0: michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: int michael@0: main(int argc, char** argv) michael@0: { michael@0: if (1 < argc) { michael@0: // XXX can we run w/o scoped XPCOM? michael@0: const char* test = argv[1]; michael@0: ScopedXPCOM xpcom(test); michael@0: if (xpcom.failed()) michael@0: return 1; michael@0: michael@0: // running in a spawned process. call the specificed child function. michael@0: if (!strcmp("Sanity", test)) michael@0: return Sanity_Child(); michael@0: if (!strcmp("Sanity2", test)) michael@0: return Sanity2_Child(); michael@0: if (!strcmp("Sanity3", test)) michael@0: return Sanity3_Child(); michael@0: if (!strcmp("Sanity4", test)) michael@0: return Sanity4_Child(); michael@0: michael@0: if (!strcmp("TwoThreads", test)) michael@0: return TwoThreads_Child(); michael@0: if (!strcmp("ContentionNoDeadlock", test)) michael@0: return ContentionNoDeadlock_Child(); michael@0: michael@0: fail("%s | %s - unknown child test", __FILE__, __FUNCTION__); michael@0: return 1; michael@0: } michael@0: michael@0: ScopedXPCOM xpcom("Storage deadlock detector correctness (" __FILE__ ")"); michael@0: if (xpcom.failed()) michael@0: return 1; michael@0: michael@0: // in the first invocation of this process. we will be the "driver". michael@0: int rv = 0; michael@0: michael@0: sPathToThisBinary = argv[0]; michael@0: michael@0: if (NS_FAILED(Sanity())) michael@0: rv = 1; michael@0: if (NS_FAILED(Sanity2())) michael@0: rv = 1; michael@0: if (NS_FAILED(Sanity3())) michael@0: rv = 1; michael@0: if (NS_FAILED(Sanity4())) michael@0: rv = 1; michael@0: michael@0: if (NS_FAILED(TwoThreads())) michael@0: rv = 1; michael@0: if (NS_FAILED(ContentionNoDeadlock())) michael@0: rv = 1; michael@0: michael@0: return rv; michael@0: }