17#include <system_error>
20#include <unordered_map>
24#ifdef DEBUG_LOCKCONTENTION
26template <
typename LockType>
27void ContendedLock(std::string_view
name, std::string_view file,
int nLine, LockType& lock)
32template void ContendedLock(std::string_view
name, std::string_view file,
int nLine, std::unique_lock<std::mutex>& lock);
33template void ContendedLock(std::string_view
name, std::string_view file,
int nLine, std::unique_lock<std::recursive_mutex>& lock);
55 std::string&& thread_name)
59 m_thread_name(
std::move(thread_name)),
65 "'%s' in %s:%s%s (in thread '%s')",
66 mutexName, sourceFile, sourceLine, (fTry ?
" (TRY)" :
""), m_thread_name);
69 std::string Name()
const
76 std::string mutexName;
77 std::string sourceFile;
78 const std::string m_thread_name;
82using LockStackItem = std::pair<void*, CLockLocation>;
83using LockStack = std::vector<LockStackItem>;
84using LockStacks = std::unordered_map<std::thread::id, LockStack>;
86using LockPair = std::pair<void*, void*>;
87using LockOrders = std::map<LockPair, LockStack>;
88using InvLockOrders = std::set<LockPair>;
93 InvLockOrders invlockorders
GUARDED_BY(dd_mutex);
97LockData& GetLockData() {
102 static LockData& lock_data = *
new LockData();
106static void potential_deadlock_detected(
const LockPair& mismatch,
const LockStack& s1,
const LockStack& s2)
108 LogError(
"POTENTIAL DEADLOCK DETECTED");
109 LogError(
"Previous lock order was:");
110 for (
const LockStackItem& i : s1) {
112 if (i.first == mismatch.first) {
115 if (i.first == mismatch.second) {
121 std::string mutex_a, mutex_b;
123 for (
const LockStackItem& i : s2) {
125 if (i.first == mismatch.first) {
127 mutex_a = i.second.Name();
129 if (i.first == mismatch.second) {
131 mutex_b = i.second.Name();
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());
139 throw std::logic_error(
strprintf(
"potential deadlock detected: %s -> %s -> %s", mutex_b, mutex_a, mutex_b));
142static void double_lock_detected(
const void* mutex,
const LockStack& lock_stack)
146 for (
const LockStackItem& i : lock_stack) {
148 if (i.first == mutex) {
153 if (g_debug_lockorder_abort) {
155 "Assertion failed: detected double lock for %s, details in debug log.\n",
156 lock_stack.back().second.ToString());
159 throw std::logic_error(
"double lock detected");
162template <
typename MutexType>
163static void push_lock(MutexType* c,
const CLockLocation& locklocation)
165 constexpr bool is_recursive_mutex =
166 std::is_base_of_v<RecursiveMutex, MutexType> ||
167 std::is_base_of_v<std::recursive_mutex, MutexType>;
169 LockData& lockdata = GetLockData();
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];
177 if (is_recursive_mutex) {
184 auto lock_stack_copy = lock_stack;
185 lock_stack.pop_back();
186 double_lock_detected(c, lock_stack_copy);
190 const LockPair p1 = std::make_pair(i.first, c);
191 if (lockdata.lockorders.contains(p1))
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);
202 lockdata.lockorders.emplace(p1, lock_stack);
203 lockdata.invlockorders.insert(p2);
207static void pop_lock()
209 LockData& lockdata = GetLockData();
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());
219template <
typename MutexType>
220void EnterCritical(
const char* pszName,
const char* pszFile,
int nLine, MutexType*
cs,
bool fTry)
224template void EnterCritical(
const char*,
const char*,
int, std::mutex*,
bool);
225template void EnterCritical(
const char*,
const char*,
int, std::recursive_mutex*,
bool);
227void CheckLastCritical(
void*
cs, std::string& lockname,
const char* guardname,
const char* file,
int line)
229 LockData& lockdata = GetLockData();
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();
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());
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);
250 throw std::logic_error(
strprintf(
"%s was not most recent critical section locked", guardname));
258static std::string LocksHeld()
260 LockData& lockdata = GetLockData();
263 const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
265 for (
const LockStackItem& i : lock_stack)
266 result += i.second.ToString() + std::string(
"\n");
270static bool LockHeld(
void* mutex)
272 LockData& lockdata = GetLockData();
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;
283template <
typename MutexType>
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());
293template <
typename MutexType>
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());
305 LockData& lockdata = GetLockData();
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++);
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++);
324 LockData& lockdata = GetLockData();
326 const auto it = lockdata.m_lock_stacks.find(std::this_thread::get_id());
327 if (it == lockdata.m_lock_stacks.end()) {
330 return it->second.empty();
333bool g_debug_lockorder_abort =
true;
std::string ThreadGetInternalName()
Get the thread's internal (in-memory) name; used e.g.
std::string ToString(const T &t)
Locale-independent version of std::to_string.
void AssertLockHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) EXCLUSIVE_LOCKS_REQUIRED(cs)
void EnterCritical(const char *pszName, const char *pszFile, int nLine, MutexType *cs, bool fTry=false)
void DeleteLock(void *cs)
void CheckLastCritical(void *cs, std::string &lockname, const char *guardname, const char *file, int line)
void AssertLockNotHeldInternal(const char *pszName, const char *pszFile, int nLine, MutexType *cs) LOCKS_EXCLUDED(cs)
#define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category)