Merge pull request #4190 from kittywhiskers/tlocks

merge #11640, #11599, #16112, #16127, #18635, #19249: thread safety and locking improvements
This commit is contained in:
UdjinM6 2021-06-11 15:23:35 +03:00 committed by GitHub
commit a8aee57447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 303 additions and 172 deletions

View File

@ -362,7 +362,7 @@ if test "x$enable_werror" = "xyes"; then
AC_MSG_ERROR("enable-werror set but -Werror is not usable")
fi
AX_CHECK_COMPILE_FLAG([-Werror=vla],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=vla"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=thread-safety-analysis],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=thread-safety-analysis"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=thread-safety],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=thread-safety"],,[[$CXXFLAG_WERROR]])
fi
if test "x$CXXFLAGS_overridden" = "xno"; then
@ -371,7 +371,7 @@ if test "x$CXXFLAGS_overridden" = "xno"; then
AX_CHECK_COMPILE_FLAG([-Wformat],[CXXFLAGS="$CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wvla],[CXXFLAGS="$CXXFLAGS -Wvla"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wformat-security],[CXXFLAGS="$CXXFLAGS -Wformat-security"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wthread-safety-analysis],[CXXFLAGS="$CXXFLAGS -Wthread-safety-analysis"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wthread-safety],[CXXFLAGS="$CXXFLAGS -Wthread-safety"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wrange-loop-analysis],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wrange-loop-analysis"],,[[$CXXFLAG_WERROR]])
## Some compilers (gcc) ignore unknown -Wno-* options, but warn about all

View File

@ -101,6 +101,7 @@ BITCOIN_TESTS =\
test/skiplist_tests.cpp \
test/streams_tests.cpp \
test/subsidy_tests.cpp \
test/sync_tests.cpp \
test/timedata_tests.cpp \
test/torcontrol_tests.cpp \
test/transaction_tests.cpp \

View File

