sync.cpp raw

   1  // Copyright (c) 2011-present 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  
   7  #include <logging/timer.h>
   8  #include <tinyformat.h>
   9  #include <util/log.h>
  10  #include <util/stdmutex.h>
  11  #include <util/strencodings.h>
  12  #include <util/threadnames.h>
  13  
  14  #include <map>
  15  #include <mutex>
  16  #include <set>
  17  #include <system_error>
  18  #include <thread>
  19  #include <type_traits>
  20  #include <unordered_map>
  21  #include <utility>
  22  #include <vector>
  23  
  24  #ifdef DEBUG_LOCKCONTENTION
  25  
  26  template <typename LockType>
  27  void ContendedLock(std::string_view name, std::string_view file, int nLine, LockType& lock)
  28  {
  29      LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", name, file, nLine), BCLog::LOCK);
  30      lock.lock();
  31  }
  32  template void ContendedLock(std::string_view name, std::string_view file, int nLine, std::unique_lock<std::mutex>& lock);
  33  template void ContendedLock(std::string_view name, std::string_view file, int nLine, std::unique_lock<std::recursive_mutex>& lock);
  34  
  35  #endif
  36  
  37  #ifdef DEBUG_LOCKORDER
  38  //
  39  // Early deadlock detection.
  40  // Problem being solved:
  41  //    Thread 1 locks A, then B, then C
  42  //    Thread 2 locks D, then C, then A
  43  //     --> may result in deadlock between the two threads, depending on when they run.
  44  // Solution implemented here:
  45  // Keep track of pairs of locks: (A before B), (A before C), etc.
  46  // Complain if any thread tries to lock in a different order.
  47  //
  48  
  49  struct CLockLocation {
  50      CLockLocation(
  51          const char* pszName,
  52          const char* pszFile,
  53          int nLine,
  54          bool fTryIn,
  55          std::string&& thread_name)
  56          : fTry(fTryIn),
  57            mutexName(pszName),
  58            sourceFile(pszFile),
  59            m_thread_name(std::move(thread_name)),
  60            sourceLine(nLine) {}
  61  
  62      std::string ToString() const
  63      {
  64          return strprintf(
  65              "'%s' in %s:%s%s (in thread '%s')",
  66              mutexName, sourceFile, sourceLine, (fTry ? " (TRY)" : ""), m_thread_name);
  67      }
  68  
  69      std::string Name() const
  70      {
  71          return mutexName;
  72      }
  73  
  74  private:
  75      bool fTry;
  76      std::string mutexName;
  77      std::string sourceFile;
  78      const std::string m_thread_name;
  79      int sourceLine;
  80  };
  81  
  82  using LockStackItem = std::pair<void*, CLockLocation>;
  83  using LockStack = std::vector<LockStackItem>;
  84  using LockStacks = std::unordered_map<std::thread::id, LockStack>;
  85  
  86  using LockPair = std::pair<void*, void*>;
  87  using LockOrders = std::map<LockPair, LockStack>;
  88  using InvLockOrders = std::set<LockPair>;
  89  
  90  struct LockData {
  91      LockStacks m_lock_stacks GUARDED_BY(dd_mutex);
  92      LockOrders lockorders GUARDED_BY(dd_mutex);
  93      InvLockOrders invlockorders GUARDED_BY(dd_mutex);
  94      StdMutex dd_mutex;
  95  };
  96  
  97  LockData& GetLockData() {
  98      // This approach guarantees that the object is not destroyed until after its last use.
  99      // The operating system automatically reclaims all the memory in a program's heap when that program exits.
 100      // Since the ~LockData() destructor is never called, the LockData class and all
 101      // its subclasses must have implicitly-defined destructors.
 102      static LockData& lock_data = *new LockData();
 103      return lock_data;
 104  }
 105  
 106  static void potential_deadlock_detected(const LockPair& mismatch, const LockStack& s1, const LockStack& s2)
 107  {
 108      LogError("POTENTIAL DEADLOCK DETECTED");
 109      LogError("Previous lock order was:");
 110      for (const LockStackItem& i : s1) {
 111          std::string prefix{};
 112          if (i.first == mismatch.first) {
 113              prefix = " (1)";
 114          }
 115          if (i.first == mismatch.second) {
 116              prefix = " (2)";
 117          }
 118          LogError("%s %s", prefix, i.second.ToString());
 119      }
 120  
 121      std::string mutex_a, mutex_b;
 122      LogError("Current lock order is:");
 123      for (const LockStackItem& i : s2) {
 124          std::string prefix{};
 125          if (i.first == mismatch.first) {
 126              prefix = " (1)";
 127              mutex_a = i.second.Name();
 128          }
 129          if (i.first == mismatch.second) {
 130              prefix = " (2)";
 131              mutex_b = i.second.Name();
 132          }
 133          LogError("%s %s", prefix, i.second.ToString());
 134      }
 135      if (g_debug_lockorder_abort) {
 136          tfm::format(std::cerr, "Assertion failed: detected inconsistent lock order for %s, details in debug log.\n", s2.back().second.ToString());
 137          abort();
 138      }
 139      throw std::logic_error(strprintf("potential deadlock detected: %s -> %s -> %s", mutex_b, mutex_a, mutex_b));
 140  }
 141  
 142  static void double_lock_detected(const void* mutex, const LockStack& lock_stack)
 143  {
 144      LogError("DOUBLE LOCK DETECTED");
 145      LogError("Lock order:");
 146      for (const LockStackItem& i : lock_stack) {
 147          std::string prefix{};
 148          if (i.first == mutex) {
 149              prefix = " (*)";
 150          }
 151          LogError("%s %s", prefix, i.second.ToString());
 152      }
 153      if (g_debug_lockorder_abort) {
 154          tfm::format(std::cerr,
 155                      "Assertion failed: detected double lock for %s, details in debug log.\n",
 156                      lock_stack.back().second.ToString());
 157          abort();
 158      }
 159      throw std::logic_error("double lock detected");
 160  }
 161  
 162  template <typename MutexType>
 163  static void push_lock(MutexType* c, const CLockLocation& locklocation)
 164  {
 165      constexpr bool is_recursive_mutex =
 166          std::is_base_of_v<RecursiveMutex, MutexType> ||
 167          std::is_base_of_v<std::recursive_mutex, MutexType>;
 168  
 169      LockData& lockdata = GetLockData();
 170      STDLOCK(lockdata.dd_mutex);
 171  
 172      LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
 173      lock_stack.emplace_back(c, locklocation);
 174      for (size_t j = 0; j < lock_stack.size() - 1; ++j) {
 175          const LockStackItem& i = lock_stack[j];
 176          if (i.first == c) {
 177              if (is_recursive_mutex) {
 178                  break;
 179              }
 180              // It is not a recursive mutex and it appears in the stack two times:
 181              // at position `j` and at the end (which we added just before this loop).
 182              // Can't allow locking the same (non-recursive) mutex two times from the
 183              // same thread as that results in an undefined behavior.
 184              auto lock_stack_copy = lock_stack;
 185              lock_stack.pop_back();
 186              double_lock_detected(c, lock_stack_copy);
 187              // double_lock_detected() does not return.
 188          }
 189  
 190          const LockPair p1 = std::make_pair(i.first, c);
 191          if (lockdata.lockorders.contains(p1))
 192              continue;
 193  
 194          const LockPair p2 = std::make_pair(c, i.first);
 195          if (lockdata.lockorders.contains(p2)) {
 196              auto lock_stack_copy = lock_stack;
 197              lock_stack.pop_back();
 198              potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy);
 199              // potential_deadlock_detected() does not return.
 200          }
 201  
 202          lockdata.lockorders.emplace(p1, lock_stack);
 203          lockdata.invlockorders.insert(p2);
 204      }
 205  }
 206  
 207  static void pop_lock()
 208  {
 209      LockData& lockdata = GetLockData();
 210      STDLOCK(lockdata.dd_mutex);
 211  
 212      LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
 213      lock_stack.pop_back();
 214      if (lock_stack.empty()) {
 215          lockdata.m_lock_stacks.erase(std::this_thread::get_id());
 216      }
 217  }
 218  
 219  template <typename MutexType>
 220  void EnterCritical(const char* pszName, const char* pszFile, int nLine, MutexType* cs, bool fTry)
 221  {
 222      push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName()));
 223  }
 224  template void EnterCritical(const char*, const char*, int, std::mutex*, bool);
 225  template void EnterCritical(const char*, const char*, int, std::recursive_mutex*, bool);
 226  
 227  void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line)
 228  {
 229      LockData& lockdata = GetLockData();
 230      STDLOCK(lockdata.dd_mutex);
 231  
 232      const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
 233      if (!lock_stack.empty()) {
 234          const auto& lastlock = lock_stack.back();
 235          if (lastlock.first == cs) {
 236              lockname = lastlock.second.Name();
 237              return;
 238          }
 239      }
 240  
 241      LogError("INCONSISTENT LOCK ORDER DETECTED");
 242      LogError("Current lock order (least recent first) is:");
 243      for (const LockStackItem& i : lock_stack) {
 244          LogError(" %s", i.second.ToString());
 245      }
 246      if (g_debug_lockorder_abort) {
 247          tfm::format(std::cerr, "%s:%s %s was not most recent critical section locked, details in debug log.\n", file, line, guardname);
 248          abort();
 249      }
 250      throw std::logic_error(strprintf("%s was not most recent critical section locked", guardname));
 251  }
 252  
 253  void LeaveCritical()
 254  {
 255      pop_lock();
 256  }
 257  
 258  static std::string LocksHeld()
 259  {
 260      LockData& lockdata = GetLockData();
 261      STDLOCK(lockdata.dd_mutex);
 262  
 263      const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
 264      std::string result;
 265      for (const LockStackItem& i : lock_stack)
 266          result += i.second.ToString() + std::string("\n");
 267      return result;
 268  }
 269  
 270  static bool LockHeld(void* mutex)
 271  {
 272      LockData& lockdata = GetLockData();
 273      STDLOCK(lockdata.dd_mutex);
 274  
 275      const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
 276      for (const LockStackItem& i : lock_stack) {
 277          if (i.first == mutex) return true;
 278      }
 279  
 280      return false;
 281  }
 282  
 283  template <typename MutexType>
 284  void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
 285  {
 286      if (LockHeld(cs)) return;
 287      tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
 288      abort();
 289  }
 290  template void AssertLockHeldInternal(const char*, const char*, int, Mutex*);
 291  template void AssertLockHeldInternal(const char*, const char*, int, RecursiveMutex*);
 292  
 293  template <typename MutexType>
 294  void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
 295  {
 296      if (!LockHeld(cs)) return;
 297      tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
 298      abort();
 299  }
 300  template void AssertLockNotHeldInternal(const char*, const char*, int, Mutex*);
 301  template void AssertLockNotHeldInternal(const char*, const char*, int, RecursiveMutex*);
 302  
 303  void DeleteLock(void* cs)
 304  {
 305      LockData& lockdata = GetLockData();
 306      STDLOCK(lockdata.dd_mutex);
 307      const LockPair item = std::make_pair(cs, nullptr);
 308      LockOrders::iterator it = lockdata.lockorders.lower_bound(item);
 309      while (it != lockdata.lockorders.end() && it->first.first == cs) {
 310          const LockPair invitem = std::make_pair(it->first.second, it->first.first);
 311          lockdata.invlockorders.erase(invitem);
 312          lockdata.lockorders.erase(it++);
 313      }
 314      InvLockOrders::iterator invit = lockdata.invlockorders.lower_bound(item);
 315      while (invit != lockdata.invlockorders.end() && invit->first == cs) {
 316          const LockPair invinvitem = std::make_pair(invit->second, invit->first);
 317          lockdata.lockorders.erase(invinvitem);
 318          lockdata.invlockorders.erase(invit++);
 319      }
 320  }
 321  
 322  bool LockStackEmpty()
 323  {
 324      LockData& lockdata = GetLockData();
 325      STDLOCK(lockdata.dd_mutex);
 326      const auto it = lockdata.m_lock_stacks.find(std::this_thread::get_id());
 327      if (it == lockdata.m_lock_stacks.end()) {
 328          return true;
 329      }
 330      return it->second.empty();
 331  }
 332  
 333  bool g_debug_lockorder_abort = true;
 334  
 335  #endif /* DEBUG_LOCKORDER */
 336