sync_tests.cpp raw

   1  // Copyright (c) 2012-2022 The Limenka 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()
 144