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