Coverage Report

Created: 2026-08-05 14:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/sync.cpp
Line
Count
Source
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
993k
{
29
993k
    LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", name, file, nLine), BCLog::LOCK);
30
993k
    lock.lock();
31
993k
}
void ContendedLock<std::unique_lock<std::mutex>>(std::basic_string_view<char, std::char_traits<char>>, std::basic_string_view<char, std::char_traits<char>>, int, std::unique_lock<std::mutex>&)
Line
Count
Source
28
841k
{
29
841k
    LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", name, file, nLine), BCLog::LOCK);
30
841k
    lock.lock();
31
841k
}
void ContendedLock<std::unique_lock<std::recursive_mutex>>(std::basic_string_view<char, std::char_traits<char>>, std::basic_string_view<char, std::char_traits<char>>, int, std::unique_lock<std::recursive_mutex>&)
Line
Count
Source
28
152k
{
29
152k
    LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", name, file, nLine), BCLog::LOCK);
30
152k
    lock.lock();
31
152k
}
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
75.5M
        : fTry(fTryIn),
57
75.5M
          mutexName(pszName),
58
75.5M
          sourceFile(pszFile),
59
75.5M
          m_thread_name(std::move(thread_name)),
60
75.5M
          sourceLine(nLine) {}
61
62
    std::string ToString() const
63
28
    {
64
28
        return strprintf(
65
28
            "'%s' in %s:%s%s (in thread '%s')",
66
28
            mutexName, sourceFile, sourceLine, (fTry ? " (TRY)" : ""), m_thread_name);
67
28
    }
68
69
    std::string Name() const
70
2.49M
    {
71
2.49M
        return mutexName;
72
2.49M
    }
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
209M
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
209M
    static LockData& lock_data = *new LockData();
103
209M
    return lock_data;
104
209M
}
105
106
static void potential_deadlock_detected(const LockPair& mismatch, const LockStack& s1, const LockStack& s2)
107
4
{
108
4
    LogError("POTENTIAL DEADLOCK DETECTED");
109
4
    LogError("Previous lock order was:");
110
8
    for (const LockStackItem& i : s1) {
111
8
        std::string prefix{};
112
8
        if (i.first == mismatch.first) {
113
4
            prefix = " (1)";
114
4
        }
115
8
        if (i.first == mismatch.second) {
116
4
            prefix = " (2)";
117
4
        }
118
8
        LogError("%s %s", prefix, i.second.ToString());
119
8
    }
120
121
4
    std::string mutex_a, mutex_b;
122
4
    LogError("Current lock order is:");
123
8
    for (const LockStackItem& i : s2) {
124
8
        std::string prefix{};
125
8
        if (i.first == mismatch.first) {
126
4
            prefix = " (1)";
127
4
            mutex_a = i.second.Name();
128
4
        }
129
8
        if (i.first == mismatch.second) {
130
4
            prefix = " (2)";
131
4
            mutex_b = i.second.Name();
132
4
        }
133
8
        LogError("%s %s", prefix, i.second.ToString());
134
8
    }
135
4
    if (g_debug_lockorder_abort) {
136
0
        tfm::format(std::cerr, "Assertion failed: detected inconsistent lock order for %s, details in debug log.\n", s2.back().second.ToString());
137
0
        abort();
138
0
    }
139
4
    throw std::logic_error(strprintf("potential deadlock detected: %s -> %s -> %s", mutex_b, mutex_a, mutex_b));
140
4
}
141
142
static void double_lock_detected(const void* mutex, const LockStack& lock_stack)
143
1
{
144
1
    LogError("DOUBLE LOCK DETECTED");
145
1
    LogError("Lock order:");
146
2
    for (const LockStackItem& i : lock_stack) {
147
2
        std::string prefix{};
148
2
        if (i.first == mutex) {
149
2
            prefix = " (*)";
150
2
        }
151
2
        LogError("%s %s", prefix, i.second.ToString());
152
2
    }
153
1
    if (g_debug_lockorder_abort) {
154
0
        tfm::format(std::cerr,
155
0
                    "Assertion failed: detected double lock for %s, details in debug log.\n",
156
0
                    lock_stack.back().second.ToString());
157
0
        abort();
158
0
    }
159
1
    throw std::logic_error("double lock detected");
160
1
}
161
162
template <typename MutexType>
163
static void push_lock(MutexType* c, const CLockLocation& locklocation)
164
75.5M
{
165
75.5M
    constexpr bool is_recursive_mutex =
166
75.5M
        std::is_base_of_v<RecursiveMutex, MutexType> ||
167
75.5M
        std::is_base_of_v<std::recursive_mutex, MutexType>;
168
169
75.5M
    LockData& lockdata = GetLockData();
170
75.5M
    STDLOCK(lockdata.dd_mutex);
171
172
75.5M
    LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
173
75.5M
    lock_stack.emplace_back(c, locklocation);
174
179M
    for (size_t j = 0; j < lock_stack.size() - 1; ++j) {
175
133M
        const LockStackItem& i = lock_stack[j];
176
133M
        if (i.first == c) {
177
30.2M
            if (is_recursive_mutex) {
178
30.2M
                break;
179
30.2M
            }
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
1
            auto lock_stack_copy = lock_stack;
185
1
            lock_stack.pop_back();
186
1
            double_lock_detected(c, lock_stack_copy);
187
            // double_lock_detected() does not return.
188
1
        }
189
190
103M
        const LockPair p1 = std::make_pair(i.first, c);
191
103M
        if (lockdata.lockorders.contains(p1))
192
103M
            continue;
193
194
223k
        const LockPair p2 = std::make_pair(c, i.first);
195
223k
        if (lockdata.lockorders.contains(p2)) {
196
4
            auto lock_stack_copy = lock_stack;
197
4
            lock_stack.pop_back();
198
4
            potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy);
199
            // potential_deadlock_detected() does not return.
200
4
        }
201
202
223k
        lockdata.lockorders.emplace(p1, lock_stack);
203
223k
        lockdata.invlockorders.insert(p2);
204
223k
    }
