mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-22 18:12:31 +02: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.
|
- Prefer `Mutex` type to `RecursiveMutex` one.
|
||||||
|
|
||||||
- Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to
|
- 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
|
get compile-time warnings about potential race conditions or deadlocks in code.
|
||||||
run-time asserts in function definitions:
|
|
||||||
|
|
||||||
- In functions that are declared separately from where they are defined, the
|
- In functions that are declared separately from where they are defined, the
|
||||||
thread safety annotations should be added exclusively to the function
|
thread safety annotations should be added exclusively to the function
|
||||||
declaration. Annotations on the definition could lead to false positives
|
declaration. Annotations on the definition could lead to false positives
|
||||||
(lack of compile failure) at call sites between the two.
|
(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++
|
```C++
|
||||||
// txmempool.h
|
// txmempool.h
|
||||||
class CTxMemPool
|
class CTxMemPool
|
||||||
@ -952,21 +957,37 @@ void CTxMemPool::UpdateTransactionsFromBlock(...)
|
|||||||
|
|
||||||
```C++
|
```C++
|
||||||
// validation.h
|
// validation.h
|
||||||
class ChainstateManager
|
class CChainState
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
|
...
|
||||||
|
Mutex m_chainstate_mutex;
|
||||||
|
...
|
||||||
public:
|
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
|
// validation.cpp
|
||||||
bool ChainstateManager::ProcessNewBlock(...)
|
bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
|
||||||
{
|
{
|
||||||
|
AssertLockNotHeld(m_chainstate_mutex);
|
||||||
AssertLockNotHeld(::cs_main);
|
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 bool fHaveGenesis = false;
|
||||||
static Mutex g_genesis_wait_mutex;
|
static GlobalMutex g_genesis_wait_mutex;
|
||||||
static std::condition_variable g_genesis_wait_cv;
|
static std::condition_variable g_genesis_wait_cv;
|
||||||
|
|
||||||
static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex)
|
static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex)
|
||||||
|
@ -114,7 +114,7 @@ static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256
|
|||||||
//
|
//
|
||||||
bool fDiscover = true;
|
bool fDiscover = true;
|
||||||
bool fListen = true;
|
bool fListen = true;
|
||||||
Mutex g_maplocalhost_mutex;
|
GlobalMutex g_maplocalhost_mutex;
|
||||||
std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
|
std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
|
||||||
static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {};
|
static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {};
|
||||||
std::string strSubVersion;
|
std::string strSubVersion;
|
||||||
|
@ -248,7 +248,7 @@ struct LocalServiceInfo {
|
|||||||
uint16_t nPort;
|
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 std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
|
||||||
|
|
||||||
extern const std::string NET_MESSAGE_TYPE_OTHER;
|
extern const std::string NET_MESSAGE_TYPE_OTHER;
|
||||||
|
@ -292,7 +292,7 @@ struct Peer {
|
|||||||
return m_tx_relay.get();
|
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());
|
return WITH_LOCK(m_tx_relay_mutex, return m_tx_relay.get());
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
static Mutex g_proxyinfo_mutex;
|
static GlobalMutex g_proxyinfo_mutex;
|
||||||
static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
|
static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
|
||||||
static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
|
static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
|
||||||
int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
||||||
|
@ -105,7 +105,7 @@ private:
|
|||||||
//! A thread to interact with m_node asynchronously
|
//! A thread to interact with m_node asynchronously
|
||||||
QThread* const m_thread;
|
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 subscribeToCoreSignals();
|
||||||
void unsubscribeFromCoreSignals();
|
void unsubscribeFromCoreSignals();
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ struct CUpdatedBlock
|
|||||||
int height;
|
int height;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Mutex cs_blockchange;
|
static GlobalMutex cs_blockchange;
|
||||||
static std::condition_variable cond_blockchange;
|
static std::condition_variable cond_blockchange;
|
||||||
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
|
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
static Mutex g_rpc_warmup_mutex;
|
static GlobalMutex g_rpc_warmup_mutex;
|
||||||
static std::atomic<bool> g_rpc_running{false};
|
static std::atomic<bool> g_rpc_running{false};
|
||||||
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
||||||
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
||||||
/* Timer-creating functions */
|
/* Timer-creating functions */
|
||||||
static RPCTimerInterface* timerInterface = nullptr;
|
static RPCTimerInterface* timerInterface = nullptr;
|
||||||
/* Map of name to timer. */
|
/* 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 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);
|
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 */
|
/** Wrapped mutex: supports waiting but not recursive locking */
|
||||||
using Mutex = AnnotatedMixin<std::mutex>;
|
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)
|
#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, 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, 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)
|
#define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs)
|
||||||
|
|
||||||
/** Wrapper around std::unique_lock style lock for Mutex. */
|
/** Wrapper around std::unique_lock style lock for Mutex. */
|
||||||
@ -232,12 +244,26 @@ public:
|
|||||||
template<typename MutexArg>
|
template<typename MutexArg>
|
||||||
using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
|
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) \
|
#define LOCK2(cs1, cs2) \
|
||||||
DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \
|
DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
|
||||||
DebugLock<decltype(cs2)> criticalblock2(cs2, #cs2, __FILE__, __LINE__);
|
DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__);
|
||||||
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__, true)
|
#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
|
||||||
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__)
|
#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
|
||||||
|
|
||||||
#define ENTER_CRITICAL_SECTION(cs) \
|
#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
|
//! 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.
|
//! 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
|
class CSemaphore
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
#include <warnings.h>
|
#include <warnings.h>
|
||||||
|
|
||||||
static Mutex g_timeoffset_mutex;
|
static GlobalMutex g_timeoffset_mutex;
|
||||||
static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0;
|
static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,7 +96,7 @@ const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
|
|||||||
ArgsManager gArgs;
|
ArgsManager gArgs;
|
||||||
|
|
||||||
/** Mutex to protect dir_locks. */
|
/** 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
|
/** A map that contains all the currently held directory locks. After
|
||||||
* successful locking, these will be held here until the global destructor
|
* successful locking, these will be held here until the global destructor
|
||||||
* cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks
|
* cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks
|
||||||
|
@ -122,7 +122,7 @@ static constexpr int PRUNE_LOCK_BUFFER{10};
|
|||||||
*/
|
*/
|
||||||
RecursiveMutex cs_main;
|
RecursiveMutex cs_main;
|
||||||
|
|
||||||
Mutex g_best_block_mutex;
|
GlobalMutex g_best_block_mutex;
|
||||||
std::condition_variable g_best_block_cv;
|
std::condition_variable g_best_block_cv;
|
||||||
uint256 g_best_block;
|
uint256 g_best_block;
|
||||||
bool g_parallel_script_checks{false};
|
bool g_parallel_script_checks{false};
|
||||||
|
@ -113,7 +113,7 @@ enum class SynchronizationState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern RecursiveMutex cs_main;
|
extern RecursiveMutex cs_main;
|
||||||
extern Mutex g_best_block_mutex;
|
extern GlobalMutex g_best_block_mutex;
|
||||||
extern std::condition_variable g_best_block_cv;
|
extern std::condition_variable g_best_block_cv;
|
||||||
/** Used to notify getblocktemplate RPC of new tips. */
|
/** Used to notify getblocktemplate RPC of new tips. */
|
||||||
extern uint256 g_best_block;
|
extern uint256 g_best_block;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
namespace wallet {
|
namespace wallet {
|
||||||
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
|
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 int g_sqlite_count GUARDED_BY(g_sqlite_mutex) = 0;
|
||||||
|
|
||||||
static void ErrorLogCallback(void* arg, int code, const char* msg)
|
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 GlobalMutex g_loading_wallet_mutex;
|
||||||
static Mutex g_wallet_release_mutex;
|
static GlobalMutex g_wallet_release_mutex;
|
||||||
static std::condition_variable g_wallet_release_cv;
|
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_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
|
||||||
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
|
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
static Mutex g_warnings_mutex;
|
static GlobalMutex g_warnings_mutex;
|
||||||
static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex);
|
static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex);
|
||||||
static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false;
|
static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user