Skip to content

Commit b423ce1

Browse files
hebastoPiRK
authored andcommitted
test: Fix inconsistent lock order in wallet_tests/CreateWallet
Summary: > sync: Improve CheckLastCritical() > > This commit adds actual lock stack logging if check fails. > [skip ci] sync: Check precondition in LEAVE_CRITICAL_SECTION() macro > > This change reveals a bug in the wallet_tests/CreateWalletFromFile test, > that will be fixed in the following commit. > test: Fix inconsistent lock order in wallet_tests/CreateWallet This is a backport of [[bitcoin/bitcoin#19982 | core#19982]] Test Plan: I confirmed that after commit #2, running `ninja check` with `-DCMAKE_BUILD_TYPE=Debug` reveals a lock order issue: ``` ../src/wallet/test/wallet_tests.cpp:853 wallet_param->wallet()->cs_wallet was not most recent critical section locked, details in debug log. ``` This goes away with the next commit. Tested also with TSAN, after removing the suppression. Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D11093
1 parent 0e5745c commit b423ce1

File tree

7 files changed

+82
-24
lines changed

7 files changed

+82
-24
lines changed

src/sync.cpp

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -176,24 +176,33 @@ void EnterCritical(const char *pszName, const char *pszFile, int nLine,
176176

177177
void CheckLastCritical(void *cs, std::string &lockname, const char *guardname,
178178
const char *file, int line) {
179-
{
180-
LockData &lockdata = GetLockData();
181-
std::lock_guard<std::mutex> lock(lockdata.dd_mutex);
182-
183-
const LockStack &lock_stack =
184-
lockdata.m_lock_stacks[std::this_thread::get_id()];
185-
if (!lock_stack.empty()) {
186-
const auto &lastlock = lock_stack.back();
187-
if (lastlock.first == cs) {
188-
lockname = lastlock.second.Name();
189-
return;
190-
}
179+
LockData &lockdata = GetLockData();
180+
std::lock_guard<std::mutex> lock(lockdata.dd_mutex);
181+
182+
const LockStack &lock_stack =
183+
lockdata.m_lock_stacks[std::this_thread::get_id()];
184+
if (!lock_stack.empty()) {
185+
const auto &lastlock = lock_stack.back();
186+
if (lastlock.first == cs) {
187+
lockname = lastlock.second.Name();
188+
return;
191189
}
192190
}
193-
throw std::system_error(
194-
EPERM, std::generic_category(),
195-
strprintf("%s:%s %s was not most recent critical section locked", file,
196-
line, guardname));
191+
192+
LogPrintf("INCONSISTENT LOCK ORDER DETECTED\n");
193+
LogPrintf("Current lock order (least recent first) is:\n");
194+
for (const LockStackItem &i : lock_stack) {
195+
LogPrintf(" %s\n", i.second.ToString());
196+
}
197+
if (g_debug_lockorder_abort) {
198+
tfm::format(std::cerr,
199+
"%s:%s %s was not most recent critical section locked, "
200+
"details in debug log.\n",
201+
file, line, guardname);
202+
abort();
203+
}
204+
throw std::logic_error(
205+
strprintf("%s was not most recent critical section locked", guardname));
197206
}
198207

199208
void LeaveCritical() {

src/sync.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ using DebugLock = UniqueLock<typename std::remove_reference<
257257

258258
#define LEAVE_CRITICAL_SECTION(cs) \
259259
{ \
260+
std::string lockname; \
261+
CheckLastCritical((void *)(&cs), lockname, #cs, __FILE__, __LINE__); \
260262
(cs).unlock(); \
261263
LeaveCritical(); \
262264
}

src/test/reverselock_tests.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ BOOST_AUTO_TEST_CASE(reverselock_errors) {
4646
WAIT_LOCK(mutex, lock);
4747

4848
#ifdef DEBUG_LOCKORDER
49+
bool prev = g_debug_lockorder_abort;
50+
g_debug_lockorder_abort = false;
51+
4952
// Make sure trying to reverse lock a previous lock fails
50-
try {
51-
REVERSE_LOCK(lock2);
52-
BOOST_CHECK(false); // REVERSE_LOCK(lock2) succeeded
53-
} catch (...) {
54-
}
53+
BOOST_CHECK_EXCEPTION(
54+
REVERSE_LOCK(lock2), std::logic_error,
55+
HasReason("lock2 was not most recent critical section locked"));
5556
BOOST_CHECK(lock2.owns_lock());
57+
58+
g_debug_lockorder_abort = prev;
5659
#endif
5760

5861
// Make sure trying to reverse lock an unlocked lock fails

src/test/sync_tests.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ void TestPotentialDeadLockDetected(MutexType &mutex1, MutexType &mutex2) {
2828
BOOST_CHECK(!error_thrown);
2929
#endif
3030
}
31+
32+
template <typename MutexType>
33+
void TestInconsistentLockOrderDetected(MutexType &mutex1, MutexType &mutex2)
34+
NO_THREAD_SAFETY_ANALYSIS {
35+
ENTER_CRITICAL_SECTION(mutex1);
36+
ENTER_CRITICAL_SECTION(mutex2);
37+
#ifdef DEBUG_LOCKORDER
38+
BOOST_CHECK_EXCEPTION(
39+
LEAVE_CRITICAL_SECTION(mutex1), std::logic_error,
40+
HasReason("mutex1 was not most recent critical section locked"));
41+
#endif // DEBUG_LOCKORDER
42+
LEAVE_CRITICAL_SECTION(mutex2);
43+
LEAVE_CRITICAL_SECTION(mutex1);
44+
BOOST_CHECK(LockStackEmpty());
45+
}
3146
} // namespace
3247

3348
BOOST_FIXTURE_TEST_SUITE(sync_tests, BasicTestingSetup)
@@ -55,4 +70,29 @@ BOOST_AUTO_TEST_CASE(potential_deadlock_detected) {
5570
#endif
5671
}
5772

73+
BOOST_AUTO_TEST_CASE(inconsistent_lock_order_detected) {
74+
#ifdef DEBUG_LOCKORDER
75+
bool prev = g_debug_lockorder_abort;
76+
g_debug_lockorder_abort = false;
77+
#endif // DEBUG_LOCKORDER
78+
79+
RecursiveMutex rmutex1, rmutex2;
80+
TestInconsistentLockOrderDetected(rmutex1, rmutex2);
81+
// By checking lock order consistency (CheckLastCritical) before any
82+
// unlocking (LeaveCritical) the lock tracking data must not have been
83+
// broken by exception.
84+
TestInconsistentLockOrderDetected(rmutex1, rmutex2);
85+
86+
Mutex mutex1, mutex2;
87+
TestInconsistentLockOrderDetected(mutex1, mutex2);
88+
// By checking lock order consistency (CheckLastCritical) before any
89+
// unlocking (LeaveCritical) the lock tracking data must not have been
90+
// broken by exception.
91+
TestInconsistentLockOrderDetected(mutex1, mutex2);
92+
93+
#ifdef DEBUG_LOCKORDER
94+
g_debug_lockorder_abort = prev;
95+
#endif // DEBUG_LOCKORDER
96+
}
97+
5898
BOOST_AUTO_TEST_SUITE_END()

src/wallet/test/wallet_tests.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include <memory>
3030
#include <vector>
3131

32+
extern RecursiveMutex cs_wallets;
33+
3234
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
3335

3436
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain &chain) {
@@ -830,7 +832,8 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) {
830832
addtx_count = 0;
831833
auto handler = HandleLoadWallet(
832834
[&](std::unique_ptr<interfaces::Wallet> wallet_param)
833-
EXCLUSIVE_LOCKS_REQUIRED(wallet_param->wallet()->cs_wallet) {
835+
EXCLUSIVE_LOCKS_REQUIRED(wallet_param->wallet()->cs_wallet,
836+
cs_wallets) {
834837
BOOST_CHECK(rescan_completed);
835838
m_coinbase_txns.push_back(
836839
CreateAndProcessBlock(
@@ -850,9 +853,11 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) {
850853
BOOST_CHECK(chain->broadcastTransaction(
851854
GetConfig(), MakeTransactionRef(mempool_tx),
852855
DEFAULT_TRANSACTION_MAXFEE, false, error));
856+
LEAVE_CRITICAL_SECTION(cs_wallets);
853857
LEAVE_CRITICAL_SECTION(wallet_param->wallet()->cs_wallet);
854858
SyncWithValidationInterfaceQueue();
855859
ENTER_CRITICAL_SECTION(wallet_param->wallet()->cs_wallet);
860+
ENTER_CRITICAL_SECTION(cs_wallets);
856861
});
857862
wallet = TestLoadWallet(*chain);
858863
BOOST_CHECK_EQUAL(addtx_count, 4);

src/wallet/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
4545
"be considered unused, even if the opposite is the case."},
4646
};
4747

48-
static RecursiveMutex cs_wallets;
48+
RecursiveMutex cs_wallets;
4949
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
5050
static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
5151

test/sanitizer_suppressions/tsan

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ deadlock:CConnman::ForNode
3535
deadlock:CConnman::GetNodeStats
3636
deadlock:CChainState::ConnectTip
3737
deadlock:UpdateTip
38-
deadlock:wallet_tests::CreateWallet
3938

4039
# Lock order inversion due to the large cs_main lock from Shutdown, which comes
4140
# before the cs_proofrequest lock

0 commit comments

Comments
 (0)