384 linhas
9.4 KiB
C++
384 linhas
9.4 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#ifndef __MUTEX_H__
|
|
#define __MUTEX_H__
|
|
|
|
#include <util/assertions.h>
|
|
#include <util/util.h>
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
#include <tbb/concurrent_hash_map.h>
|
|
|
|
#include "util/rank.h"
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <bool enableAssertions>
|
|
class BaseMutex {
|
|
private:
|
|
#ifdef DEBUG
|
|
static const int kMagic = 0xba5eba11;
|
|
int m_magic;
|
|
Rank m_rank;
|
|
// m_owner/m_hasOwner for keeping track of lock ownership, useful for debugging
|
|
pthread_t m_owner;
|
|
unsigned int m_acquires;
|
|
bool m_recursive;
|
|
bool m_hasOwner;
|
|
#endif
|
|
inline void recordAcquisition() {
|
|
#ifdef DEBUG
|
|
if (enableAssertions) {
|
|
assert(!m_hasOwner ||
|
|
pthread_equal(m_owner, pthread_self()));
|
|
assert(m_acquires == 0 ||
|
|
pthread_equal(m_owner, pthread_self()));
|
|
pushRank(m_rank);
|
|
m_hasOwner = true;
|
|
m_owner = pthread_self();
|
|
m_acquires++;
|
|
assert(m_recursive || m_acquires == 1);
|
|
}
|
|
#endif
|
|
}
|
|
inline void invalidateOwner() {
|
|
#ifdef DEBUG
|
|
if (enableAssertions) {
|
|
m_hasOwner = false;
|
|
m_acquires = 0;
|
|
}
|
|
#endif
|
|
}
|
|
inline void recordRelease() {
|
|
#ifdef DEBUG
|
|
if (enableAssertions) {
|
|
popRank(m_rank);
|
|
assertOwnedBySelf();
|
|
assert(m_acquires > 0);
|
|
if (--m_acquires == 0) {
|
|
m_hasOwner = false;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
public:
|
|
inline void assertNotOwned() const {
|
|
#ifdef DEBUG
|
|
if (enableAssertions) {
|
|
assert(!m_hasOwner);
|
|
assert(m_acquires == 0);
|
|
}
|
|
#endif
|
|
}
|
|
inline void assertOwnedBySelf() const {
|
|
#ifdef DEBUG
|
|
if (enableAssertions) {
|
|
assert(m_hasOwner);
|
|
assert(pthread_equal(m_owner, pthread_self()));
|
|
assert(m_acquires > 0);
|
|
}
|
|
#endif
|
|
}
|
|
public:
|
|
BaseMutex(bool recursive = true, Rank r = RankUnranked) {
|
|
pthread_mutexattr_init(&m_mutexattr);
|
|
if (recursive) {
|
|
pthread_mutexattr_settype(&m_mutexattr, PTHREAD_MUTEX_RECURSIVE);
|
|
} else {
|
|
#if defined(__APPLE__)
|
|
pthread_mutexattr_settype(&m_mutexattr, PTHREAD_MUTEX_DEFAULT);
|
|
#else
|
|
pthread_mutexattr_settype(&m_mutexattr, PTHREAD_MUTEX_ADAPTIVE_NP);
|
|
#endif
|
|
}
|
|
pthread_mutex_init(&m_mutex, &m_mutexattr);
|
|
#ifdef DEBUG
|
|
m_rank = r;
|
|
m_magic = kMagic;
|
|
invalidateOwner();
|
|
m_recursive = recursive;
|
|
#endif
|
|
}
|
|
~BaseMutex() {
|
|
#ifdef DEBUG
|
|
assert(m_magic == kMagic);
|
|
#endif
|
|
assertNotOwned();
|
|
pthread_mutex_destroy(&m_mutex);
|
|
pthread_mutexattr_destroy(&m_mutexattr);
|
|
#ifdef DEBUG
|
|
m_magic = ~m_magic;
|
|
#endif
|
|
}
|
|
|
|
bool tryLock() {
|
|
#ifdef DEBUG
|
|
assert(m_magic == kMagic);
|
|
#endif
|
|
bool success = !pthread_mutex_trylock(&m_mutex);
|
|
if (success) {
|
|
recordAcquisition();
|
|
assertOwnedBySelf();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool tryLockWait(long long ns) {
|
|
#ifdef DEBUG
|
|
assert(m_magic == kMagic);
|
|
#endif
|
|
struct timespec delta;
|
|
delta.tv_sec = 0;
|
|
delta.tv_nsec = ns;
|
|
bool success = !pthread_mutex_timedlock(&m_mutex, &delta);
|
|
if (success) {
|
|
recordAcquisition();
|
|
assertOwnedBySelf();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void lock() {
|
|
#ifdef DEBUG
|
|
assert(m_magic == kMagic);
|
|
checkRank(m_rank);
|
|
#endif
|
|
UNUSED int ret = pthread_mutex_lock(&m_mutex);
|
|
assert(ret == 0);
|
|
|
|
recordAcquisition();
|
|
assertOwnedBySelf();
|
|
}
|
|
|
|
void unlock() {
|
|
#ifdef DEBUG
|
|
assert(m_magic == kMagic);
|
|
#endif
|
|
recordRelease();
|
|
UNUSED int ret = pthread_mutex_unlock(&m_mutex);
|
|
assert(ret == 0);
|
|
}
|
|
|
|
private:
|
|
BaseMutex(const BaseMutex &); // suppress
|
|
BaseMutex &operator=(const BaseMutex &); // suppress
|
|
|
|
protected:
|
|
pthread_mutexattr_t m_mutexattr;
|
|
pthread_mutex_t m_mutex;
|
|
};
|
|
|
|
/**
|
|
* Standard recursive mutex, which can be used for condition variables.
|
|
* This mutex does not support enabling assertions
|
|
*/
|
|
class Mutex : public BaseMutex<false> {
|
|
public:
|
|
Mutex(bool recursive = true, Rank rank = RankUnranked) :
|
|
BaseMutex<false>(recursive, rank) {}
|
|
pthread_mutex_t &getRaw() { return m_mutex; }
|
|
};
|
|
|
|
/**
|
|
* Simple recursive mutex, which does not expose the underlying raw
|
|
* pthread_mutex_t. This allows this mutex to support enabling assertions
|
|
*/
|
|
class SimpleMutex : public BaseMutex<true> {
|
|
public:
|
|
SimpleMutex(bool recursive = true, Rank rank = RankUnranked) :
|
|
BaseMutex<true>(recursive, rank) {}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class SpinLock {
|
|
#ifdef DEBUG
|
|
Rank m_rank;
|
|
#endif
|
|
public:
|
|
SpinLock(Rank rank = RankUnranked)
|
|
#ifdef DEBUG
|
|
: m_rank(rank)
|
|
#endif
|
|
{
|
|
pthread_spin_init(&m_spinlock, 0);
|
|
}
|
|
~SpinLock() {
|
|
pthread_spin_destroy(&m_spinlock);
|
|
}
|
|
|
|
void lock() {
|
|
pushRank(m_rank);
|
|
pthread_spin_lock(&m_spinlock);
|
|
}
|
|
void unlock() {
|
|
popRank(m_rank);
|
|
pthread_spin_unlock(&m_spinlock);
|
|
}
|
|
|
|
pthread_spinlock_t &getRaw() { return m_spinlock;}
|
|
|
|
private:
|
|
SpinLock(const SpinLock &); // suppress
|
|
SpinLock &operator=(const SpinLock &); // suppress
|
|
|
|
pthread_spinlock_t m_spinlock;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Read-write lock wrapper.
|
|
*/
|
|
class ReadWriteMutex {
|
|
#ifdef DEBUG
|
|
/*
|
|
* We have a track record of self-deadlocking on these, and our pthread
|
|
* implementation tends to do crazy things when a rwlock is double-wlocked,
|
|
* so check and assert early in debug builds.
|
|
*/
|
|
static const pthread_t InvalidThread = (pthread_t)0;
|
|
pthread_t m_writeOwner;
|
|
Rank m_rank;
|
|
#endif
|
|
|
|
void invalidateWriteOwner() {
|
|
#ifdef DEBUG
|
|
m_writeOwner = InvalidThread;
|
|
#endif
|
|
}
|
|
|
|
void recordWriteAcquire() {
|
|
#ifdef DEBUG
|
|
assert(m_writeOwner == InvalidThread);
|
|
m_writeOwner = pthread_self();
|
|
#endif
|
|
}
|
|
|
|
void assertNotWriteOwner() {
|
|
#ifdef DEBUG
|
|
assert(m_writeOwner != pthread_self());
|
|
#endif
|
|
}
|
|
|
|
void assertNotWriteOwned() {
|
|
#ifdef DEBUG
|
|
assert(m_writeOwner == InvalidThread);
|
|
#endif
|
|
}
|
|
|
|
public:
|
|
ReadWriteMutex(Rank rank = RankUnranked)
|
|
#ifdef DEBUG
|
|
: m_rank(rank)
|
|
#endif
|
|
{
|
|
invalidateWriteOwner();
|
|
pthread_rwlock_init(&m_rwlock, nullptr);
|
|
}
|
|
|
|
~ReadWriteMutex() {
|
|
assertNotWriteOwned();
|
|
pthread_rwlock_destroy(&m_rwlock);
|
|
}
|
|
|
|
void acquireRead() {
|
|
/*
|
|
* Atomically downgrading a write lock to a read lock is not part of the
|
|
* pthreads standard. See task #528421.
|
|
*/
|
|
assertNotWriteOwner();
|
|
pushRank(m_rank);
|
|
pthread_rwlock_rdlock(&m_rwlock);
|
|
/*
|
|
* Again, see task #528421.
|
|
*/
|
|
assertNotWriteOwned();
|
|
}
|
|
|
|
void acquireWrite() {
|
|
assertNotWriteOwner();
|
|
pushRank(m_rank);
|
|
pthread_rwlock_wrlock(&m_rwlock);
|
|
assertNotWriteOwned();
|
|
recordWriteAcquire();
|
|
}
|
|
|
|
bool attemptRead() { return !pthread_rwlock_tryrdlock(&m_rwlock); }
|
|
bool attemptWrite() { return !pthread_rwlock_trywrlock(&m_rwlock); }
|
|
void release() {
|
|
#ifdef DEBUG
|
|
popRank(m_rank);
|
|
if (m_writeOwner == pthread_self()) {
|
|
invalidateWriteOwner();
|
|
}
|
|
#endif
|
|
pthread_rwlock_unlock(&m_rwlock);
|
|
}
|
|
|
|
private:
|
|
ReadWriteMutex(const ReadWriteMutex &); // suppress
|
|
ReadWriteMutex &operator=(const ReadWriteMutex &); // suppress
|
|
|
|
pthread_rwlock_t m_rwlock;
|
|
};
|
|
|
|
/*
|
|
* A ranked wrapper around tbb::concurrent_hash_map.
|
|
*/
|
|
template<typename K, typename V, typename H=K, Rank R=RankUnranked>
|
|
class RankedCHM : public tbb::concurrent_hash_map<K, V, H> {
|
|
typedef tbb::concurrent_hash_map<K, V, H> RawCHM;
|
|
public:
|
|
class accessor : public RawCHM::accessor {
|
|
bool freed;
|
|
public:
|
|
accessor() : freed(false) { pushRank(R); }
|
|
~accessor() { if (!freed) popRank(R); }
|
|
void release() {
|
|
RawCHM::accessor::release();
|
|
popRank(R);
|
|
freed = true;
|
|
}
|
|
};
|
|
class const_accessor : public RawCHM::const_accessor {
|
|
bool freed;
|
|
public:
|
|
const_accessor() : freed(false) { pushRank(R); }
|
|
~const_accessor() { if (!freed) popRank(R); }
|
|
void release() {
|
|
RawCHM::const_accessor::release();
|
|
popRank(R);
|
|
freed = true;
|
|
}
|
|
};
|
|
|
|
bool find(const_accessor& a, const K& k) const { return RawCHM::find(a, k); }
|
|
bool find(accessor& a, const K& k) { return RawCHM::find(a, k); }
|
|
bool insert(accessor& a, const K& k) { return RawCHM::insert(a, k); }
|
|
bool insert(const_accessor& a, const K& k) { return RawCHM::insert(a, k); }
|
|
bool erase(accessor& a) { return RawCHM::erase(a); }
|
|
bool erase(const_accessor& a) { return RawCHM::erase(a); }
|
|
bool erase(const K& k) { return RawCHM::erase(k); }
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|
|
|
|
#endif // __MUTEX_H__
|