205
75.5M
}
sync.cpp:void push_lock<std::mutex>(std::mutex*, CLockLocation const&)
Line
Count
Source
164
37.8M
{
165
37.8M
    constexpr bool is_recursive_mutex =
166
37.8M
        std::is_base_of_v<RecursiveMutex, MutexType> ||
167
37.8M
        std::is_base_of_v<std::recursive_mutex, MutexType>;
168
169
37.8M
    LockData& lockdata = GetLockData();
170
37.8M
    STDLOCK(lockdata.dd_mutex);
171
172
37.8M
    LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
173
37.8M
    lock_stack.emplace_back(c, locklocation);
174
75.1M
    for (size_t j = 0; j < lock_stack.size() - 1; ++j) {
175
37.2M
        const LockStackItem& i = lock_stack[j];
176
37.2M
        if (i.first == c) {
177
1
            if (is_recursive_mutex) {
178
0
                break;
179
0
            }
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
1
            auto lock_stack_copy = lock_stack;
185
1
            lock_stack.pop_back();
186
1
            double_lock_detected(c, lock_stack_copy);
187
            // double_lock_detected() does not return.
188
1
        }
189
190
37.2M
        const LockPair p1 = std::make_pair(i.first, c);
191
37.2M
        if (lockdata.lockorders.contains(p1))
192
37.0M
            continue;
193
194
191k
        const LockPair p2 = std::make_pair(c, i.first);
195
191k
        if (lockdata.lockorders.contains(p2)) {
196
2
            auto lock_stack_copy = lock_stack;
197
2
            lock_stack.pop_back();
198
2
            potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy);
199
            // potential_deadlock_detected() does not return.
200
2
        }
201
202
191k
        lockdata.lockorders.emplace(p1, lock_stack);
203
191k
        lockdata.invlockorders.insert(p2);
204
191k
    }
