Skip to content

Commit 8f3ab9a

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#24931: Strengthen thread safety assertions
ce893c0 doc: Update developer notes (Anthony Towns) d285291 sync.h: Imply negative assertions when calling LOCK (Anthony Towns) bba87c0 scripted-diff: Convert global Mutexes to GlobalMutexes (Anthony Towns) a559509 sync.h: Add GlobalMutex type (Anthony Towns) be6aa72 qt/clientmodel: thread safety annotation for m_cached_tip_mutex (Anthony Towns) f24bd45 net_processing: thread safety annotation for m_tx_relay_mutex (Anthony Towns) Pull request description: This changes `LOCK(mutex)` for non-global, non-recursive mutexes to be annotated with the negative capability for the mutex it refers to, to prevent . clang applies negative capabilities recursively, so this helps avoid forgetting to annotate functions. This can't reasonably be used for globals, because clang would require every function to be annotated with `EXCLUSIVE_LOCKS_REQUIRED(!g_mutex)` for each global mutex; so this introduces a trivial `GlobalMutex` subclass of `Mutex`, and reduces the annotations for both `GlobalMutex` to `LOCKS_EXCLUDED` which only catches trivial errors (eg (`LOCK(x); LOCK(x);`). ACKs for top commit: MarcoFalke: review ACK ce893c0 🐦 hebasto: ACK ce893c0 Tree-SHA512: 5c35e8c7677ce3d994a7e3774f4344adad496223a51b3a1d1d3b5f20684b2e1d5cff688eb3fbc8d33e1b9940dfa76e515f9434e21de6f3ce3c935e29a319f529
2 parents c3daa32 + ce893c0 commit 8f3ab9a

17 files changed

+78
-31
lines changed

doc/developer-notes.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -921,14 +921,19 @@ Threads and synchronization
921921
- Prefer `Mutex` type to `RecursiveMutex` one.
922922
923923
- Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to
924-
get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with
925-
run-time asserts in function definitions:
924+
get compile-time warnings about potential race conditions or deadlocks in code.
926925
927926
- In functions that are declared separately from where they are defined, the
928927
thread safety annotations should be added exclusively to the function
929928
declaration. Annotations on the definition could lead to false positives
930929
(lack of compile failure) at call sites between the two.
931930
931+
- Prefer locks that are in a class rather than global, and that are
932+
internal to a class (private or protected) rather than public.
933+
934+
- Combine annotations in function declarations with run-time asserts in
935+
function definitions:
936+
932937
```C++
933938
// txmempool.h
934939
class CTxMemPool
@@ -952,21 +957,37 @@ void CTxMemPool::UpdateTransactionsFromBlock(...)
952957

953958
```C++
954959
// validation.h
955-
class ChainstateManager
960+
class CChainState
956961
{
962+
protected:
963+
...
964+
Mutex m_chainstate_mutex;
965+
...
957966
public:
958967
...
959-
bool ProcessNewBlock(...) LOCKS_EXCLUDED(::cs_main);
968+
bool ActivateBestChain(
969+
BlockValidationState& state,
970+
std::shared_ptr<const CBlock> pblock = nullptr)
971+
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
972+
LOCKS_EXCLUDED(::cs_main);
973+
...
974+
bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
975+
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
976+
LOCKS_EXCLUDED(::cs_main);
960977
...
961978
}
962979

