Skip to content

Commit

Permalink
Bug 1347963 - part 1 - introduce mozilla::RecursiveMutex; r=erahm
Browse files Browse the repository at this point in the history
Having a proper recursively-acquirable mutex type makes intent clearer,
and RecursiveMutex also happens to be somewhat faster than
ReentrantMonitor.
  • Loading branch information
froydnj committed Mar 14, 2017
1 parent cf9d07e commit 1db8042
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 3 deletions.
1 change: 1 addition & 0 deletions storage/test/gtest/test_deadlock_detector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// duplication.

#include "mozilla/CondVar.h"
#include "mozilla/RecursiveMutex.h"
#include "mozilla/ReentrantMonitor.h"
#include "SQLiteMutex.h"

Expand Down
26 changes: 26 additions & 0 deletions xpcom/tests/gtest/TestDeadlockDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "nsMemory.h"

#include "mozilla/CondVar.h"
#include "mozilla/RecursiveMutex.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Mutex.h"

Expand Down Expand Up @@ -202,6 +203,31 @@ TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity4DeathTest))
ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex);
}

int
Sanity5_Child()
{
DisableCrashReporter();

mozilla::RecursiveMutex m1("dd.sanity4.m1");
MUTEX m2("dd.sanity4.m2");
m1.Lock();
m2.Lock();
m1.Lock();
return 0;
}

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);
}

//-----------------------------------------------------------------------------
// Multithreaded tests

Expand Down
25 changes: 25 additions & 0 deletions xpcom/tests/gtest/TestRecursiveMutex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsThreadUtils.h"
#include "mozilla/RecursiveMutex.h"
#include "gtest/gtest.h"

using mozilla::RecursiveMutex;
using mozilla::RecursiveMutexAutoLock;

// Basic test to make sure the underlying implementation of RecursiveMutex is,
// well, actually recursively acquirable.

