/ src / test / sync_tests.cpp
sync_tests.cpp
  1  // Copyright (c) 2012-2022 The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <sync.h>
  6  #include <test/util/setup_common.h>
  7  
  8  #include <boost/test/unit_test.hpp>
  9  
 10  #include <mutex>
 11  #include <stdexcept>
 12  
 13  namespace {
 14  template <typename MutexType>
 15  void TestPotentialDeadLockDetected(MutexType& mutex1, MutexType& mutex2)
 16  {
 17      {
 18          LOCK2(mutex1, mutex2);
 19      }
 20      BOOST_CHECK(LockStackEmpty());
 21      bool error_thrown = false;
 22      try {
 23          LOCK2(mutex2, mutex1);
 24      } catch (const std::logic_error& e) {
 25          BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected: mutex1 -> mutex2 -> mutex1");
 26          error_thrown = true;
 27      }
 28      BOOST_CHECK(LockStackEmpty());
 29      #ifdef DEBUG_LOCKORDER
 30      BOOST_CHECK(error_thrown);
 31      #else
 32      BOOST_CHECK(!error_thrown);
 33      #endif
 34  }
 35  
 36  #ifdef DEBUG_LOCKORDER
 37  template <typename MutexType>
 38  void TestDoubleLock2(MutexType& m)
 39  {
 40      ENTER_CRITICAL_SECTION(m);
 41      LEAVE_CRITICAL_SECTION(m);
 42  }
 43  
 44  template <typename MutexType>
 45  void TestDoubleLock(bool should_throw)
 46  {
 47      const bool prev = g_debug_lockorder_abort;
 48      g_debug_lockorder_abort = false;
 49  
 50      MutexType m;
 51      ENTER_CRITICAL_SECTION(m);
 52      if (should_throw) {
 53          BOOST_CHECK_EXCEPTION(TestDoubleLock2(m), std::logic_error,
 54                                HasReason("double lock detected"));
 55      } else {
 56          BOOST_CHECK_NO_THROW(TestDoubleLock2(m));
 57      }
 58      LEAVE_CRITICAL_SECTION(m);
 59  
 60      BOOST_CHECK(LockStackEmpty());
 61  
 62      g_debug_lockorder_abort = prev;
 63  }
 64  #endif /* DEBUG_LOCKORDER */
 65  
 66  template <typename MutexType>
 67  void TestInconsistentLockOrderDetected(MutexType& mutex1, MutexType& mutex2) NO_THREAD_SAFETY_ANALYSIS
 68  {
 69      ENTER_CRITICAL_SECTION(mutex1);
 70      ENTER_CRITICAL_SECTION(mutex2);
 71  #ifdef DEBUG_LOCKORDER
 72      BOOST_CHECK_EXCEPTION(LEAVE_CRITICAL_SECTION(mutex1), std::logic_error, HasReason("mutex1 was not most recent critical section locked"));
 73  #endif // DEBUG_LOCKORDER
 74      LEAVE_CRITICAL_SECTION(mutex2);
 75      LEAVE_CRITICAL_SECTION(mutex1);
 76      BOOST_CHECK(LockStackEmpty());
 77  }
 78  } // namespace
 79  
 80  BOOST_AUTO_TEST_SUITE(sync_tests)
 81  
 82  BOOST_AUTO_TEST_CASE(potential_deadlock_detected)
 83  {
 84      #ifdef DEBUG_LOCKORDER
 85      bool prev = g_debug_lockorder_abort;
 86      g_debug_lockorder_abort = false;
 87      #endif
 88  
 89      RecursiveMutex rmutex1, rmutex2;
 90      TestPotentialDeadLockDetected(rmutex1, rmutex2);
 91      // The second test ensures that lock tracking data have not been broken by exception.
 92      TestPotentialDeadLockDetected(rmutex1, rmutex2);
 93  
 94      Mutex mutex1, mutex2;
 95      TestPotentialDeadLockDetected(mutex1, mutex2);
 96      // The second test ensures that lock tracking data have not been broken by exception.
 97      TestPotentialDeadLockDetected(mutex1, mutex2);
 98  
 99      #ifdef DEBUG_LOCKORDER
100      g_debug_lockorder_abort = prev;
101      #endif
102  }
103  
104  /* Double lock would produce an undefined behavior. Thus, we only do that if
105   * DEBUG_LOCKORDER is activated to detect it. We don't want non-DEBUG_LOCKORDER
106   * build to produce tests that exhibit known undefined behavior. */
107  #ifdef DEBUG_LOCKORDER
108  BOOST_AUTO_TEST_CASE(double_lock_mutex)
109  {
110      TestDoubleLock<Mutex>(/*should_throw=*/true);
111  }
112  
113  BOOST_AUTO_TEST_CASE(double_lock_recursive_mutex)
114  {
115      TestDoubleLock<RecursiveMutex>(/*should_throw=*/false);
116  }
117  #endif /* DEBUG_LOCKORDER */
118  
119  BOOST_AUTO_TEST_CASE(inconsistent_lock_order_detected)
120  {
121  #ifdef DEBUG_LOCKORDER
122      bool prev = g_debug_lockorder_abort;
123      g_debug_lockorder_abort = false;
124  #endif // DEBUG_LOCKORDER
125  
126      RecursiveMutex rmutex1, rmutex2;
127      TestInconsistentLockOrderDetected(rmutex1, rmutex2);
128      // By checking lock order consistency (CheckLastCritical) before any unlocking (LeaveCritical)
129      // the lock tracking data must not have been broken by exception.
130      TestInconsistentLockOrderDetected(rmutex1, rmutex2);
131  
132      Mutex mutex1, mutex2;
133      TestInconsistentLockOrderDetected(mutex1, mutex2);
134      // By checking lock order consistency (CheckLastCritical) before any unlocking (LeaveCritical)
135      // the lock tracking data must not have been broken by exception.
136      TestInconsistentLockOrderDetected(mutex1, mutex2);
137  
138  #ifdef DEBUG_LOCKORDER
139      g_debug_lockorder_abort = prev;
140  #endif // DEBUG_LOCKORDER
141  }
142  
143  BOOST_AUTO_TEST_SUITE_END()