205
37.8M
}
sync.cpp:void push_lock<std::recursive_mutex>(std::recursive_mutex*, CLockLocation const&)
Line
Count
Source
164
37.6M
{
165
37.6M
    constexpr bool is_recursive_mutex =
166
37.6M
        std::is_base_of_v<RecursiveMutex, MutexType> ||
167
37.6M
        std::is_base_of_v<std::recursive_mutex, MutexType>;
168
169
37.6M
    LockData& lockdata = GetLockData();
170
37.6M
    STDLOCK(lockdata.dd_mutex);
171
172
37.6M
    LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
173
37.6M
    lock_stack.emplace_back(c, locklocation);
174
103M
    for (size_t j = 0; j < lock_stack.size() - 1; ++j) {
175
96.5M
        const LockStackItem& i = lock_stack[j];
176
96.5M
        if (i.first == c) {
177
30.2M
            if (is_recursive_mutex) {
178
30.2M
                break;
179
30.2M
            }
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
0
            auto lock_stack_copy = lock_stack;
185
0
            lock_stack.pop_back();
186
0
            double_lock_detected(c, lock_stack_copy);
187
            // double_lock_detected() does not return.
188
0
        }
189
190
66.2M
        const LockPair p1 = std::make_pair(i.first, c);
191
66.2M
        if (lockdata.lockorders.contains(p1))
192
66.2M
            continue;
193
194
31.4k
        const LockPair p2 = std::make_pair(c, i.first);
195
31.4k
        if (lockdata.lockorders.contains(p2)) {
196
2
            auto lock_stack_copy = lock_stack;
197
2
            lock_stack.pop_back();
198
2
            potential_deadlock_detected(p1, lockdata.lockorders[p2], lock_stack_copy);
199
            // potential_deadlock_detected() does not return.
200
2
        }
201
202
31.4k
        lockdata.lockorders.emplace(p1, lock_stack);
203
31.4k
        lockdata.invlockorders.insert(p2);
204
31.4k
    }
205
37.6M
}
206
207
static void pop_lock()
208
75.5M
{
209
75.5M
    LockData& lockdata = GetLockData();
210
75.5M
    STDLOCK(lockdata.dd_mutex);
211
212
75.5M
    LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
213
75.5M
    lock_stack.pop_back();
214
75.5M
    if (lock_stack.empty()) {
215
20.0M
        lockdata.m_lock_stacks.erase(std::this_thread::get_id());
216
20.0M
    }
217
75.5M
}
218
219
template <typename MutexType>
220
void EnterCritical(const char* pszName, const char* pszFile, int nLine, MutexType* cs, bool fTry)
221
75.5M
{
222
75.5M
    push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName()));
223
75.5M
}
void EnterCritical<std::mutex>(char const*, char const*, int, std::mutex*, bool)
Line
Count
Source
221
37.8M
{
222
37.8M
    push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName()));
223
37.8M
}
void EnterCritical<std::recursive_mutex>(char const*, char const*, int, std::recursive_mutex*, bool)
Line
Count
Source
221
37.6M
{
222
37.6M
    push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry, util::ThreadGetInternalName()));