TEST(RecursiveMutex, SmokeTest)
{
RecursiveMutex mutex("testing mutex");

RecursiveMutexAutoLock lock1(mutex);
RecursiveMutexAutoLock lock2(mutex);

//...and done.
}
1 change: 1 addition & 0 deletions xpcom/tests/gtest/TestSlicedInputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "nsCOMPtr.h"
#include "nsIInputStream.h"
#include "nsIPipe.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringStream.h"
Expand Down
1 change: 1 addition & 0 deletions xpcom/tests/gtest/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ UNIFIED_SOURCES += [
'TestPLDHash.cpp',
'TestPriorityQueue.cpp',
'TestRacingServiceManager.cpp',
'TestRecursiveMutex.cpp',
'TestRWLock.cpp',
'TestSlicedInputStream.cpp',
'TestSnappyStreams.cpp',
Expand Down
63 changes: 62 additions & 1 deletion xpcom/threads/BlockingResourceBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "mozilla/CondVar.h"
#include "mozilla/DeadlockDetector.h"
#include "mozilla/RecursiveMutex.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/RWLock.h"
Expand All @@ -38,7 +39,7 @@ namespace mozilla {
// static members
const char* const BlockingResourceBase::kResourceTypeName[] = {
// needs to be kept in sync with BlockingResourceType
"Mutex", "ReentrantMonitor", "CondVar"
"Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"
};

#ifdef DEBUG
Expand Down Expand Up @@ -527,6 +528,66 @@ ReentrantMonitor::Wait(PRIntervalTime aInterval)
}


//
// Debug implementation of RecursiveMutex
void
RecursiveMutex::Lock()
{
BlockingResourceBase* chainFront = ResourceChainFront();

// the code below implements mutex reentrancy semantics

if (this == chainFront) {
// immediately re-entered the mutex: acceptable
LockInternal();
++mEntryCount;
return;
}

// this is sort of a hack around not recording the thread that
// owns this monitor
if (chainFront) {
for (BlockingResourceBase* br = ResourceChainPrev(chainFront);
br;
br = ResourceChainPrev(br)) {
if (br == this) {
NS_WARNING(
"Re-entering RecursiveMutex after acquiring other resources.");

// show the caller why this is potentially bad
CheckAcquire();

LockInternal();
++mEntryCount;
return;
}
}
}

CheckAcquire();
LockInternal();
NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
Acquire(); // protected by us
mOwningThread = PR_GetCurrentThread();
mEntryCount = 1;
}

void
RecursiveMutex::Unlock()
{
if (--mEntryCount == 0) {
Release(); // protected by us
mOwningThread = nullptr;
}
UnlockInternal();
}

void
RecursiveMutex::AssertCurrentThreadIn()
{
MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
}

//
// Debug implementation of CondVar
nsresult
Expand Down
2 changes: 1 addition & 1 deletion xpcom/threads/BlockingResourceBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class BlockingResourceBase
{
public:
// Needs to be kept in sync with kResourceTypeNames.
enum BlockingResourceType { eMutex, eReentrantMonitor, eCondVar };
enum BlockingResourceType { eMutex, eReentrantMonitor, eCondVar, eRecursiveMutex };

/**
* kResourceTypeName
Expand Down
89 changes: 89 additions & 0 deletions xpcom/threads/RecursiveMutex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/RecursiveMutex.h"

#ifdef XP_WIN
#include <windows.h>

#define NativeHandle(m) (reinterpret_cast<CRITICAL_SECTION*>(&m))
#endif

namespace mozilla {

RecursiveMutex::RecursiveMutex(const char* aName MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: BlockingResourceBase(aName, eRecursiveMutex)
#ifdef DEBUG
, mOwningThread(nullptr)
, mEntryCount(0)
#endif
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
#ifdef XP_WIN
// This number was adapted from NSPR.
static const DWORD sLockSpinCount = 100;

#if defined(RELEASE_OR_BETA)
// Vista and later automatically allocate and subsequently leak a debug info
// object for each critical section that we allocate unless we tell the
// system not to do that.
DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO;
#else
DWORD flags = 0;
#endif
BOOL r = InitializeCriticalSectionEx(NativeHandle(mMutex),
sLockSpinCount, flags);
MOZ_RELEASE_ASSERT(r);
#else
pthread_mutexattr_t attr;

MOZ_RELEASE_ASSERT(pthread_mutexattr_init(&attr) == 0,
"pthread_mutexattr_init failed");

MOZ_RELEASE_ASSERT(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0,
"pthread_mutexattr_settype failed");

MOZ_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attr) == 0,
"pthread_mutex_init failed");

MOZ_RELEASE_ASSERT(pthread_mutexattr_destroy(&attr) == 0,
"pthread_mutexattr_destroy failed");
#endif
}

RecursiveMutex::~RecursiveMutex()
{
#ifdef XP_WIN
DeleteCriticalSection(NativeHandle(mMutex));
#else
MOZ_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0,
"pthread_mutex_destroy failed");
#endif
}

void
RecursiveMutex::LockInternal()
{
#ifdef XP_WIN
EnterCriticalSection(NativeHandle(mMutex));
#else
MOZ_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0,
"pthread_mutex_lock failed");
#endif
}

void
RecursiveMutex::UnlockInternal()
{
#ifdef XP_WIN
LeaveCriticalSection(NativeHandle(mMutex));
#else
MOZ_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0,
"pthread_mutex_unlock failed");
#endif
}

} // namespace mozilla
124 changes: 124 additions & 0 deletions xpcom/threads/RecursiveMutex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

// A lock that can be acquired multiple times on the same thread.

#ifndef mozilla_RecursiveMutex_h
#define mozilla_RecursiveMutex_h

#include "mozilla/BlockingResourceBase.h"
#include "mozilla/GuardObjects.h"

#ifndef XP_WIN
#include <pthread.h>
#endif

namespace mozilla {

class RecursiveMutex : public BlockingResourceBase
{
public:
explicit RecursiveMutex(const char* aName MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~RecursiveMutex();

#ifdef DEBUG
void Lock();
void Unlock();
#else
void Lock() { LockInternal(); }
void Unlock() { UnlockInternal(); }
#endif

void AssertCurrentThreadIn()
#ifdef DEBUG
;
#else
{
}
#endif

private:
RecursiveMutex() = delete;
RecursiveMutex(const RecursiveMutex&) = delete;
RecursiveMutex& operator=(const RecursiveMutex&) = delete;

void LockInternal();
void UnlockInternal();

#ifdef DEBUG
PRThread* mOwningThread;
size_t mEntryCount;
#endif

#if !defined(XP_WIN)
pthread_mutex_t mMutex;
#else
// We eschew including windows.h and using CRITICAL_SECTION here so that files
// including us don't also pull in windows.h. Just use a type that's big
// enough for CRITICAL_SECTION, and we'll fix it up later.
void* mMutex[6];
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

class MOZ_RAII RecursiveMutexAutoLock
{
public:
explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mRecursiveMutex(&aRecursiveMutex)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
NS_ASSERTION(mRecursiveMutex, "null mutex");
mRecursiveMutex->Lock();
}

~RecursiveMutexAutoLock(void)
{
mRecursiveMutex->Unlock();
}

private:
RecursiveMutexAutoLock() = delete;
RecursiveMutexAutoLock(const RecursiveMutexAutoLock&) = delete;
RecursiveMutexAutoLock& operator=(const RecursiveMutexAutoLock&) = delete;
static void* operator new(size_t) CPP_THROW_NEW;

mozilla::RecursiveMutex* mRecursiveMutex;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

class MOZ_RAII RecursiveMutexAutoUnlock
{
public:
explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mRecursiveMutex(&aRecursiveMutex)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
NS_ASSERTION(mRecursiveMutex, "null mutex");
mRecursiveMutex->Unlock();
}

~RecursiveMutexAutoUnlock(void)
{
mRecursiveMutex->Lock();
}

private:
RecursiveMutexAutoUnlock() = delete;
RecursiveMutexAutoUnlock(const RecursiveMutexAutoUnlock&) = delete;
RecursiveMutexAutoUnlock& operator=(const RecursiveMutexAutoUnlock&) = delete;
static void* operator new(size_t) CPP_THROW_NEW;

mozilla::RecursiveMutex* mRecursiveMutex;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

} // namespace mozilla

#endif // mozilla_RecursiveMutex_h
Loading

0 comments on commit 1db8042

Please sign in to comment.