963980
// validation.cpp
964-
bool ChainstateManager::ProcessNewBlock(...)
981+
bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
965982
{
983+
AssertLockNotHeld(m_chainstate_mutex);
966984
AssertLockNotHeld(::cs_main);
967-
...
968-
LOCK(::cs_main);
969-
...
985+
{
986+
LOCK(cs_main);
987+
...
988+
}
989+
990+
return ActivateBestChain(state, std::shared_ptr<const CBlock>());
970991
}
971992
```
972993

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ void SetupServerArgs(ArgsManager& argsman)
599599
}
600600

601601
static bool fHaveGenesis = false;
602-
static Mutex g_genesis_wait_mutex;
602+
static GlobalMutex g_genesis_wait_mutex;
603603
static std::condition_variable g_genesis_wait_cv;
604604

605605
static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex)

src/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256
114114
//
115115
bool fDiscover = true;
116116
bool fListen = true;
117-
Mutex g_maplocalhost_mutex;
117+
GlobalMutex g_maplocalhost_mutex;
118118
std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
119119
static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {};
120120
std::string strSubVersion;

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ struct LocalServiceInfo {
248248
uint16_t nPort;
249249
};
250250

251-
extern Mutex g_maplocalhost_mutex;
251+
extern GlobalMutex g_maplocalhost_mutex;
252252
extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
253253

254254
extern const std::string NET_MESSAGE_TYPE_OTHER;

src/net_processing.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ struct Peer {
292292
return m_tx_relay.get();
293293
};
294294

295-
TxRelay* GetTxRelay()
295+
TxRelay* GetTxRelay() EXCLUSIVE_LOCKS_REQUIRED(!m_tx_relay_mutex)
296296
{
297297
return WITH_LOCK(m_tx_relay_mutex, return m_tx_relay.get());
298298
};

src/netbase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
#endif
3131

3232
// Settings
33-
static Mutex g_proxyinfo_mutex;
33+
static GlobalMutex g_proxyinfo_mutex;
3434
static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
3535
static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
3636
int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;

src/qt/clientmodel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class ClientModel : public QObject
105105
//! A thread to interact with m_node asynchronously
106106
QThread* const m_thread;
107107

108-
void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header);
108+
void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex);
109109
void subscribeToCoreSignals();
110110
void unsubscribeFromCoreSignals();
111111

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ struct CUpdatedBlock
6666
int height;
6767
};
6868

69-
static Mutex cs_blockchange;
69+
static GlobalMutex cs_blockchange;
7070
static std::condition_variable cond_blockchange;
7171
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
7272

src/rpc/server.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
#include <mutex>
2020
#include <unordered_map>
2121

22-
static Mutex g_rpc_warmup_mutex;
22+
static GlobalMutex g_rpc_warmup_mutex;
2323
static std::atomic<bool> g_rpc_running{false};
2424
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
2525
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
2626
/* Timer-creating functions */
2727
static RPCTimerInterface* timerInterface = nullptr;
2828
/* Map of name to timer. */
29-
static Mutex g_deadline_timers_mutex;
29+
static GlobalMutex g_deadline_timers_mutex;
3030
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
3131
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
3232

src/sync.h

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,22 @@ using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>;
129129
/** Wrapped mutex: supports waiting but not recursive locking */
130130
using Mutex = AnnotatedMixin<std::mutex>;
131131

132+
/** Different type to mark Mutex at global scope
133+
*
134+
* Thread safety analysis can't handle negative assertions about mutexes
135+
* with global scope well, so mark them with a separate type, and
136+
* eventually move all the mutexes into classes so they are not globally
137+
* visible.
138+
*
139+
* See: https://github.com/bitcoin/bitcoin/pull/20272#issuecomment-720755781
140+
*/
141+
class GlobalMutex : public Mutex { };
142+
132143
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
133144

134145
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) { AssertLockNotHeldInternal(name, file, line, cs); }
135146
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, RecursiveMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }
147+
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, GlobalMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }
136148
#define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs)
137149

138150
/** Wrapper around std::unique_lock style lock for Mutex. */
@@ -232,12 +244,26 @@ class SCOPED_LOCKABLE UniqueLock : public Base
232244
template<typename MutexArg>
233245
using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
234246

235-
#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(cs, #cs, __FILE__, __LINE__)
247+
// When locking a Mutex, require negative capability to ensure the lock
248+
// is not already held
249+
inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
250+
inline Mutex* MaybeCheckNotHeld(Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
251+
252+
// When locking a GlobalMutex, just check it is not locked in the surrounding scope
253+
inline GlobalMutex& MaybeCheckNotHeld(GlobalMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
254+
inline GlobalMutex* MaybeCheckNotHeld(GlobalMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
255+
256+
// When locking a RecursiveMutex, it's okay to already hold the lock
257+
// but check that it is not known to be locked in the surrounding scope anyway
258+
inline RecursiveMutex& MaybeCheckNotHeld(RecursiveMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
259+
inline RecursiveMutex* MaybeCheckNotHeld(RecursiveMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
260+
261+
#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
236262
#define LOCK2(cs1, cs2) \
237-
DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \
238-
DebugLock<decltype(cs2)> criticalblock2(cs2, #cs2, __FILE__, __LINE__);
239-
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__, true)
240-
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__)
263+
DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
264+
DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__);
265+
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
266+
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
241267

242268
#define ENTER_CRITICAL_SECTION(cs) \
243269
{ \
@@ -276,7 +302,7 @@ using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove
276302
//!
277303
//! The above is detectable at compile-time with the -Wreturn-local-addr flag in
278304
//! gcc and the -Wreturn-stack-address flag in clang, both enabled by default.
279-
#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }()
305+
#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }())
280306

281307
class CSemaphore
282308
{

0 commit comments

Comments
 (0)