зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1347963 - part 1 - introduce mozilla::RecursiveMutex; r=erahm
Having a proper recursively-acquirable mutex type makes intent clearer, and RecursiveMutex also happens to be somewhat faster than ReentrantMonitor.
This commit is contained in:
Родитель
fd24d774c3
Коммит
a6c96367a7
|
@ -10,6 +10,7 @@
|
|||
// duplication.
|
||||
|
||||
#include "mozilla/CondVar.h"
|
||||
#include "mozilla/RecursiveMutex.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "SQLiteMutex.h"
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "nsMemory.h"
|
||||
|
||||
#include "mozilla/CondVar.h"
|
||||
#include "mozilla/RecursiveMutex.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStringStream.h"
|
||||
|
|
|
@ -32,6 +32,7 @@ UNIFIED_SOURCES += [
|
|||
'TestPLDHash.cpp',
|
||||
'TestPriorityQueue.cpp',
|
||||
'TestRacingServiceManager.cpp',
|
||||
'TestRecursiveMutex.cpp',
|
||||
'TestRWLock.cpp',
|
||||
'TestSlicedInputStream.cpp',
|
||||
'TestSnappyStreams.cpp',
|
||||
|
|
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -256,5 +256,4 @@ private:
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
#endif // ifndef mozilla_ReentrantMonitor_h
|
||||
|
|
|
@ -49,6 +49,7 @@ EXPORTS.mozilla += [
|
|||
'Monitor.h',
|
||||
'MozPromise.h',
|
||||
'Mutex.h',
|
||||
'RecursiveMutex.h',
|
||||
'ReentrantMonitor.h',
|
||||
'RWLock.h',
|
||||
'SchedulerGroup.h',
|
||||
|
@ -82,6 +83,7 @@ UNIFIED_SOURCES += [
|
|||
'nsThreadPool.cpp',
|
||||
'nsThreadUtils.cpp',
|
||||
'nsTimerImpl.cpp',
|
||||
'RecursiveMutex.cpp',
|
||||
'RWLock.cpp',
|
||||
'SchedulerGroup.cpp',
|
||||
'SharedThreadPool.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче