aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMacroFake <falke.marco@gmail.com>2022-06-10 16:42:26 +0200
committerMacroFake <falke.marco@gmail.com>2022-06-10 16:42:53 +0200
commit8f3ab9a1b12a967cd9827675e9fce112e51d42d8 (patch)
treed445ae549da93a4ecbb8915c43a30daf40f149c3
parentc3daa321f921f4e2514ef93c48d39ae39e7f2d46 (diff)
parentce893c0497fc9b8ab9752153dfcc77c9f427545e (diff)
downloadbitcoin-8f3ab9a1b12a967cd9827675e9fce112e51d42d8.tar.xz
Merge bitcoin/bitcoin#24931: Strengthen thread safety assertions
ce893c0497fc9b8ab9752153dfcc77c9f427545e doc: Update developer notes (Anthony Towns) d2852917eecad6ab422a7b2c9892d351a7f0cc96 sync.h: Imply negative assertions when calling LOCK (Anthony Towns) bba87c0553780eacf0317fbfec7330ea27aa02f8 scripted-diff: Convert global Mutexes to GlobalMutexes (Anthony Towns) a559509a0b8cade27199740212d7b589f71a0e3b sync.h: Add GlobalMutex type (Anthony Towns) be6aa72f9f8d50b6b5b19b319a74abe7ab4099ff qt/clientmodel: thread safety annotation for m_cached_tip_mutex (Anthony Towns) f24bd45b37e1b2d19e5a053dbfefa30306c1d41a 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 ce893c0497fc9b8ab9752153dfcc77c9f427545e 🐦 hebasto: ACK ce893c0497fc9b8ab9752153dfcc77c9f427545e Tree-SHA512: 5c35e8c7677ce3d994a7e3774f4344adad496223a51b3a1d1d3b5f20684b2e1d5cff688eb3fbc8d33e1b9940dfa76e515f9434e21de6f3ce3c935e29a319f529
-rw-r--r--doc/developer-notes.md37
-rw-r--r--src/init.cpp2
-rw-r--r--src/net.cpp2
-rw-r--r--src/net.h2
-rw-r--r--src/net_processing.cpp2
-rw-r--r--src/netbase.cpp2
-rw-r--r--src/qt/clientmodel.h2
-rw-r--r--src/rpc/blockchain.cpp2
-rw-r--r--src/rpc/server.cpp4
-rw-r--r--src/sync.h38
-rw-r--r--src/timedata.cpp2
-rw-r--r--src/util/system.cpp2
-rw-r--r--src/validation.cpp2
-rw-r--r--src/validation.h2
-rw-r--r--src/wallet/sqlite.cpp2
-rw-r--r--src/wallet/wallet.cpp4
-rw-r--r--src/warnings.cpp2
17 files changed, 78 insertions, 31 deletions
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index bd385efdfd..d1b660efff 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -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>());
}
```
diff --git a/src/init.cpp b/src/init.cpp
index d0fd6074b1..f6e6f7cf43 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -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)
diff --git a/src/net.cpp b/src/net.cpp
index 82b5a69eb5..0cdf98c40f 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -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;
diff --git a/src/net.h b/src/net.h
index 3d1a2658c7..d6b605652b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -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;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 30d5548385..1d69975f55 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -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());
};
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 8ff3b7a68c..030f462ed9 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -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;
diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h
index b10ffb4523..81f03a58ec 100644
--- a/src/qt/clientmodel.h
+++ b/src/qt/clientmodel.h
@@ -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();
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index d682a7d3e8..bf4bfbb95f 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -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);
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index af00acdc9f..66ed18045e 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -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);
diff --git a/src/sync.h b/src/sync.h
index a175926113..7ec4b668ac 100644
--- a/src/sync.h
+++ b/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
{
diff --git a/src/timedata.cpp b/src/timedata.cpp
index 7faf22aba0..d0ca609a48 100644
--- a/src/timedata.cpp
+++ b/src/timedata.cpp
@@ -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;
/**
diff --git a/src/util/system.cpp b/src/util/system.cpp
index f88b0fac77..6e2638a5d6 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -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
diff --git a/src/validation.cpp b/src/validation.cpp
index 23ad221ffe..1a8f501863 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -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};
diff --git a/src/validation.h b/src/validation.h
index cc94add3cb..70b6c072e6 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -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;
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 2515df3177..053fb8f983 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -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)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 0c83c3d954..910562e669 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -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);
diff --git a/src/warnings.cpp b/src/warnings.cpp
index 60388cc713..dabb194ce1 100644
--- a/src/warnings.cpp
+++ b/src/warnings.cpp
@@ -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;