@ -106,13 +106,12 @@ ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& c
std::vector<bool> have_txn(txn_available.size());
{
LOCK(pool->cs);
const std::vector<std::pair<uint256, CTxMemPool::txiter> >& vTxHashes = pool->vTxHashes;
for (size_t i = 0; i < vTxHashes.size(); i++) {
uint64_t shortid = cmpctblock.GetShortID(vTxHashes[i].first);
for (size_t i = 0; i < pool->vTxHashes.size(); i++) {
uint64_t shortid = cmpctblock.GetShortID(pool->vTxHashes[i].first);
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
if (idit != shorttxids.end()) {
if (!have_txn[idit->second]) {
txn_available[idit->second] = vTxHashes[i].second->GetSharedTx();
txn_available[idit->second] = pool->vTxHashes[i].second->GetSharedTx();
have_txn[idit->second] = true;
mempool_count++;
} else {

View File

@ -127,7 +127,7 @@ class PartiallyDownloadedBlock {
protected:
std::vector<CTransactionRef> txn_available;
size_t prefilled_count = 0, mempool_count = 0, extra_count = 0;
CTxMemPool* pool;
const CTxMemPool* pool;
public:
CBlockHeader header;
explicit PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {}

View File

@ -74,7 +74,7 @@ class WorkQueue
{
private:
/** Mutex protects entire object */
std::mutex cs;
Mutex cs;
std::condition_variable cond;
std::deque<std::unique_ptr<WorkItem>> queue;
bool running;
@ -93,7 +93,7 @@ public:
/** Enqueue a work item */
bool Enqueue(WorkItem* item)
{
std::unique_lock<std::mutex> lock(cs);
LOCK(cs);
if (queue.size() >= maxDepth) {
return false;
}
@ -107,7 +107,7 @@ public:
while (true) {
std::unique_ptr<WorkItem> i;
{
std::unique_lock<std::mutex> lock(cs);
WAIT_LOCK(cs, lock);
while (running && queue.empty())
cond.wait(lock);
if (!running)
@ -121,7 +121,7 @@ public:
/** Interrupt and exit loops */
void Interrupt()
{
std::unique_lock<std::mutex> lock(cs);
LOCK(cs);
running = false;
cond.notify_all();
}

View File

@ -702,17 +702,17 @@ static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex
}
static bool fHaveGenesis = false;
static CWaitableCriticalSection cs_GenesisWait;
static CConditionVariable condvar_GenesisWait;
static Mutex g_genesis_wait_mutex;
static std::condition_variable g_genesis_wait_cv;
static void BlockNotifyGenesisWait(bool, const CBlockIndex *pBlockIndex)
{
if (pBlockIndex != nullptr) {
{
WaitableLock lock_GenesisWait(cs_GenesisWait);
LOCK(g_genesis_wait_mutex);
fHaveGenesis = true;
}
condvar_GenesisWait.notify_all();
g_genesis_wait_cv.notify_all();
}
}
@ -1089,12 +1089,6 @@ void InitLogging()
{
g_logger->m_print_to_file = !gArgs.IsArgNegated("-debuglogfile");
g_logger->m_file_path = AbsPathForConfigVal(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
// Add newlines to the logfile to distinguish this execution from the last
// one; called before console logging is set up, so this is only sent to
// debug.log.
LogPrintf("\n\n\n\n\n");
g_logger->m_print_to_console = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false));
g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
@ -1709,10 +1703,10 @@ bool AppInitMain()
// and because this needs to happen before any other debug.log printing
g_logger->ShrinkDebugFile();
}
if (!g_logger->OpenDebugLog()) {
return InitError(strprintf("Could not open debug log file %s",
g_logger->m_file_path.string()));
}
}
if (!g_logger->StartLogging()) {
return InitError(strprintf("Could not open debug log file %s",
g_logger->m_file_path.string()));
}
if (!g_logger->m_log_timestamps)
@ -2377,12 +2371,12 @@ bool AppInitMain()
// Wait for genesis block to be processed
{
WaitableLock lock(cs_GenesisWait);
WAIT_LOCK(g_genesis_wait_mutex, lock);
// We previously could hang here if StartShutdown() is called prior to
// ThreadImport getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested()) {
condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500));
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
}
uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait);
}

View File

@ -31,24 +31,38 @@ static int FileWriteStr(const std::string &str, FILE *fp)
return fwrite(str.data(), 1, str.size(), fp);
}
bool BCLog::Logger::OpenDebugLog()
bool BCLog::Logger::StartLogging()
{
std::lock_guard<std::mutex> scoped_lock(m_file_mutex);
StdLockGuard scoped_lock(m_cs);
assert(m_buffering);
assert(m_fileout == nullptr);
assert(!m_file_path.empty());
m_fileout = fsbridge::fopen(m_file_path, "a");
if (!m_fileout) {
return false;
if (m_print_to_file) {
assert(!m_file_path.empty());
m_fileout = fsbridge::fopen(m_file_path, "a");
if (!m_fileout) {
return false;
}
setbuf(m_fileout, nullptr); // unbuffered
// Add newlines to the logfile to distinguish this execution from the
// last one.
FileWriteStr("\n\n\n\n\n", m_fileout);
}
setbuf(m_fileout, nullptr); // unbuffered
// dump buffered messages from before we opened the log
m_buffering = false;
while (!m_msgs_before_open.empty()) {
FileWriteStr(m_msgs_before_open.front(), m_fileout);
const std::string& s = m_msgs_before_open.front();
if (m_print_to_file) FileWriteStr(s, m_fileout);
if (m_print_to_console) fwrite(s.data(), 1, s.size(), stdout);
m_msgs_before_open.pop_front();
}
if (m_print_to_console) fflush(stdout);
return true;
}
@ -247,8 +261,9 @@ std::string BCLog::Logger::LogThreadNameStr(const std::string &str)
return strThreadLogged;
}
void BCLog::Logger::LogPrintStr(const std::string &str)
void BCLog::Logger::LogPrintStr(const std::string& str)
{
StdLockGuard scoped_lock(m_cs);
std::string strThreadLogged = LogThreadNameStr(str);
std::string strTimestamped = LogTimestampStr(strThreadLogged);
@ -257,32 +272,31 @@ void BCLog::Logger::LogPrintStr(const std::string &str)
else
m_started_new_line = false;
if (m_buffering) {
// buffer if we haven't started logging yet
m_msgs_before_open.push_back(strTimestamped);
return;
}
if (m_print_to_console) {
// print to console
fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout);
fflush(stdout);
}
if (m_print_to_file) {
std::lock_guard<std::mutex> scoped_lock(m_file_mutex);
assert(m_fileout != nullptr);
// buffer if we haven't opened the log yet
if (m_fileout == nullptr) {
m_msgs_before_open.push_back(strTimestamped);
}
else
{
// reopen the log file, if requested
if (m_reopen_file) {
m_reopen_file = false;
m_fileout = fsbridge::freopen(m_file_path, "a", m_fileout);
if (!m_fileout) {
return;
}
setbuf(m_fileout, nullptr); // unbuffered
// reopen the log file, if requested
if (m_reopen_file) {
m_reopen_file = false;
FILE* new_fileout = fsbridge::fopen(m_file_path, "a");
if (new_fileout) {
setbuf(new_fileout, nullptr); // unbuffered
fclose(m_fileout);
m_fileout = new_fileout;
}
FileWriteStr(strTimestamped, m_fileout);
}
FileWriteStr(strTimestamped, m_fileout);
}
}

View File

@ -8,6 +8,7 @@
#include <fs.h>
#include <tinyformat.h>
#include <threadsafety.h>
#include <atomic>
#include <cstdint>
@ -82,9 +83,11 @@ namespace BCLog {
class Logger
{
private:
FILE* m_fileout = nullptr;
std::mutex m_file_mutex;
std::list<std::string> m_msgs_before_open;
mutable StdMutex m_cs; // Can not use Mutex from sync.h because in debug mode it would cause a deadlock when a potential deadlock was detected
FILE* m_fileout GUARDED_BY(m_cs) = nullptr;
std::list<std::string> m_msgs_before_open GUARDED_BY(m_cs);
bool m_buffering GUARDED_BY(m_cs) = true; //!< Buffer messages before logging can be started.
/**
* m_started_new_line is a state variable that will suppress printing of
@ -111,12 +114,18 @@ namespace BCLog {
std::atomic<bool> m_reopen_file{false};
/** Send a string to the log output */
void LogPrintStr(const std::string &str);
void LogPrintStr(const std::string& str);
/** Returns whether logs will be written to any output */
bool Enabled() const { return m_print_to_console || m_print_to_file; }
bool Enabled() const
{
StdLockGuard scoped_lock(m_cs);
return m_buffering || m_print_to_console || m_print_to_file;
}
/** Start logging (and flush all buffered messages) */
bool StartLogging();
bool OpenDebugLog();
void ShrinkDebugFile();
uint64_t GetCategoryMask() const { return m_categories.load(); }

View File

@ -2040,7 +2040,7 @@ void CConnman::ThreadSocketHandler()
void CConnman::WakeMessageHandler()
{
{
std::lock_guard<std::mutex> lock(mutexMsgProc);
LOCK(mutexMsgProc);
fMsgProcWake = true;
}
condMsgProc.notify_one();
@ -2862,9 +2862,9 @@ void CConnman::ThreadMessageHandler()
ReleaseNodeVector(vNodesCopy);
std::unique_lock<std::mutex> lock(mutexMsgProc);
WAIT_LOCK(mutexMsgProc, lock);
if (!fMoreWork) {
condMsgProc.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::milliseconds(100), [this] { return fMsgProcWake; });
condMsgProc.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::milliseconds(100), [this]() EXCLUSIVE_LOCKS_REQUIRED(mutexMsgProc) { return fMsgProcWake; });
}
fMsgProcWake = false;
}
@ -3198,7 +3198,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
flagInterruptMsgProc = false;
{
std::unique_lock<std::mutex> lock(mutexMsgProc);
LOCK(mutexMsgProc);
fMsgProcWake = false;
}
@ -3303,7 +3303,7 @@ void CExplicitNetCleanup::callCleanup()
void CConnman::Interrupt()
{
{
std::lock_guard<std::mutex> lock(mutexMsgProc);
LOCK(mutexMsgProc);
flagInterruptMsgProc = true;
}
condMsgProc.notify_all();

View File

@ -617,10 +617,10 @@ private:
const uint64_t nSeed0, nSeed1;
/** flag for waking the message processor. */
bool fMsgProcWake;
bool fMsgProcWake GUARDED_BY(mutexMsgProc);
std::condition_variable condMsgProc;
std::mutex mutexMsgProc;
Mutex mutexMsgProc;
std::atomic<bool> flagInterruptMsgProc;
CThreadInterrupt interruptNet;

View File

@ -12,6 +12,7 @@
#include <wincrypt.h>
#endif
#include <logging.h> // for LogPrint()
#include <sync.h> // for WAIT_LOCK
#include <utiltime.h> // for GetTime()
#include <stdlib.h>
@ -295,7 +296,7 @@ void RandAddSeedSleep()
}
static std::mutex cs_rng_state;
static Mutex cs_rng_state;
static unsigned char rng_state[32] = {0};
static uint64_t rng_counter = 0;
@ -305,7 +306,7 @@ static void AddDataToRng(void* data, size_t len) {
hasher.Write((const unsigned char*)data, len);
unsigned char buf[64];
{
std::unique_lock<std::mutex> lock(cs_rng_state);
WAIT_LOCK(cs_rng_state, lock);
hasher.Write(rng_state, sizeof(rng_state));
hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter));
++rng_counter;
@ -337,7 +338,7 @@ void GetStrongRandBytes(unsigned char* out, int num)
// Combine with and update state
{
std::unique_lock<std::mutex> lock(cs_rng_state);
WAIT_LOCK(cs_rng_state, lock);
hasher.Write(rng_state, sizeof(rng_state));
hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter));
++rng_counter;

View File

@ -53,9 +53,9 @@ struct CUpdatedBlock
int height;
};
static std::mutex cs_blockchange;
static Mutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
@ -247,7 +247,7 @@ static UniValue getbestchainlock(const JSONRPCRequest& request)
void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
{
if(pindex) {
std::lock_guard<std::mutex> lock(cs_blockchange);
LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
@ -278,12 +278,12 @@ static UniValue waitfornewblock(const JSONRPCRequest& request)
CUpdatedBlock block;
{
std::unique_lock<std::mutex> lock(cs_blockchange);
WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]{return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
else
cond_blockchange.wait(lock, [&block]{return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
cond_blockchange.wait(lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
@ -320,11 +320,11 @@ static UniValue waitforblock(const JSONRPCRequest& request)
CUpdatedBlock block;
{
std::unique_lock<std::mutex> lock(cs_blockchange);
WAIT_LOCK(cs_blockchange, lock);
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]{return latestblock.hash == hash || !IsRPCRunning();});
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning();});
else
cond_blockchange.wait(lock, [&hash]{return latestblock.hash == hash || !IsRPCRunning(); });
cond_blockchange.wait(lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning(); });
block = latestblock;
}
@ -363,13 +363,14 @@ static UniValue waitforblockheight(const JSONRPCRequest& request)
CUpdatedBlock block;
{
std::unique_lock<std::mutex> lock(cs_blockchange);
WAIT_LOCK(cs_blockchange, lock);
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]{return latestblock.height >= height || !IsRPCRunning();});
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning();});
else
cond_blockchange.wait(lock, [&height]{return latestblock.height >= height || !IsRPCRunning(); });
cond_blockchange.wait(lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning(); });
block = latestblock;
}
// TODO: Backport g_utxosetscan and associated logic from #16127
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);

