mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-29 15:45:00 +02:00
Merge #19340: Preserve the LockData
initial state if "potential deadlock detected" exception thrown
63e9e40b73
test: Add LockStackEmpty() (Hennadii Stepanov)42b2a95373
test: Repeat deadlock tests (Hennadii Stepanov)1f96be25b0
Preserve initial state if push_lock() throws exception (Hennadii Stepanov) Pull request description: On master (e3fa3c7d67
) if the `push_lock()` throws the "potential deadlock detected" exception (via the `potential_deadlock_detected()` call), the `LockData` instance internal state differs from one when the `push_lock()` was called. This non-well behaviour makes (at least) testing brittle. This PR preserves the `LockData` instance initial state if `push_lock()` throws an exception, and improves the `sync_tests` unit test. ACKs for top commit: MarcoFalke: re-ACK63e9e40b73
vasild: ACK63e9e40
Tree-SHA512: 7679182154ce5f079b44b790faf76eb5f553328dea70a326ff6b600db70e2f9ae015a33a104ca070cb660318280cb79b6b42e37ea5166f26f9e627ba721fcdec
This commit is contained in:
22
src/sync.cpp
22
src/sync.cpp
@@ -149,12 +149,17 @@ static void push_lock(void* c, const CLockLocation& locklocation)
|
|||||||
const LockPair p1 = std::make_pair(i.first, c);
|
const LockPair p1 = std::make_pair(i.first, c);
|
||||||
if (lockdata.lockorders.count(p1))
|
if (lockdata.lockorders.count(p1))
|
||||||
continue;
|
continue;
|
||||||
lockdata.lockorders.emplace(p1, lock_stack);
|
|
||||||
|
|
||||||
const LockPair p2 = std::make_pair(c, i.first);
|
const LockPair p2 = std::make_pair(c, i.first);
|
||||||
|
if (lockdata.lockorders.count(p2)) {
|
||||||
|
auto lock_stack_copy = lock_stack;
|
||||||
|
lock_stack.pop_back();
|
||||||
|
potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy);
|
||||||
|
// potential_deadlock_detected() does not return.
|
||||||
|
}
|
||||||
|
|
||||||
|
lockdata.lockorders.emplace(p1, lock_stack);
|
||||||
lockdata.invlockorders.insert(p2);
|
lockdata.invlockorders.insert(p2);
|
||||||
if (lockdata.lockorders.count(p2))
|
|
||||||
potential_deadlock_detected(p1, lockdata.lockorders[p2], lockdata.lockorders[p1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +264,17 @@ void DeleteLock(void* cs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LockStackEmpty()
|
||||||
|
{
|
||||||
|
LockData& lockdata = GetLockData();
|
||||||
|
std::lock_guard<std::mutex> lock(lockdata.dd_mutex);
|
||||||
|
const auto it = lockdata.m_lock_stacks.find(std::this_thread::get_id());
|
||||||
|
if (it == lockdata.m_lock_stacks.end()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return it->second.empty();
|
||||||
|
}
|
||||||
|
|
||||||
bool g_debug_lockorder_abort = true;
|
bool g_debug_lockorder_abort = true;
|
||||||
|
|
||||||
#endif /* DEBUG_LOCKORDER */
|
#endif /* DEBUG_LOCKORDER */
|
||||||
|
14
src/sync.h
14
src/sync.h
@@ -56,6 +56,7 @@ template <typename MutexType>
|
|||||||
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs);
|
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs);
|
||||||
void AssertLockNotHeldInternal(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);
|
void DeleteLock(void* cs);
|
||||||
|
bool LockStackEmpty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call abort() if a potential lock order deadlock bug is detected, instead of
|
* Call abort() if a potential lock order deadlock bug is detected, instead of
|
||||||
@@ -64,13 +65,14 @@ void DeleteLock(void* cs);
|
|||||||
*/
|
*/
|
||||||
extern bool g_debug_lockorder_abort;
|
extern bool g_debug_lockorder_abort;
|
||||||
#else
|
#else
|
||||||
void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
|
inline void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
|
||||||
void static inline LeaveCritical() {}
|
inline void LeaveCritical() {}
|
||||||
void static inline CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) {}
|
inline void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) {}
|
||||||
template <typename MutexType>
|
template <typename MutexType>
|
||||||
void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
|
inline void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
|
||||||
void static inline AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
|
inline void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
|
||||||
void static inline DeleteLock(void* cs) {}
|
inline void DeleteLock(void* cs) {}
|
||||||
|
inline bool LockStackEmpty() { return true; }
|
||||||
#endif
|
#endif
|
||||||
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
||||||
#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
||||||
|
@@ -14,6 +14,7 @@ void TestPotentialDeadLockDetected(MutexType& mutex1, MutexType& mutex2)
|
|||||||
{
|
{
|
||||||
LOCK2(mutex1, mutex2);
|
LOCK2(mutex1, mutex2);
|
||||||
}
|
}
|
||||||
|
BOOST_CHECK(LockStackEmpty());
|
||||||
bool error_thrown = false;
|
bool error_thrown = false;
|
||||||
try {
|
try {
|
||||||
LOCK2(mutex2, mutex1);
|
LOCK2(mutex2, mutex1);
|
||||||
@@ -21,6 +22,7 @@ void TestPotentialDeadLockDetected(MutexType& mutex1, MutexType& mutex2)
|
|||||||
BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected: mutex1 -> mutex2 -> mutex1");
|
BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected: mutex1 -> mutex2 -> mutex1");
|
||||||
error_thrown = true;
|
error_thrown = true;
|
||||||
}
|
}
|
||||||
|
BOOST_CHECK(LockStackEmpty());
|
||||||
#ifdef DEBUG_LOCKORDER
|
#ifdef DEBUG_LOCKORDER
|
||||||
BOOST_CHECK(error_thrown);
|
BOOST_CHECK(error_thrown);
|
||||||
#else
|
#else
|
||||||
@@ -40,9 +42,13 @@ BOOST_AUTO_TEST_CASE(potential_deadlock_detected)
|
|||||||
|
|
||||||
RecursiveMutex rmutex1, rmutex2;
|
RecursiveMutex rmutex1, rmutex2;
|
||||||
TestPotentialDeadLockDetected(rmutex1, rmutex2);
|
TestPotentialDeadLockDetected(rmutex1, rmutex2);
|
||||||
|
// The second test ensures that lock tracking data have not been broken by exception.
|
||||||
|
TestPotentialDeadLockDetected(rmutex1, rmutex2);
|
||||||
|
|
||||||
Mutex mutex1, mutex2;
|
Mutex mutex1, mutex2;
|
||||||
TestPotentialDeadLockDetected(mutex1, mutex2);
|
TestPotentialDeadLockDetected(mutex1, mutex2);
|
||||||
|
// The second test ensures that lock tracking data have not been broken by exception.
|
||||||
|
TestPotentialDeadLockDetected(mutex1, mutex2);
|
||||||
|
|
||||||
#ifdef DEBUG_LOCKORDER
|
#ifdef DEBUG_LOCKORDER
|
||||||
g_debug_lockorder_abort = prev;
|
g_debug_lockorder_abort = prev;
|
||||||
|
Reference in New Issue
Block a user