summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest/TestDeadlockDetector.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetector.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp
new file mode 100644
index 0000000000..c02ba13da2
--- /dev/null
+++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=2 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "prthread.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#include "mozilla/CondVar.h"
+#include "mozilla/RecursiveMutex.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Mutex.h"
+
+#include "mozilla/gtest/MozHelpers.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+// The code in this file is also used by
+// storage/test/gtest/test_deadlock_detector.cpp. The following two macros are
+// used to provide the necessary differentiation between this file and that
+// file.
+#ifndef MUTEX
+# define MUTEX mozilla::Mutex
+#endif
+#ifndef TESTNAME
+# define TESTNAME(name) XPCOM##name
+#endif
+
+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);
+}
+
+/**
+ * Simple test fixture that makes sure the gdb sleep setup in the
+ * ah crap handler is bypassed during the death tests.
+ */
+class TESTNAME(DeadlockDetectorTest) : public ::testing::Test {
+ protected:
+ void SetUp() final { SAVE_GDB_SLEEP_GLOBAL(mOldSleepDuration); }
+
+ void TearDown() final { RESTORE_GDB_SLEEP_GLOBAL(mOldSleepDuration); }
+
+ private:
+#if defined(HAS_GDB_SLEEP_DURATION)
+ unsigned int mOldSleepDuration;
+#endif // defined(HAS_GDB_SLEEP_DURATION)
+};
+
+//-----------------------------------------------------------------------------
+// Single-threaded sanity tests
+
+// Stupidest possible deadlock.
+static int Sanity_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity.m1");
+ m1.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(SanityDeathTest)) {
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*"
+ "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity_Child(), regex);
+}
+
+// Slightly less stupid deadlock.
+static int Sanity2_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity2.m1");
+ MUTEX m2("dd.sanity2.m2");
+ m1.Lock();
+ m2.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity2DeathTest)) {
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*"
+ "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity2_Child(), regex);
+}
+
+#if 0
+// Temporarily disabled, see bug 1370644.
+int
+Sanity3_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity3.m1");
+ MUTEX m2("dd.sanity3.m2");
+ MUTEX m3("dd.sanity3.m3");
+ MUTEX 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;
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity3DeathTest))
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity3_Child(), regex);
+}
+#endif
+
+static int Sanity4_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ mozilla::ReentrantMonitor m1 MOZ_UNANNOTATED("dd.sanity4.m1");
+ MUTEX m2("dd.sanity4.m2");
+ m1.Enter();
+ m2.Lock();
+ m1.Enter();
+ return 0;
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity4DeathTest)) {
+ const char* const regex =
+ "Re-entering ReentrantMonitor after acquiring other resources.*"
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- ReentrantMonitor : "
+ "dd.sanity4.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
+ "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+ ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex);
+}
+
+static int Sanity5_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ mozilla::RecursiveMutex m1 MOZ_UNANNOTATED("dd.sanity4.m1");
+ MUTEX m2("dd.sanity4.m2");
+ m1.Lock();
+ m2.Lock();
+ m1.Lock();
+ return 0;
+}
+
+#if !defined(DISABLE_STORAGE_SANITY5_DEATH_TEST)
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity5DeathTest)) {
+ const char* const regex =
+ "Re-entering RecursiveMutex after acquiring other resources.*"
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- RecursiveMutex : dd.sanity4.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
+ "=== Cycle completed at.*--- RecursiveMutex : dd.sanity4.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+ ASSERT_DEATH_IF_SUPPORTED(Sanity5_Child(), regex);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Multithreaded tests
+
+/**
+ * Helper for passing state to threads in the multithread tests.
+ */
+struct ThreadState {
+ /**
+ * Locks to use during the test. This is just a reference and is owned by
+ * the main test thread.
+ */
+ const nsTArray<MUTEX*>& locks;
+
+ /**
+ * Integer argument used to identify each thread.
+ */
+ int id;
+};
+
+#if 0
+// Temporarily disabled, see bug 1370644.
+static void
+TwoThreads_thread(void* arg) MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ ThreadState* state = static_cast<ThreadState*>(arg);
+
+ MUTEX* ttM1 = state->locks[0];
+ MUTEX* ttM2 = state->locks[1];
+
+ if (state->id) {
+ ttM1->Lock();
+ ttM2->Lock();
+ ttM2->Unlock();
+ ttM1->Unlock();
+ }
+ else {
+ ttM2->Lock();
+ ttM1->Lock();
+ ttM1->Unlock();
+ ttM2->Unlock();
+ }
+}
+
+int
+TwoThreads_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ mozilla::gtest::DisableCrashReporter();
+
+ nsTArray<MUTEX*> locks = {
+ new MUTEX("dd.twothreads.m1"),
+ new MUTEX("dd.twothreads.m2")
+ };
+
+ ThreadState state_1 {locks, 0};
+ PRThread* t1 = spawn(TwoThreads_thread, &state_1);
+ PR_JoinThread(t1);
+
+ ThreadState state_2 {locks, 1};
+ PRThread* t2 = spawn(TwoThreads_thread, &state_2);
+ PR_JoinThread(t2);
+
+ for (auto& lock : locks) {
+ delete lock;
+ }
+
+ return 0;
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(TwoThreadsDeathTest))
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*"
+ "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*"
+ "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(TwoThreads_Child(), regex);
+}
+#endif
+
+static void ContentionNoDeadlock_thread(void* arg)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ const uint32_t K = 100000;
+
+ ThreadState* state = static_cast<ThreadState*>(arg);
+ int32_t starti = static_cast<int32_t>(state->id);
+ auto& cndMs = state->locks;
+
+ for (uint32_t k = 0; k < K; ++k) {
+ for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i) cndMs[i]->Lock();
+ // comment out the next two lines for deadlocking fun!
+ for (int32_t i = cndMs.Length() - 1; i >= starti; --i) cndMs[i]->Unlock();
+
+ starti = (starti + 1) % 3;
+ }
+}
+
+static int ContentionNoDeadlock_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ const size_t kMutexCount = 4;
+
+ PRThread* threads[3];
+ nsTArray<MUTEX*> locks;
+ ThreadState states[] = {{locks, 0}, {locks, 1}, {locks, 2}};
+
+ for (uint32_t i = 0; i < kMutexCount; ++i)
+ locks.AppendElement(new MUTEX("dd.cnd.ms"));
+
+ for (int32_t i = 0; i < (int32_t)ArrayLength(threads); ++i)
+ threads[i] = spawn(ContentionNoDeadlock_thread, states + i);
+
+ for (uint32_t i = 0; i < ArrayLength(threads); ++i) PR_JoinThread(threads[i]);
+
+ for (uint32_t i = 0; i < locks.Length(); ++i) delete locks[i];
+
+ return 0;
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(ContentionNoDeadlock)) {
+ // Just check that this test runs to completion.
+ ASSERT_EQ(ContentionNoDeadlock_Child(), 0);
+}