223
37.6M
}
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
2.49M
{
229
2.49M
    LockData& lockdata = GetLockData();
230
2.49M
    STDLOCK(lockdata.dd_mutex);
231
232
2.49M
    const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
233
2.49M
    if (!lock_stack.empty()) {
234
2.49M
        const auto& lastlock = lock_stack.back();
235
2.49M
        if (lastlock.first == cs) {
236
2.49M
            lockname = lastlock.second.Name();
237
2.49M
            return;
238
2.49M
        }
239
2.49M
    }
240
241
18.4E
    LogError("INCONSISTENT LOCK ORDER DETECTED");
242
18.4E
    LogError("Current lock order (least recent first) is:");
243
18.4E
    for (const LockStackItem& i : lock_stack) {
244
10
        LogError(" %s", i.second.ToString());
245
10
    }
246
18.4E
    if (g_debug_lockorder_abort) {
247
0
        tfm::format(std::cerr, "%s:%s %s was not most recent critical section locked, details in debug log.\n", file, line, guardname);
248
0
        abort();
249
0
    }
250
18.4E
    throw std::logic_error(strprintf("%s was not most recent critical section locked", guardname));
251
18.4E
}
252
253
void LeaveCritical()
254
75.5M
{
255
75.5M
    pop_lock();
256
75.5M
}
257
258
static std::string LocksHeld()
259
0
{
260
0
    LockData& lockdata = GetLockData();
261
0
    STDLOCK(lockdata.dd_mutex);
262
263
0
    const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
264
0
    std::string result;
265
0
    for (const LockStackItem& i : lock_stack)
266
0
        result += i.second.ToString() + std::string("\n");
267
0
    return result;
268
0
}
269
270
static bool LockHeld(void* mutex)
271
55.4M
{
272
55.4M
    LockData& lockdata = GetLockData();
273
55.4M
    STDLOCK(lockdata.dd_mutex);
274
275
55.4M
    const LockStack& lock_stack = lockdata.m_lock_stacks[std::this_thread::get_id()];
276
106M
    for (const LockStackItem& i : lock_stack) {
277
106M
        if (i.first == mutex) return true;
278
106M
    }
279
280
7.39M
    return false;
281
55.4M
}
282
283
template <typename MutexType>
284
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
285
48.0M
{
286
48.0M
    if (LockHeld(cs)) return;
287
18.4E
    tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
288
18.4E
    abort();
289
48.0M
}
void AssertLockHeldInternal<AnnotatedMixin<std::mutex>>(char const*, char const*, int, AnnotatedMixin<std::mutex>*)
Line
Count
Source
285
12.3M
{
286
12.3M
    if (LockHeld(cs)) return;
287
18.4E
    tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
288
18.4E
    abort();
289
12.3M
}
void AssertLockHeldInternal<AnnotatedMixin<std::recursive_mutex>>(char const*, char const*, int, AnnotatedMixin<std::recursive_mutex>*)
Line
Count
Source
285
35.6M
{
286
35.6M
    if (LockHeld(cs)) return;
287
18.4E
    tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
288
18.4E
    abort();
289
35.6M
}
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
7.39M
{
296
7.39M
    if (!LockHeld(cs)) return;
297
18.4E
    tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
298
18.4E
    abort();
299
7.39M
}
void AssertLockNotHeldInternal<AnnotatedMixin<std::mutex>>(char const*, char const*, int, AnnotatedMixin<std::mutex>*)
Line
Count
Source
295
6.77M
{
296
6.77M
    if (!LockHeld(cs)) return;
297
18.4E
    tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
298
18.4E
    abort();
299
6.77M
}
void AssertLockNotHeldInternal<AnnotatedMixin<std::recursive_mutex>>(char const*, char const*, int, AnnotatedMixin<std::recursive_mutex>*)
Line
Count
Source
295
620k
{
296
620k
    if (!LockHeld(cs)) return;
297
0
    tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
298
0
    abort();
299
620k
}
Unexecuted instantiation: void AssertLockNotHeldInternal<GlobalMutex>(char const*, char const*, int, GlobalMutex*)
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
163k
{
305
163k
    LockData& lockdata = GetLockData();
306
163k
    STDLOCK(lockdata.dd_mutex);
307
163k
    const LockPair item = std::make_pair(cs, nullptr);
308
163k
    LockOrders::iterator it = lockdata.lockorders.lower_bound(item);
309
258k
    while (it != lockdata.lockorders.end() && it->first.first == cs) {
310
94.4k
        const LockPair invitem = std::make_pair(it->first.second, it->first.first);
311
94.4k
        lockdata.invlockorders.erase(invitem);
312
94.4k
        lockdata.lockorders.erase(it++);
313
94.4k
    }
314
163k
    InvLockOrders::iterator invit = lockdata.invlockorders.lower_bound(item);
315
283k
    while (invit != lockdata.invlockorders.end() && invit->first == cs) {
316
119k
        const LockPair invinvitem = std::make_pair(invit->second, invit->first);
317
119k
        lockdata.lockorders.erase(invinvitem);
318
119k
        lockdata.invlockorders.erase(invit++);
319
119k
    }
320
163k
}
321
322
bool LockStackEmpty()
323
14
{
324
14
    LockData& lockdata = GetLockData();
325
14
    STDLOCK(lockdata.dd_mutex);
326
14
    const auto it = lockdata.m_lock_stacks.find(std::this_thread::get_id());
327
14
    if (it == lockdata.m_lock_stacks.end()) {
328
14
        return true;
329
14
    }
330
0
    return it->second.empty();
331
14
}
332
333
bool g_debug_lockorder_abort = true;
334
335
#endif /* DEBUG_LOCKORDER */