View File

@ -503,7 +503,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
{
checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1);
WaitableLock lock(g_best_block_mutex);
WAIT_LOCK(g_best_block_mutex, lock);
while (g_best_block == hashWatchedChain && IsRPCRunning())
{
if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout)

View File

@ -10,9 +10,9 @@
#include <fs.h>
#include <logging.h>
#include <streams.h>
#include <threadsafety.h>
#include <utilstrencodings.h>
#include <mutex>
#include <map>
#include <vector>
#include <memory>
@ -169,8 +169,8 @@ static __attribute__((noinline)) std::vector<uint64_t> GetStackFrames(size_t ski
static BOOL symInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE);
// dbghelp is not thread safe
static std::mutex m;
std::lock_guard<std::mutex> l(m);
static StdMutex m;
StdLockGuard l(m);
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
@ -531,7 +531,7 @@ static void PrintCrashInfo(const crash_info& ci)
}
#ifdef ENABLE_CRASH_HOOKS
static std::mutex g_stacktraces_mutex;
static StdMutex g_stacktraces_mutex;
static std::map<void*, std::shared_ptr<std::vector<uint64_t>>> g_stacktraces;
#if CRASH_HOOKS_WRAPPED_CXX_ABI
@ -594,7 +594,7 @@ extern "C" void* __attribute__((noinline)) WRAPPED_NAME(__cxa_allocate_exception
void* p = __real___cxa_allocate_exception(thrown_size);
std::lock_guard<std::mutex> l(g_stacktraces_mutex);
StdLockGuard l(g_stacktraces_mutex);
g_stacktraces.emplace(p, st);
return p;
}
@ -603,7 +603,7 @@ extern "C" void __attribute__((noinline)) WRAPPED_NAME(__cxa_free_exception)(voi
{
__real___cxa_free_exception(thrown_exception);
std::lock_guard<std::mutex> l(g_stacktraces_mutex);
StdLockGuard l(g_stacktraces_mutex);
g_stacktraces.erase(thrown_exception);
}
@ -667,7 +667,7 @@ static std::shared_ptr<std::vector<uint64_t>> GetExceptionStacktrace(const std::
#ifdef ENABLE_CRASH_HOOKS
void* p = *(void**)&e;
std::lock_guard<std::mutex> l(g_stacktraces_mutex);
StdLockGuard l(g_stacktraces_mutex);
auto it = g_stacktraces.find(p);
if (it == g_stacktraces.end()) {
return nullptr;

View File

@ -104,7 +104,11 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch,
printf("%s\n", strOutput.c_str());
LogPrintf("%s\n", strOutput.c_str());
assert(false);
if (g_debug_lockorder_abort) {
fprintf(stderr, "Assertion failed: detected inconsistent lock order at %s:%i, details in debug log.\n", __FILE__, __LINE__);
abort();
}
throw std::logic_error("potential deadlock detected");
}
static void push_lock(void* c, const CLockLocation& locklocation)
@ -152,7 +156,8 @@ std::string LocksHeld()
return result;
}
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs)
template <typename MutexType>
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
{
for (const std::pair<void*, CLockLocation>& i : g_lockstack)
if (i.first == cs)
@ -160,6 +165,8 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine,
fprintf(stderr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str());
abort();
}
template void AssertLockHeldInternal(const char*, const char*, int, Mutex*);
template void AssertLockHeldInternal(const char*, const char*, int, CCriticalSection*);
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs)
{
@ -193,4 +200,6 @@ void DeleteLock(void* cs)
}
}
bool g_debug_lockorder_abort = true;
#endif /* DEBUG_LOCKORDER */

