diff --git a/src/sync.cpp b/src/sync.cpp index 3b5db85b7..1a432bfda 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -1,185 +1,197 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sync.h" #include "util.h" #include "utilstrencodings.h" #include #include #ifdef DEBUG_LOCKCONTENTION #if !defined(HAVE_THREAD_LOCAL) static_assert(false, "thread_local is not supported"); #endif void PrintLockContention(const char *pszName, const char *pszFile, int nLine) { LogPrintf("LOCKCONTENTION: %s\n", pszName); LogPrintf("Locker: %s:%d\n", pszFile, nLine); } #endif /* DEBUG_LOCKCONTENTION */ #ifdef DEBUG_LOCKORDER // // Early deadlock detection. // Problem being solved: // Thread 1 locks A, then B, then C // Thread 2 locks D, then C, then A // --> may result in deadlock between the two threads, depending on when // they run. // Solution implemented here: // Keep track of pairs of locks: (A before B), (A before C), etc. // Complain if any thread tries to lock in a different order. // struct CLockLocation { CLockLocation(const char *pszName, const char *pszFile, int nLine, bool fTryIn) { mutexName = pszName; sourceFile = pszFile; sourceLine = nLine; fTry = fTryIn; } std::string ToString() const { return mutexName + " " + sourceFile + ":" + itostr(sourceLine) + (fTry ? " (TRY)" : ""); } private: bool fTry; std::string mutexName; std::string sourceFile; int sourceLine; }; typedef std::vector> LockStack; typedef std::map, LockStack> LockOrders; typedef std::set> InvLockOrders; struct LockData { // Very ugly hack: as the global constructs and destructors run single // threaded, we use this boolean to know whether LockData still exists, // as DeleteLock can get called by global CCriticalSection destructors // after LockData disappears. bool available; LockData() : available(true) {} ~LockData() { available = false; } LockOrders lockorders; InvLockOrders invlockorders; std::mutex dd_mutex; } static lockdata; static thread_local std::unique_ptr lockstack; static void potential_deadlock_detected(const std::pair &mismatch, const LockStack &s1, const LockStack &s2) { LogPrintf("POTENTIAL DEADLOCK DETECTED\n"); LogPrintf("Previous lock order was:\n"); for (const std::pair &i : s2) { if (i.first == mismatch.first) { LogPrintf(" (1)"); } if (i.first == mismatch.second) { LogPrintf(" (2)"); } LogPrintf(" %s\n", i.second.ToString()); } LogPrintf("Current lock order is:\n"); for (const std::pair &i : s1) { if (i.first == mismatch.first) { LogPrintf(" (1)"); } if (i.first == mismatch.second) { LogPrintf(" (2)"); } LogPrintf(" %s\n", i.second.ToString()); } assert(false); } static void push_lock(void *c, const CLockLocation &locklocation) { if (!lockstack) { lockstack.reset(new LockStack); } std::lock_guard lock(lockdata.dd_mutex); lockstack->push_back(std::make_pair(c, locklocation)); for (const std::pair &i : (*lockstack)) { if (i.first == c) break; std::pair p1 = std::make_pair(i.first, c); if (lockdata.lockorders.count(p1)) continue; lockdata.lockorders[p1] = (*lockstack); std::pair p2 = std::make_pair(c, i.first); lockdata.invlockorders.insert(p2); if (lockdata.lockorders.count(p2)) potential_deadlock_detected(p1, lockdata.lockorders[p2], lockdata.lockorders[p1]); } } static void pop_lock() { (*lockstack).pop_back(); } void EnterCritical(const char *pszName, const char *pszFile, int nLine, void *cs, bool fTry) { push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry)); } void LeaveCritical() { pop_lock(); } std::string LocksHeld() { std::string result; for (const std::pair &i : *lockstack) { result += i.second.ToString() + std::string("\n"); } return result; } void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, void *cs) { for (const std::pair &i : *lockstack) { if (i.first == cs) return; } fprintf(stderr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); abort(); } +void AssertLockNotHeldInternal(const char *pszName, const char *pszFile, + int nLine, void *cs) { + for (const std::pair &i : *lockstack) { + if (i.first == cs) { + fprintf(stderr, + "Assertion failed: lock %s held in %s:%i; locks held:\n%s", + pszName, pszFile, nLine, LocksHeld().c_str()); + abort(); + } + } +} + void DeleteLock(void *cs) { if (!lockdata.available) { // We're already shutting down. return; } std::lock_guard lock(lockdata.dd_mutex); std::pair item = std::make_pair(cs, nullptr); LockOrders::iterator it = lockdata.lockorders.lower_bound(item); while (it != lockdata.lockorders.end() && it->first.first == cs) { std::pair invitem = std::make_pair(it->first.second, it->first.first); lockdata.invlockorders.erase(invitem); lockdata.lockorders.erase(it++); } InvLockOrders::iterator invit = lockdata.invlockorders.lower_bound(item); while (invit != lockdata.invlockorders.end() && invit->first == cs) { std::pair invinvitem = std::make_pair(invit->second, invit->first); lockdata.lockorders.erase(invinvitem); lockdata.invlockorders.erase(invit++); } } #endif /* DEBUG_LOCKORDER */ diff --git a/src/sync.h b/src/sync.h index cd0df5cc6..a18ffab5d 100644 --- a/src/sync.h +++ b/src/sync.h @@ -1,265 +1,272 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 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 #include #include ///////////////////////////////////////////////// // // // THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE // // // ///////////////////////////////////////////////// /* CCriticalSection mutex; std::recursive_mutex mutex; LOCK(mutex); std::unique_lock criticalblock(mutex); LOCK2(mutex1, mutex2); std::unique_lock criticalblock1(mutex1); std::unique_lock criticalblock2(mutex2); TRY_LOCK(mutex, name); std::unique_lock name(mutex, std::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 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 AssertLockNotHeldInternal(const char *pszName, const char *pszFile, + int nLine, void *cs); void DeleteLock(void *cs); #else static inline void EnterCritical(const char *pszName, const char *pszFile, int nLine, void *cs, bool fTry = false) {} static inline void LeaveCritical() {} static inline void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, void *cs) {} +static inline void AssertLockNotHeldInternal(const char *pszName, + const char *pszFile, int nLine, + void *cs) {} static inline void DeleteLock(void *cs) {} #endif #define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) +#define AssertLockNotHeld(cs) \ + AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs) /** * Wrapped mutex: supports recursive locking, but no waiting * TODO: We should move away from using the recursive lock by default. */ class CCriticalSection : public AnnotatedMixin { public: ~CCriticalSection() { DeleteLock((void *)this); } }; /** Wrapped mutex: supports waiting but not recursive locking */ typedef AnnotatedMixin CWaitableCriticalSection; /** * Just a typedef for std::condition_variable, can be wrapped later if desired. */ typedef std::condition_variable CConditionVariable; /** * Just a typedef for std::unique_lock, can be wrapped later if desired. */ typedef std::unique_lock WaitableLock; #ifdef DEBUG_LOCKCONTENTION void PrintLockContention(const char *pszName, const char *pszFile, int nLine); #endif /** Wrapper around std::unique_lock */ class SCOPED_LOCKABLE CCriticalBlock { private: std::unique_lock 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: CCriticalBlock(CCriticalSection &mutexIn, const char *pszName, const char *pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : lock(mutexIn, std::defer_lock) { if (fTry) TryEnter(pszName, pszFile, nLine); else Enter(pszName, pszFile, nLine); } CCriticalBlock(CCriticalSection *pmutexIn, const char *pszName, const char *pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn) { if (!pmutexIn) return; lock = std::unique_lock(*pmutexIn, std::defer_lock); if (fTry) TryEnter(pszName, pszFile, nLine); else Enter(pszName, pszFile, nLine); } ~CCriticalBlock() UNLOCK_FUNCTION() { if (lock.owns_lock()) LeaveCritical(); } operator bool() { return lock.owns_lock(); } }; #define PASTE(x, y) x##y #define PASTE2(x, y) PASTE(x, y) #define LOCK(cs) \ CCriticalBlock PASTE2(criticalblock, __COUNTER__)(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: std::condition_variable condition; std::mutex mutex; int value; public: CSemaphore(int init) : value(init) {} void wait() { std::unique_lock lock(mutex); condition.wait(lock, [&]() { return value >= 1; }); value--; } bool try_wait() { std::lock_guard lock(mutex); if (value < 1) { return false; } value--; return true; } void post() { { std::lock_guard 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; fHaveGrant = false; } CSemaphoreGrant() : sem(nullptr), 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