5eeb913d6c
The lockorder potential deadlock detection works by remembering for each lock A that is acquired while holding another B the pair (A,B), and triggering a warning when (B,A) already exists in the table. A and B in the above text are represented by pointers to the CCriticalSection object that is acquired. This does mean however that we need to clean up the table entries that refer to any critical section which is destroyed, as it memory address can potentially be used for another unrelated lock in the future. Implement this clean up by remembering not only the pairs in forward direction, but also backward direction. This allows for fast iteration over all pairs that use a deleted CCriticalSection in either the first or the second position.
289 lines
7.3 KiB
C++
289 lines
7.3 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2015 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifndef BITCOIN_SYNC_H
|
|
#define BITCOIN_SYNC_H
|
|
|
|
#include "threadsafety.h"
|
|
|
|
#include <boost/thread/condition_variable.hpp>
|
|
#include <boost/thread/locks.hpp>
|
|
#include <boost/thread/mutex.hpp>
|
|
#include <boost/thread/recursive_mutex.hpp>
|
|
|
|
|
|
////////////////////////////////////////////////
|
|
// //
|
|
// THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE //
|
|
// //
|
|
////////////////////////////////////////////////
|
|
|
|
/*
|
|
CCriticalSection mutex;
|
|
boost::recursive_mutex mutex;
|
|
|
|
LOCK(mutex);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock(mutex);
|
|
|
|
LOCK2(mutex1, mutex2);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock1(mutex1);
|
|
boost::unique_lock<boost::recursive_mutex> criticalblock2(mutex2);
|
|
|
|
TRY_LOCK(mutex, name);
|
|
boost::unique_lock<boost::recursive_mutex> name(mutex, boost::try_to_lock_t);
|
|
|
|
ENTER_CRITICAL_SECTION(mutex); // no RAII
|
|
mutex.lock();
|
|
|
|
LEAVE_CRITICAL_SECTION(mutex); // no RAII
|
|
mutex.unlock();
|
|
*/
|
|
|
|
///////////////////////////////
|
|
// //
|
|
// THE ACTUAL IMPLEMENTATION //
|
|
// //
|
|
///////////////////////////////
|
|
|
|
/**
|
|
* Template mixin that adds -Wthread-safety locking
|
|
* annotations to a subset of the mutex API.
|
|
*/
|
|
template <typename PARENT>
|
|
class LOCKABLE AnnotatedMixin : public PARENT
|
|
{
|
|
public:
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION()
|
|
{
|
|
PARENT::lock();
|
|
}
|
|
|
|
void unlock() UNLOCK_FUNCTION()
|
|
{
|
|
PARENT::unlock();
|
|
}
|
|
|
|
bool try_lock() EXCLUSIVE_TRYLOCK_FUNCTION(true)
|
|
{
|
|
return PARENT::try_lock();
|
|
}
|
|
};
|
|
|
|
#ifdef DEBUG_LOCKORDER
|
|
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
|
|
void LeaveCritical();
|
|
std::string LocksHeld();
|
|
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
|
|
void DeleteLock(void* cs);
|
|
#else
|
|
void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
|
|
void static inline LeaveCritical() {}
|
|
void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
|
|
void static inline DeleteLock(void* cs) {}
|
|
#endif
|
|
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
|
|
|
/**
|
|
* Wrapped boost mutex: supports recursive locking, but no waiting
|
|
* TODO: We should move away from using the recursive lock by default.
|
|
*/
|
|
class CCriticalSection : public AnnotatedMixin<boost::recursive_mutex>
|
|
{
|
|
public:
|
|
~CCriticalSection() {
|
|
DeleteLock((void*)this);
|
|
}
|
|
};
|
|
|
|
typedef CCriticalSection CDynamicCriticalSection;
|
|
/** Wrapped boost mutex: supports waiting but not recursive locking */
|
|
typedef AnnotatedMixin<boost::mutex> CWaitableCriticalSection;
|
|
|
|
/** Just a typedef for boost::condition_variable, can be wrapped later if desired */
|
|
typedef boost::condition_variable CConditionVariable;
|
|
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
|
|
#endif
|
|
|
|
/** Wrapper around boost::unique_lock<Mutex> */
|
|
template <typename Mutex>
|
|
class SCOPED_LOCKABLE CMutexLock
|
|
{
|
|
private:
|
|
boost::unique_lock<Mutex> lock;
|
|
|
|
void Enter(const char* pszName, const char* pszFile, int nLine)
|
|
{
|
|
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()));
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
if (!lock.try_lock()) {
|
|
PrintLockContention(pszName, pszFile, nLine);
|
|
#endif
|
|
lock.lock();
|
|
#ifdef DEBUG_LOCKCONTENTION
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool TryEnter(const char* pszName, const char* pszFile, int nLine)
|
|
{
|
|
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true);
|
|
lock.try_lock();
|
|
if (!lock.owns_lock())
|
|
LeaveCritical();
|
|
return lock.owns_lock();
|
|
}
|
|
|
|
public:
|
|
CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : lock(mutexIn, boost::defer_lock)
|
|
{
|
|
if (fTry)
|
|
TryEnter(pszName, pszFile, nLine);
|
|
else
|
|
Enter(pszName, pszFile, nLine);
|
|
}
|
|
|
|
CMutexLock(Mutex* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
|
|
{
|
|
if (!pmutexIn) return;
|
|
|
|
lock = boost::unique_lock<Mutex>(*pmutexIn, boost::defer_lock);
|
|
if (fTry)
|
|
TryEnter(pszName, pszFile, nLine);
|
|
else
|
|
Enter(pszName, pszFile, nLine);
|
|
}
|
|
|
|
~CMutexLock() UNLOCK_FUNCTION()
|
|
{
|
|
if (lock.owns_lock())
|
|
LeaveCritical();
|
|
}
|
|
|
|
operator bool()
|
|
{
|
|
return lock.owns_lock();
|
|
}
|
|
};
|
|
|
|
typedef CMutexLock<CCriticalSection> CCriticalBlock;
|
|
|
|
#define LOCK(cs) CCriticalBlock criticalblock(cs, #cs, __FILE__, __LINE__)
|
|
#define LOCK2(cs1, cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__), criticalblock2(cs2, #cs2, __FILE__, __LINE__)
|
|
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true)
|
|
|
|
#define ENTER_CRITICAL_SECTION(cs) \
|
|
{ \
|
|
EnterCritical(#cs, __FILE__, __LINE__, (void*)(&cs)); \
|
|
(cs).lock(); \
|
|
}
|
|
|
|
#define LEAVE_CRITICAL_SECTION(cs) \
|
|
{ \
|
|
(cs).unlock(); \
|
|
LeaveCritical(); \
|
|
}
|
|
|
|
class CSemaphore
|
|
{
|
|
private:
|
|
boost::condition_variable condition;
|
|
boost::mutex mutex;
|
|
int value;
|
|
|
|
public:
|
|
CSemaphore(int init) : value(init) {}
|
|
|
|
void wait()
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
while (value < 1) {
|
|
condition.wait(lock);
|
|
}
|
|
value--;
|
|
}
|
|
|
|
bool try_wait()
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
if (value < 1)
|
|
return false;
|
|
value--;
|
|
return true;
|
|
}
|
|
|
|
void post()
|
|
{
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(mutex);
|
|
value++;
|
|
}
|
|
condition.notify_one();
|
|
}
|
|
};
|
|
|
|
/** RAII-style semaphore lock */
|
|
class CSemaphoreGrant
|
|
{
|
|
private:
|
|
CSemaphore* sem;
|
|
bool fHaveGrant;
|
|
|
|
public:
|
|
void Acquire()
|
|
{
|
|
if (fHaveGrant)
|
|
return;
|
|
sem->wait();
|
|
fHaveGrant = true;
|
|
}
|
|
|
|
void Release()
|
|
{
|
|
if (!fHaveGrant)
|
|
return;
|
|
sem->post();
|
|
fHaveGrant = false;
|
|
}
|
|
|
|
bool TryAcquire()
|
|
{
|
|
if (!fHaveGrant && sem->try_wait())
|
|
fHaveGrant = true;
|
|
return fHaveGrant;
|
|
}
|
|
|
|
void MoveTo(CSemaphoreGrant& grant)
|
|
{
|
|
grant.Release();
|
|
grant.sem = sem;
|
|
grant.fHaveGrant = fHaveGrant;
|
|
sem = NULL;
|
|
fHaveGrant = false;
|
|
}
|
|
|
|
CSemaphoreGrant() : sem(NULL), fHaveGrant(false) {}
|
|
|
|
CSemaphoreGrant(CSemaphore& sema, bool fTry = false) : sem(&sema), fHaveGrant(false)
|
|
{
|
|
if (fTry)
|
|
TryAcquire();
|
|
else
|
|
Acquire();
|
|
}
|
|
|
|
~CSemaphoreGrant()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
operator bool()
|
|
{
|
|
return fHaveGrant;
|
|
}
|
|
};
|
|
|
|
#endif // BITCOIN_SYNC_H
|