View File

@ -46,14 +46,44 @@ LEAVE_CRITICAL_SECTION(mutex); // no RAII
// //
///////////////////////////////
#ifdef DEBUG_LOCKORDER
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
void LeaveCritical();
std::string LocksHeld();
template <typename MutexType>
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs);
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
void DeleteLock(void* cs);
/**
* Template mixin that adds -Wthread-safety locking
* annotations to a subset of the mutex API.
* Call abort() if a potential lock order deadlock bug is detected, instead of
* just logging information and throwing a logic_error. Defaults to true, and
* set to false in DEBUG_LOCKORDER unit tests.
*/
extern bool g_debug_lockorder_abort;
#else
void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
void static inline LeaveCritical() {}
template <typename MutexType>
void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
void static inline AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
void static inline DeleteLock(void* cs) {}
#endif
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs)
/**
* Template mixin that adds -Wthread-safety locking annotations and lock order
* checking to a subset of the mutex API.
*/
template <typename PARENT>
class LOCKABLE AnnotatedMixin : public PARENT
{
public:
~AnnotatedMixin() {
DeleteLock((void*)this);
}
void lock() EXCLUSIVE_LOCK_FUNCTION()
{
PARENT::lock();
@ -68,64 +98,42 @@ public:
{
return PARENT::try_lock();
}
};
#ifdef DEBUG_LOCKORDER
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
void LeaveCritical();
std::string LocksHeld();
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) ASSERT_EXCLUSIVE_LOCK(cs);
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
void DeleteLock(void* cs);
#else
void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {}
void static inline LeaveCritical() {}
void static inline AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
void static inline AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
void static inline DeleteLock(void* cs) {}
#endif
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs)
using UniqueLock = std::unique_lock<PARENT>;
#ifdef __clang__
//! For negative capabilities in the Clang Thread Safety Analysis.
//! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in conjunction
//! with the ! operator, to indicate that a mutex should not be held.
const AnnotatedMixin& operator!() const { return *this; }
#endif // __clang__
};
/**
* Wrapped mutex: supports recursive locking, but no waiting
* TODO: We should move away from using the recursive lock by default.
*/
class CCriticalSection : public AnnotatedMixin<std::recursive_mutex>
{
public:
~CCriticalSection() {
DeleteLock((void*)this);
}
};
typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection;
/** Wrapped mutex: supports waiting but not recursive locking */
typedef AnnotatedMixin<std::mutex> CWaitableCriticalSection;
/** Just a typedef for std::condition_variable, can be wrapped later if desired */
typedef std::condition_variable CConditionVariable;
/** Just a typedef for std::unique_lock, can be wrapped later if desired */
typedef std::unique_lock<std::mutex> WaitableLock;
typedef AnnotatedMixin<std::mutex> Mutex;
#ifdef DEBUG_LOCKCONTENTION
void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
#endif
/** Wrapper around std::unique_lock<CCriticalSection> */
class SCOPED_LOCKABLE CCriticalBlock
/** Wrapper around std::unique_lock style lock for Mutex. */
template <typename Mutex, typename Base = typename Mutex::UniqueLock>
class SCOPED_LOCKABLE UniqueLock : public Base
{
private:
std::unique_lock<CCriticalSection> lock;
void Enter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()));
EnterCritical(pszName, pszFile, nLine, (void*)(Base::mutex()));
#ifdef DEBUG_LOCKCONTENTION
if (!lock.try_lock()) {
if (!Base::try_lock()) {
PrintLockContention(pszName, pszFile, nLine);
#endif
lock.lock();
Base::lock();
#ifdef DEBUG_LOCKCONTENTION
}
#endif
@ -133,15 +141,15 @@ private:
bool TryEnter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true);
lock.try_lock();
if (!lock.owns_lock())
EnterCritical(pszName, pszFile, nLine, (void*)(Base::mutex()), true);
Base::try_lock();
if (!Base::owns_lock())
LeaveCritical();
return lock.owns_lock();
return Base::owns_lock();
}
public:
CCriticalBlock(CCriticalSection& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : lock(mutexIn, std::defer_lock)
UniqueLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock)
{
if (fTry)
TryEnter(pszName, pszFile, nLine);
@ -149,35 +157,41 @@ public:
Enter(pszName, pszFile, nLine);
}
CCriticalBlock(CCriticalSection* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
UniqueLock(Mutex* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
{
if (!pmutexIn) return;
lock = std::unique_lock<CCriticalSection>(*pmutexIn, std::defer_lock);
*static_cast<Base*>(this) = Base(*pmutexIn, std::defer_lock);
if (fTry)
TryEnter(pszName, pszFile, nLine);
else
Enter(pszName, pszFile, nLine);
}
~CCriticalBlock() UNLOCK_FUNCTION()
~UniqueLock() UNLOCK_FUNCTION()
{
if (lock.owns_lock())
if (Base::owns_lock())
LeaveCritical();
}
operator bool()
{
return lock.owns_lock();
return Base::owns_lock();
}
};
template<typename MutexArg>
using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
#define PASTE(x, y) x ## y
#define PASTE2(x, y) PASTE(x, y)
#define LOCK(cs) CCriticalBlock PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__)
#define LOCK2(cs1, cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__), criticalblock2(cs2, #cs2, __FILE__, __LINE__)
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true)
#define LOCK(cs) DebugLock<decltype(cs)> PASTE2(criticalblock, __COUNTER__)(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__)
#define ENTER_CRITICAL_SECTION(cs) \
{ \

View File

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <sync.h>
#include <util.h>
#include <utiltime.h>
#include <validation.h>
@ -59,14 +60,14 @@ struct FailingCheck {
};
struct UniqueCheck {
static std::mutex m;
static std::unordered_multiset<size_t> results;
static Mutex m;
static std::unordered_multiset<size_t> results GUARDED_BY(m);
size_t check_id;
UniqueCheck(size_t check_id_in) : check_id(check_id_in){};
UniqueCheck() : check_id(0){};
bool operator()()
{
std::lock_guard<std::mutex> l(m);
LOCK(m);
results.insert(check_id);
return true;
}
@ -129,7 +130,7 @@ struct FrozenCleanupCheck {
std::mutex FrozenCleanupCheck::m{};
std::atomic<uint64_t> FrozenCleanupCheck::nFrozen{0};
std::condition_variable FrozenCleanupCheck::cv{};
std::mutex UniqueCheck::m;
Mutex UniqueCheck::m;
std::unordered_multiset<size_t> UniqueCheck::results;
std::atomic<size_t> FakeCheckCheckCompletion::n_calls{0};
std::atomic<size_t> MemoryCheck::fake_allocated_memory{0};
@ -293,11 +294,15 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck)
control.Add(vChecks);
}
}
bool r = true;
BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT);
for (size_t i = 0; i < COUNT; ++i)
r = r && UniqueCheck::results.count(i) == 1;
BOOST_REQUIRE(r);
{
LOCK(UniqueCheck::m);
bool r = true;
BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT);
for (size_t i = 0; i < COUNT; ++i) {
r = r && UniqueCheck::results.count(i) == 1;
}
BOOST_REQUIRE(r);
}
tg.interrupt_all();
tg.join_all();
}
@ -442,4 +447,3 @@ BOOST_AUTO_TEST_CASE(test_CheckQueueControl_Locks)
}
}
BOOST_AUTO_TEST_SUITE_END()

