Skip to content

Commit bab4cce

Browse files
author
MarcoFalke
committed
Merge #19668: Do not hide compile-time thread safety warnings
ea74e10 doc: Add best practice for annotating/asserting locks (Hennadii Stepanov) 2ee7743 sync.h: Make runtime lock checks require compile-time lock checks (Anthony Towns) 23d71d1 Do not hide compile-time thread safety warnings (Hennadii Stepanov) 3ddc150 Add missed thread safety annotations (Hennadii Stepanov) af9ea55 Use LockAssertion utility class instead of AssertLockHeld() (Hennadii Stepanov) Pull request description: On the way of transit from `RecursiveMutex` to `Mutex` (see #19303) it is crucial to have run-time `AssertLockHeld()` assertion that does _not_ hide compile-time Clang Thread Safety Analysis warnings. On master (65e4eca) using `AssertLockHeld()` could hide Clang Thread Safety Analysis warnings, e.g., with the following patch applied: ```diff --- a/src/txmempool.h +++ b/src/txmempool.h @@ -607,7 +607,7 @@ public: void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); - void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); + void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main); void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs); void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); ``` Clang compiles the code without any thread safety warnings. See "Add missed thread safety annotations" commit for the actual thread safety warnings that are fixed in this PR. ACKs for top commit: MarcoFalke: ACK ea74e10 🎙 jnewbery: ACK ea74e10 ajtowns: ACK ea74e10 Tree-SHA512: 8cba996e526751a1cb0e613c0cc1b10f027a3e9945fbfb4bd30f6355fd36b9f9c2e1e95ed3183fc254b42df7c30223278e18e5bdb5e1ef85db7fef067595d447
2 parents a1d14f5 + ea74e10 commit bab4cce

File tree

7 files changed

+86
-15
lines changed

7 files changed

+86
-15
lines changed

doc/developer-notes.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,72 @@ the upper cycle, etc.
746746
Threads and synchronization
747747
----------------------------
748748
749+
- Prefer `Mutex` type to `RecursiveMutex` one
750+
751+
- Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to
752+
get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with
753+
run-time asserts in function definitions:
754+
755+
```C++
756+
// txmempool.h
757+
class CTxMemPool
758+
{
759+
public:
760+
...
761+
mutable RecursiveMutex cs;
762+
...
763+
void UpdateTransactionsFromBlock(...) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, cs);
764+
...
765+
}
766+
767+
// txmempool.cpp
768+
void CTxMemPool::UpdateTransactionsFromBlock(...)
769+
{
770+
AssertLockHeld(::cs_main);
771+
AssertLockHeld(cs);
772+
...
773+
}
774+
```
775+
776+
```C++
777+
// validation.h
778+
class ChainstateManager
779+
{
780+
public:
781+
...
782+
bool ProcessNewBlock(...) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
783+
...
784+
}
785+
786+
// validation.cpp
787+
bool ChainstateManager::ProcessNewBlock(...)
788+
{
789+
AssertLockNotHeld(::cs_main);
790+
...
791+
LOCK(::cs_main);
792+
...
793+
}
794+
```
795+
796+
- When Clang Thread Safety Analysis is unable to determine if a mutex is locked, use `LockAssertion` class instances:
797+
798+
```C++
799+
// net_processing.h
800+
void RelayTransaction(...) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
801+
802+
// net_processing.cpp
803+
void RelayTransaction(...)
804+
{
805+
AssertLockHeld(::cs_main);
806+
807+
connman.ForEachNode([&txid, &wtxid](CNode* pnode) {
808+
LockAssertion lock(::cs_main);
809+
...
810+
});
811+
}
812+
813+
```
814+
749815
- Build and run tests with `-DDEBUG_LOCKORDER` to verify that no potential
750816
deadlocks are introduced. As of 0.12, this is defined by default when
751817
configuring with `--enable-debug`.

src/net_processing.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -663,13 +663,12 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connma
663663
}
664664
}
665665
connman.ForNode(nodeid, [&connman](CNode* pfrom){
666-
AssertLockHeld(cs_main);
666+
LockAssertion lock(::cs_main);
667667
uint64_t nCMPCTBLOCKVersion = (pfrom->GetLocalServices() & NODE_WITNESS) ? 2 : 1;
668668
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
669669
// As per BIP152, we only get 3 of our peers to announce
670670
// blocks using compact encodings.
671671
connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [&connman, nCMPCTBLOCKVersion](CNode* pnodeStop){
672-
AssertLockHeld(cs_main);
673672
connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
674673
return true;
675674
});
@@ -1371,7 +1370,7 @@ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std:
13711370
}
13721371

