1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-27 21:38:34 +00:00
Files
UXP/xpcom/tests/gtest/TestDeadlockDetector.cpp
T
Moonchild f1759b33ff Issue #1656 - Remove more vim control lines.
Vim control lines were re-introduced or not entirely cleaned up.
This nukes them again.
Removing from accessible, [root], tools, uriloader, widget, xpcom and
xpfe.
Resolves #1656
2023-05-05 23:00:05 +08:00

310 lines
7.7 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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"
#include "gtest/gtest.h"
using namespace mozilla;
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 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()
{
/*** STUB ***/
}
//-----------------------------------------------------------------------------
// Single-threaded sanity tests
// Stupidest possible deadlock.
int
Sanity_Child()
{
DisableCrashReporter();
mozilla::Mutex m1("dd.sanity.m1");
m1.Lock();
m1.Lock();
return 0; // not reached
}
TEST_F(DeadlockDetectorTest, 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();
mozilla::Mutex m1("dd.sanity2.m1");
mozilla::Mutex m2("dd.sanity2.m2");
m1.Lock();
m2.Lock();
m1.Lock();
return 0; // not reached
}
TEST_F(DeadlockDetectorTest, 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();
mozilla::Mutex m1("dd.sanity3.m1");
mozilla::Mutex m2("dd.sanity3.m2");
mozilla::Mutex m3("dd.sanity3.m3");
mozilla::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(DeadlockDetectorTest, 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");
mozilla::Mutex m2("dd.sanity4.m2");
m1.Enter();
m2.Lock();
m1.Enter();
return 0;
}
TEST_F(DeadlockDetectorTest, 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<mozilla::Mutex*>& locks;
/**
* Integer argument used to identify each thread.
*/
int id;
};
static void
TwoThreads_thread(void* arg)
{
ThreadState* state = static_cast<ThreadState*>(arg);
mozilla::Mutex* ttM1 = state->locks[0];
mozilla::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<mozilla::Mutex*> locks = {
new mozilla::Mutex("dd.twothreads.m1"),
new mozilla::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(DeadlockDetectorTest, 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<mozilla::Mutex*> locks;
ThreadState states[] = {
{ locks, 0 },
{ locks, 1 },
{ locks, 2 }
};
for (uint32_t i = 0; i < kMutexCount; ++i)
locks.AppendElement(new mozilla::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(DeadlockDetectorTest, ContentionNoDeadlock)
{
// Just check that this test runs to completion.
ASSERT_EQ(ContentionNoDeadlock_Child(), 0);
}