52
src/test/sync_tests.cpp Normal file
View File

@ -0,0 +1,52 @@
// Copyright (c) 2012-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <sync.h>
#include <test/test_dash.h>
#include <boost/test/unit_test.hpp>
namespace {
template <typename MutexType>
void TestPotentialDeadLockDetected(MutexType& mutex1, MutexType& mutex2)
{
{
LOCK2(mutex1, mutex2);
}
bool error_thrown = false;
try {
LOCK2(mutex2, mutex1);
} catch (const std::logic_error& e) {
BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected");
error_thrown = true;
}
#ifdef DEBUG_LOCKORDER
BOOST_CHECK(error_thrown);
#else
BOOST_CHECK(!error_thrown);
#endif
}
} // namespace
BOOST_FIXTURE_TEST_SUITE(sync_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(potential_deadlock_detected)
{
#ifdef DEBUG_LOCKORDER
bool prev = g_debug_lockorder_abort;
g_debug_lockorder_abort = false;
#endif
CCriticalSection rmutex1, rmutex2;
TestPotentialDeadLockDetected(rmutex1, rmutex2);
Mutex mutex1, mutex2;
TestPotentialDeadLockDetected(mutex1, mutex2);
#ifdef DEBUG_LOCKORDER
g_debug_lockorder_abort = prev;
#endif
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <threadinterrupt.h>
#include <sync.h>
CThreadInterrupt::CThreadInterrupt() : flag(false) {}
@ -20,7 +21,7 @@ void CThreadInterrupt::reset()
void CThreadInterrupt::operator()()
{
{
std::unique_lock<std::mutex> lock(mut);
LOCK(mut);
flag.store(true, std::memory_order_release);
}
cond.notify_all();
@ -28,7 +29,7 @@ void CThreadInterrupt::operator()()
bool CThreadInterrupt::sleep_for(std::chrono::milliseconds rel_time)
{
std::unique_lock<std::mutex> lock(mut);
WAIT_LOCK(mut, lock);
return !cond.wait_for(lock, rel_time, [this]() { return flag.load(std::memory_order_acquire); });
}

View File

@ -5,6 +5,8 @@
#ifndef BITCOIN_THREADINTERRUPT_H
#define BITCOIN_THREADINTERRUPT_H
#include <sync.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
@ -28,7 +30,7 @@ public:
private:
std::condition_variable cond;
std::mutex mut;
Mutex mut;
std::atomic<bool> flag;
};

View File

@ -6,6 +6,8 @@
#ifndef BITCOIN_THREADSAFETY_H
#define BITCOIN_THREADSAFETY_H
#include <mutex>
#ifdef __clang__
// TL;DR Add GUARDED_BY(mutex) to member variables. The others are
// rarely necessary. Ex: int nFoo GUARDED_BY(cs_foo);
@ -54,4 +56,26 @@
#define ASSERT_EXCLUSIVE_LOCK(...)
#endif // __GNUC__
// StdMutex provides an annotated version of std::mutex for us,
// and should only be used when sync.h Mutex/LOCK/etc are not usable.
class LOCKABLE StdMutex : public std::mutex
{
public:
#ifdef __clang__
//! For negative capabilities in the Clang Thread Safety Analysis.
//! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in conjunction
//! with the ! operator, to indicate that a mutex should not be held.
const StdMutex& operator!() const { return *this; }
#endif // __clang__
};
// StdLockGuard provides an annotated version of std::lock_guard for us,
// and should only be used when sync.h Mutex/LOCK/etc are not usable.
class SCOPED_LOCKABLE StdLockGuard : public std::lock_guard<StdMutex>
{
public:
explicit StdLockGuard(StdMutex& cs) EXCLUSIVE_LOCK_FUNCTION(cs) : std::lock_guard<StdMutex>(cs) {}
~StdLockGuard() UNLOCK_FUNCTION() {}
};
#endif // BITCOIN_THREADSAFETY_H

View File

@ -153,18 +153,19 @@ public:
}
instance_of_cinit;
/** Mutex to protect dir_locks. */
static Mutex 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
* is called.
*/
static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks;
/** Mutex to protect dir_locks. */
static std::mutex cs_dir_locks;
static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks);
bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only)
{
std::lock_guard<std::mutex> ulock(cs_dir_locks);
LOCK(cs_dir_locks);
fs::path pathLockFile = directory / lockfile_name;
// If a lock for this directory already exists in the map, don't try to re-lock it
@ -188,7 +189,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b
void ReleaseDirectoryLocks()
{
std::lock_guard<std::mutex> ulock(cs_dir_locks);
LOCK(cs_dir_locks);
dir_locks.clear();
}

View File

@ -229,8 +229,8 @@ BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex;
PrevBlockMap& mapPrevBlockIndex = g_chainstate.mapPrevBlockIndex;
CChain& chainActive = g_chainstate.chainActive;
CBlockIndex *pindexBestHeader = nullptr;
CWaitableCriticalSection g_best_block_mutex;
CConditionVariable g_best_block_cv;
Mutex g_best_block_mutex;
std::condition_variable g_best_block_cv;
uint256 g_best_block;
int nScriptCheckThreads = 0;
std::atomic_bool fImporting(false);
@ -2605,7 +2605,7 @@ void static UpdateTip(const CBlockIndex *pindexNew, const CChainParams& chainPar
mempool.AddTransactionsUpdated(1);
{
WaitableLock lock(g_best_block_mutex);
LOCK(g_best_block_mutex);
g_best_block = pindexNew->GetBlockHash();
g_best_block_cv.notify_all();
}

View File

@ -157,8 +157,8 @@ extern PrevBlockMap& mapPrevBlockIndex;
extern uint64_t nLastBlockTx;
extern uint64_t nLastBlockSize;
extern const std::string strMessageMagic;
extern CWaitableCriticalSection g_best_block_mutex;
extern CConditionVariable g_best_block_cv;
extern Mutex g_best_block_mutex;
extern std::condition_variable g_best_block_cv;
extern uint256 g_best_block;
extern std::atomic_bool fImporting;
extern std::atomic_bool fReindex;

View File

@ -703,7 +703,6 @@ private:
std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
std::atomic<int64_t> m_scanning_start{0};
std::atomic<double> m_scanning_progress{0};
std::mutex mutexScanning;
friend class WalletRescanReserver;
WalletBatch *encrypted_batch = nullptr;
@ -1332,13 +1331,11 @@ public:
bool reserve()
{
assert(!m_could_reserve);
std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
if (m_wallet->fScanningWallet) {
if (m_wallet->fScanningWallet.exchange(true)) {
return false;
}
m_wallet->m_scanning_start = GetTimeMillis();
m_wallet->m_scanning_progress = 0;
m_wallet->fScanningWallet = true;
m_could_reserve = true;
return true;
}
@ -1350,7 +1347,6 @@ public:
~WalletRescanReserver()
{
std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
if (m_could_reserve) {
m_wallet->fScanningWallet = false;
}

View File

@ -14,8 +14,17 @@ class ConfArgsTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
def test_log_buffer(self):
with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0']):
self.start_node(0, extra_args=['-noconnect=0'])
self.stop_node(0)
def run_test(self):
self.stop_node(0)
self.test_log_buffer()
# Remove the -datadir argument so it doesn't override the config file
self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]