13731372
m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) {
1374-
AssertLockHeld(cs_main);
1373+
LockAssertion lock(::cs_main);
13751374

13761375
// TODO: Avoid the repeated-serialization here
13771376
if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
@@ -1513,7 +1512,8 @@ void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman&
15131512
{
15141513
connman.ForEachNode([&txid, &wtxid](CNode* pnode)
15151514
{
1516-
AssertLockHeld(cs_main);
1515+
LockAssertion lock(::cs_main);
1516+
15171517
CNodeState &state = *State(pnode->GetId());
15181518
if (state.m_wtxid_relay) {
15191519
pnode->PushTxInventory(wtxid);
@@ -3993,7 +3993,7 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
39933993
int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max();
39943994

39953995
m_connman.ForEachNode([&](CNode* pnode) {
3996-
AssertLockHeld(cs_main);
3996+
LockAssertion lock(::cs_main);
39973997

39983998
// Ignore non-outbound peers, or nodes marked for disconnect already
39993999
if (!pnode->IsOutboundOrBlockRelayConn() || pnode->fDisconnect) return;
@@ -4010,7 +4010,7 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
40104010
});
40114011
if (worst_peer != -1) {
40124012
bool disconnected = m_connman.ForNode(worst_peer, [&](CNode *pnode) {
4013-
AssertLockHeld(cs_main);
4013+
LockAssertion lock(::cs_main);
40144014

40154015
// Only disconnect a peer that has been connected to us for
40164016
// some reasonable fraction of our check-frequency, to give

src/sync.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,15 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine,
238238
template void AssertLockHeldInternal(const char*, const char*, int, Mutex*);
239239
template void AssertLockHeldInternal(const char*, const char*, int, RecursiveMutex*);
240240

241-
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs)
241+
template <typename MutexType>
242+
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
242243
{
243244
if (!LockHeld(cs)) return;
244245
tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
245246
abort();
246247
}
248+
template void AssertLockNotHeldInternal(const char*, const char*, int, Mutex*);
249+
template void AssertLockNotHeldInternal(const char*, const char*, int, RecursiveMutex*);
247250

248251
void DeleteLock(void* cs)
249252
{

src/sync.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ void LeaveCritical();
5353
void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line);
5454
std::string LocksHeld();
5555
template <typename MutexType>
56-
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs);
57-
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
56+
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(cs);
57+
template <typename MutexType>
58+
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs);
5859
void DeleteLock(void* cs);
5960
bool LockStackEmpty();
6061

@@ -69,8 +70,9 @@ inline void EnterCritical(const char* pszName, const char* pszFile, int nLine, v
6970
inline void LeaveCritical() {}
7071
inline void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) {}
7172
template <typename MutexType>
72-
inline void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
73-
inline void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
73+
inline void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(cs) {}
74+
template <typename MutexType>
75+
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) {}
7476
inline void DeleteLock(void* cs) {}
7577
inline bool LockStackEmpty() { return true; }
7678
#endif

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai
245245
*
246246
* See consensus/consensus.h for flag definitions.
247247
*/
248-
bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
248+
bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs);
249249

250250
/**
251251
* Closure representing one script verification

src/wallet/scriptpubkeyman.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
535535
//! keeps track of whether Unlock has run a thorough check before
536536
bool m_decryption_thoroughly_checked = false;
537537

538-
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey);
538+
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
539539

540540
KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
541541

src/wallet/wallet.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
11781178
* Obviously holding cs_main/cs_wallet when going into this call may cause
11791179
* deadlock
11801180
*/
1181-
void BlockUntilSyncedToCurrentChain() const LOCKS_EXCLUDED(cs_main, cs_wallet);
1181+
void BlockUntilSyncedToCurrentChain() const EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !cs_wallet);
11821182

11831183
/** set a single wallet flag */
11841184
void SetWalletFlag(uint64_t flags);
@@ -1284,7 +1284,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
12841284
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
12851285

12861286
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
1287-
void SetupDescriptorScriptPubKeyMans();
1287+
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
12881288

12891289
//! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet
12901290
DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const;

0 commit comments

Comments
 (0)