зеркало из https://github.com/mozilla/gecko-dev.git
334 строки
8.3 KiB
C++
334 строки
8.3 KiB
C++
/* -*- 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/. */
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
|
|
#include "prthread.h"
|
|
|
|
#include "nsTArray.h"
|
|
#include "nsMemory.h"
|
|
|
|
#include "mozilla/CondVar.h"
|
|
#include "mozilla/ReentrantMonitor.h"
|
|
#include "mozilla/Mutex.h"
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
#include "nsCOMPtr.h"
|
|
#include "nsICrashReporter.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#endif
|
|
|
|
#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);
|
|
}
|
|
|
|
// This global variable is defined in toolkit/xre/nsSigHandlers.cpp.
|
|
extern unsigned int _gdb_sleep_duration;
|
|
|
|
/**
|
|
* 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 {
|
|
mOldSleepDuration = ::_gdb_sleep_duration;
|
|
::_gdb_sleep_duration = 0;
|
|
}
|
|
|
|
void TearDown() final {
|
|
::_gdb_sleep_duration = mOldSleepDuration;
|
|
}
|
|
|
|
private:
|
|
unsigned int mOldSleepDuration;
|
|
};
|
|
|
|
void DisableCrashReporter()
|
|
{
|
|
#ifdef MOZ_CRASHREPORTER
|
|
nsCOMPtr<nsICrashReporter> crashreporter =
|
|
do_GetService("@mozilla.org/toolkit/crash-reporter;1");
|
|
if (crashreporter) {
|
|
crashreporter->SetEnabled(false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Single-threaded sanity tests
|
|
|
|
// Stupidest possible deadlock.
|
|
int
|
|
Sanity_Child()
|
|
{
|
|
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.
|
|
int
|
|
Sanity2_Child()
|
|
{
|
|
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);
|
|
}
|
|
|
|
int
|
|
Sanity3_Child()
|
|
{
|
|
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);
|
|
}
|
|
|
|
int
|
|
Sanity4_Child()
|
|
{
|
|
DisableCrashReporter();
|
|
|
|
mozilla::ReentrantMonitor m1("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);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
};
|
|
|
|
static void
|
|
TwoThreads_thread(void* arg)
|
|
{
|
|
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()
|
|
{
|
|
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);
|
|
}
|
|
|
|
static void
|
|
ContentionNoDeadlock_thread(void* arg)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
int
|
|
ContentionNoDeadlock_Child()
|
|
{
|
|
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);
|
|
}
|