mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-19 14:45:08 +01:00
Merge bitcoin/bitcoin#24931: Strengthen thread safety assertions
ce893c0497
doc: Update developer notes (Anthony Towns)d2852917ee
sync.h: Imply negative assertions when calling LOCK (Anthony Towns)bba87c0553
scripted-diff: Convert global Mutexes to GlobalMutexes (Anthony Towns)a559509a0b
sync.h: Add GlobalMutex type (Anthony Towns)be6aa72f9f
qt/clientmodel: thread safety annotation for m_cached_tip_mutex (Anthony Towns)f24bd45b37
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 ACKce893c0497
🐦 hebasto: ACKce893c0497
Tree-SHA512: 5c35e8c7677ce3d994a7e3774f4344adad496223a51b3a1d1d3b5f20684b2e1d5cff688eb3fbc8d33e1b9940dfa76e515f9434e21de6f3ce3c935e29a319f529
This commit is contained in:
commit
8f3ab9a1b1
@ -921,14 +921,19 @@ Threads and synchronization
|
||||
- Prefer `Mutex` type to `RecursiveMutex` one.
|
||||
|
||||
- Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to
|
||||
get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with
|
||||
run-time asserts in function definitions:
|
||||
get compile-time warnings about potential race conditions or deadlocks in code.
|
||||
|
||||
- In functions that are declared separately from where they are defined, the
|
||||
thread safety annotations should be added exclusively to the function
|
||||
declaration. Annotations on the definition could lead to false positives
|
||||
(lack of compile failure) at call sites between the two.
|
||||
|
||||
- Prefer locks that are in a class rather than global, and that are
|
||||
internal to a class (private or protected) rather than public.
|
||||
|
||||
- Combine annotations in function declarations with run-time asserts in
|
||||
function definitions:
|
||||
|
||||
```C++
|
||||
// txmempool.h
|
||||
class CTxMemPool
|
||||
@ -952,21 +957,37 @@ void CTxMemPool::UpdateTransactionsFromBlock(...)
|
||||
|
||||
```C++
|
||||
// validation.h
|
||||
class ChainstateManager
|
||||
class CChainState
|
||||
{
|
||||
protected:
|
||||
...
|
||||
Mutex m_chainstate_mutex;
|
||||
...
|
||||
public:
|
||||
...
|
||||
bool ProcessNewBlock(...) LOCKS_EXCLUDED(::cs_main);
|
||||
bool ActivateBestChain(
|
||||
BlockValidationState& state,
|
||||
std::shared_ptr<const CBlock> pblock = nullptr)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
|
||||
LOCKS_EXCLUDED(::cs_main);
|
||||
...
|
||||
bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
|
||||
LOCKS_EXCLUDED(::cs_main);
|
||||
...
|
||||
}
|
||||
|
||||
// validation.cpp
|
||||
bool ChainstateManager::ProcessNewBlock(...)
|
||||
bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
|
||||
{
|
||||
AssertLockNotHeld(m_chainstate_mutex);
|
||||
AssertLockNotHeld(::cs_main);
|
||||
...
|
||||
LOCK(::cs_main);
|
||||
...
|
||||
{
|
||||
LOCK(cs_main);
|
||||
...
|
||||
}
|
||||
|
||||
return ActivateBestChain(state, std::shared_ptr<const CBlock>());
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -599,7 +599,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
}
|
||||
|
||||
static bool fHaveGenesis = false;
|
||||
static Mutex g_genesis_wait_mutex;
|
||||
static GlobalMutex g_genesis_wait_mutex;
|
||||
static std::condition_variable g_genesis_wait_cv;
|
||||
|
||||
static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex)
|
||||
|
@ -114,7 +114,7 @@ static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256
|
||||
//
|
||||
bool fDiscover = true;
|
||||
bool fListen = true;
|
||||
Mutex g_maplocalhost_mutex;
|
||||
GlobalMutex g_maplocalhost_mutex;
|
||||
std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
|
||||
static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {};
|
||||
std::string strSubVersion;
|
||||
|
@ -248,7 +248,7 @@ struct LocalServiceInfo {
|
||||
uint16_t nPort;
|
||||
};
|
||||
|
||||
extern Mutex g_maplocalhost_mutex;
|
||||
extern GlobalMutex g_maplocalhost_mutex;
|
||||
extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
|
||||
|
||||
extern const std::string NET_MESSAGE_TYPE_OTHER;
|
||||
|
@ -292,7 +292,7 @@ struct Peer {
|
||||
return m_tx_relay.get();
|
||||
};
|
||||
|
||||
TxRelay* GetTxRelay()
|
||||
TxRelay* GetTxRelay() EXCLUSIVE_LOCKS_REQUIRED(!m_tx_relay_mutex)
|
||||
{
|
||||
return WITH_LOCK(m_tx_relay_mutex, return m_tx_relay.get());
|
||||
};
|
||||
|
@ -30,7 +30,7 @@
|
||||
#endif
|
||||
|
||||
// Settings
|
||||
static Mutex g_proxyinfo_mutex;
|
||||
static GlobalMutex g_proxyinfo_mutex;
|
||||
static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
|
||||
static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
|
||||
int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
|
@ -105,7 +105,7 @@ private:
|
||||
//! A thread to interact with m_node asynchronously
|
||||
QThread* const m_thread;
|
||||
|
||||
void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header);
|
||||
void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex);
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
|
||||
|
@ -66,7 +66,7 @@ struct CUpdatedBlock
|
||||
int height;
|
||||
};
|
||||
|
||||
static Mutex cs_blockchange;
|
||||
static GlobalMutex cs_blockchange;
|
||||
static std::condition_variable cond_blockchange;
|
||||
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
|
||||
|
||||
|
@ -19,14 +19,14 @@
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
static Mutex g_rpc_warmup_mutex;
|
||||
static GlobalMutex g_rpc_warmup_mutex;
|
||||
static std::atomic<bool> g_rpc_running{false};
|
||||
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
||||
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
||||
/* Timer-creating functions */
|
||||
static RPCTimerInterface* timerInterface = nullptr;
|
||||
/* Map of name to timer. */
|
||||
static Mutex g_deadline_timers_mutex;
|
||||
static GlobalMutex g_deadline_timers_mutex;
|
||||
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
|
||||
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
|
||||
|
||||
|
38
src/sync.h
38
src/sync.h
@ -129,10 +129,22 @@ using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>;
|
||||
/** Wrapped mutex: supports waiting but not recursive locking */
|
||||
using Mutex = AnnotatedMixin<std::mutex>;
|
||||
|
||||
/** Different type to mark Mutex at global scope
|
||||
*
|
||||
* Thread safety analysis can't handle negative assertions about mutexes
|
||||
* with global scope well, so mark them with a separate type, and
|
||||
* eventually move all the mutexes into classes so they are not globally
|
||||
* visible.
|
||||
*
|
||||
* See: https://github.com/bitcoin/bitcoin/pull/20272#issuecomment-720755781
|
||||
*/
|
||||
class GlobalMutex : public Mutex { };
|
||||
|
||||
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
|
||||
|
||||
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) { AssertLockNotHeldInternal(name, file, line, cs); }
|
||||
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, RecursiveMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }
|
||||
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, GlobalMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }
|
||||
#define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs)
|
||||
|
||||
/** Wrapper around std::unique_lock style lock for Mutex. */
|
||||
@ -232,12 +244,26 @@ public:
|
||||
template<typename MutexArg>
|
||||
using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
|
||||
|
||||
#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(cs, #cs, __FILE__, __LINE__)
|
||||
// When locking a Mutex, require negative capability to ensure the lock
|
||||
// is not already held
|
||||
inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
|
||||
inline Mutex* MaybeCheckNotHeld(Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
|
||||
|
||||
// When locking a GlobalMutex, just check it is not locked in the surrounding scope
|
||||
inline GlobalMutex& MaybeCheckNotHeld(GlobalMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
|
||||
inline GlobalMutex* MaybeCheckNotHeld(GlobalMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
|
||||
|
||||
// When locking a RecursiveMutex, it's okay to already hold the lock
|
||||
// but check that it is not known to be locked in the surrounding scope anyway
|
||||
inline RecursiveMutex& MaybeCheckNotHeld(RecursiveMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
|
||||
inline RecursiveMutex* MaybeCheckNotHeld(RecursiveMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
|
||||
|
||||
#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
|
||||
#define LOCK2(cs1, cs2) \
|
||||
DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \
|
||||
DebugLock<decltype(cs2)> criticalblock2(cs2, #cs2, __FILE__, __LINE__);
|
||||
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__, true)
|
||||
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__)
|
||||
DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
|
||||
DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__);
|
||||
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
|
||||
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
|
||||
|
||||
#define ENTER_CRITICAL_SECTION(cs) \
|
||||
{ \
|
||||
@ -276,7 +302,7 @@ using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove
|
||||
//!
|
||||
//! The above is detectable at compile-time with the -Wreturn-local-addr flag in
|
||||
//! gcc and the -Wreturn-stack-address flag in clang, both enabled by default.
|
||||
#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }()
|
||||
#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }())
|
||||
|
||||
class CSemaphore
|
||||
{
|
||||
|
@ -16,7 +16,7 @@
|
||||
#include <util/translation.h>
|
||||
#include <warnings.h>
|
||||
|
||||
static Mutex g_timeoffset_mutex;
|
||||
static GlobalMutex g_timeoffset_mutex;
|
||||
static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0;
|
||||
|
||||
/**
|
||||
|
@ -96,7 +96,7 @@ const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
|
||||
ArgsManager gArgs;
|
||||
|
||||
/** Mutex to protect dir_locks. */
|
||||
static Mutex cs_dir_locks;
|
||||
static GlobalMutex cs_dir_locks;
|
||||
/** A map that contains all the currently held directory locks. After
|
||||
* successful locking, these will be held here until the global destructor
|
||||
* cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks
|
||||
|
@ -122,7 +122,7 @@ static constexpr int PRUNE_LOCK_BUFFER{10};
|
||||
*/
|
||||
RecursiveMutex cs_main;
|
||||
|
||||
Mutex g_best_block_mutex;
|
||||
GlobalMutex g_best_block_mutex;
|
||||
std::condition_variable g_best_block_cv;
|
||||
uint256 g_best_block;
|
||||
bool g_parallel_script_checks{false};
|
||||
|
@ -113,7 +113,7 @@ enum class SynchronizationState {
|
||||
};
|
||||
|
||||
extern RecursiveMutex cs_main;
|
||||
extern Mutex g_best_block_mutex;
|
||||
extern GlobalMutex g_best_block_mutex;
|
||||
extern std::condition_variable g_best_block_cv;
|
||||
/** Used to notify getblocktemplate RPC of new tips. */
|
||||
extern uint256 g_best_block;
|
||||
|
@ -23,7 +23,7 @@
|
||||
namespace wallet {
|
||||
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
|
||||
|
||||
static Mutex g_sqlite_mutex;
|
||||
static GlobalMutex g_sqlite_mutex;
|
||||
static int g_sqlite_count GUARDED_BY(g_sqlite_mutex) = 0;
|
||||
|
||||
static void ErrorLogCallback(void* arg, int code, const char* msg)
|
||||
|
@ -173,8 +173,8 @@ void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>&
|
||||
}
|
||||
}
|
||||
|
||||
static Mutex g_loading_wallet_mutex;
|
||||
static Mutex g_wallet_release_mutex;
|
||||
static GlobalMutex g_loading_wallet_mutex;
|
||||
static GlobalMutex g_wallet_release_mutex;
|
||||
static std::condition_variable g_wallet_release_cv;
|
||||
static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
|
||||
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
static Mutex g_warnings_mutex;
|
||||
static GlobalMutex g_warnings_mutex;
|
||||
static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex);
|
||||
static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user