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") AC_MSG_ERROR("enable-werror set but -Werror is not usable")
fi fi
AX_CHECK_COMPILE_FLAG([-Werror=vla],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=vla"],,[[$CXXFLAG_WERROR]]) 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 fi
if test "x$CXXFLAGS_overridden" = "xno"; then 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([-Wformat],[CXXFLAGS="$CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wvla],[CXXFLAGS="$CXXFLAGS -Wvla"],,[[$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([-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]]) 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 ## Some compilers (gcc) ignore unknown -Wno-* options, but warn about all

View File

@ -101,6 +101,7 @@ BITCOIN_TESTS =\
test/skiplist_tests.cpp \ test/skiplist_tests.cpp \
test/streams_tests.cpp \ test/streams_tests.cpp \
test/subsidy_tests.cpp \ test/subsidy_tests.cpp \
test/sync_tests.cpp \
test/timedata_tests.cpp \ test/timedata_tests.cpp \
test/torcontrol_tests.cpp \ test/torcontrol_tests.cpp \
test/transaction_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()); std::vector<bool> have_txn(txn_available.size());
{ {
LOCK(pool->cs); LOCK(pool->cs);
const std::vector<std::pair<uint256, CTxMemPool::txiter> >& vTxHashes = pool->vTxHashes; for (size_t i = 0; i < pool->vTxHashes.size(); i++) {
for (size_t i = 0; i < vTxHashes.size(); i++) { uint64_t shortid = cmpctblock.GetShortID(pool->vTxHashes[i].first);
uint64_t shortid = cmpctblock.GetShortID(vTxHashes[i].first);
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid); std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
if (idit != shorttxids.end()) { if (idit != shorttxids.end()) {
if (!have_txn[idit->second]) { 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; have_txn[idit->second] = true;
mempool_count++; mempool_count++;
} else { } else {

View File

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

View File

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

View File

@ -702,17 +702,17 @@ static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex
} }
static bool fHaveGenesis = false; static bool fHaveGenesis = false;
static CWaitableCriticalSection cs_GenesisWait; static Mutex g_genesis_wait_mutex;
static CConditionVariable condvar_GenesisWait; static std::condition_variable g_genesis_wait_cv;
static void BlockNotifyGenesisWait(bool, const CBlockIndex *pBlockIndex) static void BlockNotifyGenesisWait(bool, const CBlockIndex *pBlockIndex)
{ {
if (pBlockIndex != nullptr) { if (pBlockIndex != nullptr) {
{ {
WaitableLock lock_GenesisWait(cs_GenesisWait); LOCK(g_genesis_wait_mutex);
fHaveGenesis = true; 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_print_to_file = !gArgs.IsArgNegated("-debuglogfile");
g_logger->m_file_path = AbsPathForConfigVal(gArgs.GetArg("-debuglogfile", DEFAULT_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_print_to_console = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false));
g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
@ -1709,11 +1703,11 @@ bool AppInitMain()
// and because this needs to happen before any other debug.log printing // and because this needs to happen before any other debug.log printing
g_logger->ShrinkDebugFile(); g_logger->ShrinkDebugFile();
} }
if (!g_logger->OpenDebugLog()) { }
if (!g_logger->StartLogging()) {
return InitError(strprintf("Could not open debug log file %s", return InitError(strprintf("Could not open debug log file %s",
g_logger->m_file_path.string())); g_logger->m_file_path.string()));
} }
}
if (!g_logger->m_log_timestamps) if (!g_logger->m_log_timestamps)
LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime())); LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
@ -2377,12 +2371,12 @@ bool AppInitMain()
// Wait for genesis block to be processed // 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 // We previously could hang here if StartShutdown() is called prior to
// ThreadImport getting started, so instead we just wait on a timer to // ThreadImport getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly. // check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested()) { 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); 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); 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_fileout == nullptr);
assert(!m_file_path.empty());
if (m_print_to_file) {
assert(!m_file_path.empty());
m_fileout = fsbridge::fopen(m_file_path, "a"); m_fileout = fsbridge::fopen(m_file_path, "a");
if (!m_fileout) { if (!m_fileout) {
return false; return false;
} }
setbuf(m_fileout, nullptr); // unbuffered 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);
}
// dump buffered messages from before we opened the log // dump buffered messages from before we opened the log
m_buffering = false;
while (!m_msgs_before_open.empty()) { 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(); m_msgs_before_open.pop_front();
} }
if (m_print_to_console) fflush(stdout);
return true; return true;
} }
@ -249,6 +263,7 @@ std::string BCLog::Logger::LogThreadNameStr(const std::string &str)
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 strThreadLogged = LogThreadNameStr(str);
std::string strTimestamped = LogTimestampStr(strThreadLogged); std::string strTimestamped = LogTimestampStr(strThreadLogged);
@ -257,34 +272,33 @@ void BCLog::Logger::LogPrintStr(const std::string &str)
else else
m_started_new_line = false; 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) { if (m_print_to_console) {
// print to console // print to console
fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout);
fflush(stdout); fflush(stdout);
} }
if (m_print_to_file) { 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 // reopen the log file, if requested
if (m_reopen_file) { if (m_reopen_file) {
m_reopen_file = false; m_reopen_file = false;
m_fileout = fsbridge::freopen(m_file_path, "a", m_fileout); FILE* new_fileout = fsbridge::fopen(m_file_path, "a");
if (!m_fileout) { if (new_fileout) {
return; setbuf(new_fileout, nullptr); // unbuffered
fclose(m_fileout);
m_fileout = new_fileout;
} }
setbuf(m_fileout, nullptr); // unbuffered
} }
FileWriteStr(strTimestamped, m_fileout); FileWriteStr(strTimestamped, m_fileout);
} }
} }
}
void BCLog::Logger::ShrinkDebugFile() void BCLog::Logger::ShrinkDebugFile()
{ {

View File

@ -8,6 +8,7 @@
#include <fs.h> #include <fs.h>
#include <tinyformat.h> #include <tinyformat.h>
#include <threadsafety.h>
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
@ -82,9 +83,11 @@ namespace BCLog {
class Logger class Logger
{ {
private: private:
FILE* m_fileout = nullptr; 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
std::mutex m_file_mutex;
std::list<std::string> m_msgs_before_open; 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 * m_started_new_line is a state variable that will suppress printing of
@ -114,9 +117,15 @@ namespace BCLog {
void LogPrintStr(const std::string& str); void LogPrintStr(const std::string& str);
/** Returns whether logs will be written to any output */ /** 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(); void ShrinkDebugFile();
uint64_t GetCategoryMask() const { return m_categories.load(); } uint64_t GetCategoryMask() const { return m_categories.load(); }

View File

@ -2040,7 +2040,7 @@ void CConnman::ThreadSocketHandler()
void CConnman::WakeMessageHandler() void CConnman::WakeMessageHandler()
{ {
{ {
std::lock_guard<std::mutex> lock(mutexMsgProc); LOCK(mutexMsgProc);
fMsgProcWake = true; fMsgProcWake = true;
} }
condMsgProc.notify_one(); condMsgProc.notify_one();
@ -2862,9 +2862,9 @@ void CConnman::ThreadMessageHandler()
ReleaseNodeVector(vNodesCopy); ReleaseNodeVector(vNodesCopy);
std::unique_lock<std::mutex> lock(mutexMsgProc); WAIT_LOCK(mutexMsgProc, lock);
if (!fMoreWork) { 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; fMsgProcWake = false;
} }
@ -3198,7 +3198,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
flagInterruptMsgProc = false; flagInterruptMsgProc = false;
{ {
std::unique_lock<std::mutex> lock(mutexMsgProc); LOCK(mutexMsgProc);
fMsgProcWake = false; fMsgProcWake = false;
} }
@ -3303,7 +3303,7 @@ void CExplicitNetCleanup::callCleanup()
void CConnman::Interrupt() void CConnman::Interrupt()
{ {
{ {
std::lock_guard<std::mutex> lock(mutexMsgProc); LOCK(mutexMsgProc);
flagInterruptMsgProc = true; flagInterruptMsgProc = true;
} }
condMsgProc.notify_all(); condMsgProc.notify_all();

View File

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

View File

@ -12,6 +12,7 @@
#include <wincrypt.h> #include <wincrypt.h>
#endif #endif
#include <logging.h> // for LogPrint() #include <logging.h> // for LogPrint()
#include <sync.h> // for WAIT_LOCK
#include <utiltime.h> // for GetTime() #include <utiltime.h> // for GetTime()
#include <stdlib.h> #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 unsigned char rng_state[32] = {0};
static uint64_t rng_counter = 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); hasher.Write((const unsigned char*)data, len);
unsigned char buf[64]; 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(rng_state, sizeof(rng_state));
hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter));
++rng_counter; ++rng_counter;
@ -337,7 +338,7 @@ void GetStrongRandBytes(unsigned char* out, int num)
// Combine with and update state // 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(rng_state, sizeof(rng_state));
hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter));
++rng_counter; ++rng_counter;

View File

@ -53,9 +53,9 @@ struct CUpdatedBlock
int height; int height;
}; };
static std::mutex cs_blockchange; static Mutex cs_blockchange;
static std::condition_variable cond_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); 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) void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
{ {
if(pindex) { if(pindex) {
std::lock_guard<std::mutex> lock(cs_blockchange); LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash(); latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight; latestblock.height = pindex->nHeight;
} }
@ -278,12 +278,12 @@ static UniValue waitfornewblock(const JSONRPCRequest& request)
CUpdatedBlock block; CUpdatedBlock block;
{ {
std::unique_lock<std::mutex> lock(cs_blockchange); WAIT_LOCK(cs_blockchange, lock);
block = latestblock; block = latestblock;
if(timeout) 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 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; block = latestblock;
} }
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
@ -320,11 +320,11 @@ static UniValue waitforblock(const JSONRPCRequest& request)
CUpdatedBlock block; CUpdatedBlock block;
{ {
std::unique_lock<std::mutex> lock(cs_blockchange); WAIT_LOCK(cs_blockchange, lock);
if(timeout) 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 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; block = latestblock;
} }
@ -363,13 +363,14 @@ static UniValue waitforblockheight(const JSONRPCRequest& request)
CUpdatedBlock block; CUpdatedBlock block;
{ {
std::unique_lock<std::mutex> lock(cs_blockchange); WAIT_LOCK(cs_blockchange, lock);
if(timeout) 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 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; block = latestblock;
} }
// TODO: Backport g_utxosetscan and associated logic from #16127
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height); 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); 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()) while (g_best_block == hashWatchedChain && IsRPCRunning())
{ {
if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout) if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout)

View File

@ -10,9 +10,9 @@
#include <fs.h> #include <fs.h>
#include <logging.h> #include <logging.h>
#include <streams.h> #include <streams.h>
#include <threadsafety.h>
#include <utilstrencodings.h> #include <utilstrencodings.h>
#include <mutex>
#include <map> #include <map>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -169,8 +169,8 @@ static __attribute__((noinline)) std::vector<uint64_t> GetStackFrames(size_t ski
static BOOL symInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE); static BOOL symInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE);
// dbghelp is not thread safe // dbghelp is not thread safe
static std::mutex m; static StdMutex m;
std::lock_guard<std::mutex> l(m); StdLockGuard l(m);
HANDLE process = GetCurrentProcess(); HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread(); HANDLE thread = GetCurrentThread();
@ -531,7 +531,7 @@ static void PrintCrashInfo(const crash_info& ci)
} }
#ifdef ENABLE_CRASH_HOOKS #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; static std::map<void*, std::shared_ptr<std::vector<uint64_t>>> g_stacktraces;
#if CRASH_HOOKS_WRAPPED_CXX_ABI #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); 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); g_stacktraces.emplace(p, st);
return p; return p;
} }
@ -603,7 +603,7 @@ extern "C" void __attribute__((noinline)) WRAPPED_NAME(__cxa_free_exception)(voi
{ {
__real___cxa_free_exception(thrown_exception); __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); g_stacktraces.erase(thrown_exception);
} }
@ -667,7 +667,7 @@ static std::shared_ptr<std::vector<uint64_t>> GetExceptionStacktrace(const std::
#ifdef ENABLE_CRASH_HOOKS #ifdef ENABLE_CRASH_HOOKS
void* p = *(void**)&e; void* p = *(void**)&e;
std::lock_guard<std::mutex> l(g_stacktraces_mutex); StdLockGuard l(g_stacktraces_mutex);
auto it = g_stacktraces.find(p); auto it = g_stacktraces.find(p);
if (it == g_stacktraces.end()) { if (it == g_stacktraces.end()) {
return nullptr; 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()); printf("%s\n", strOutput.c_str());
LogPrintf("%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) static void push_lock(void* c, const CLockLocation& locklocation)
@ -152,7 +156,8 @@ std::string LocksHeld()
return result; 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) for (const std::pair<void*, CLockLocation>& i : g_lockstack)
if (i.first == cs) 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()); fprintf(stderr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str());
abort(); 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) 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 */ #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 * Call abort() if a potential lock order deadlock bug is detected, instead of
* annotations to a subset of the mutex API. * 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> template <typename PARENT>
class LOCKABLE AnnotatedMixin : public PARENT class LOCKABLE AnnotatedMixin : public PARENT
{ {
public: public:
~AnnotatedMixin() {
DeleteLock((void*)this);
}
void lock() EXCLUSIVE_LOCK_FUNCTION() void lock() EXCLUSIVE_LOCK_FUNCTION()
{ {
PARENT::lock(); PARENT::lock();
@ -68,64 +98,42 @@ public:
{ {
return PARENT::try_lock(); return PARENT::try_lock();
} }
};
#ifdef DEBUG_LOCKORDER using UniqueLock = std::unique_lock<PARENT>;
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false); #ifdef __clang__
void LeaveCritical(); //! For negative capabilities in the Clang Thread Safety Analysis.
std::string LocksHeld(); //! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in conjunction
void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) ASSERT_EXCLUSIVE_LOCK(cs); //! with the ! operator, to indicate that a mutex should not be held.
void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs); const AnnotatedMixin& operator!() const { return *this; }
void DeleteLock(void* cs); #endif // __clang__
#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)
/** /**
* Wrapped mutex: supports recursive locking, but no waiting * Wrapped mutex: supports recursive locking, but no waiting
* TODO: We should move away from using the recursive lock by default. * TODO: We should move away from using the recursive lock by default.
*/ */
class CCriticalSection : public AnnotatedMixin<std::recursive_mutex> typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection;
{
public:
~CCriticalSection() {
DeleteLock((void*)this);
}
};
/** Wrapped mutex: supports waiting but not recursive locking */ /** Wrapped mutex: supports waiting but not recursive locking */
typedef AnnotatedMixin<std::mutex> CWaitableCriticalSection; typedef AnnotatedMixin<std::mutex> Mutex;
/** 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;
#ifdef DEBUG_LOCKCONTENTION #ifdef DEBUG_LOCKCONTENTION
void PrintLockContention(const char* pszName, const char* pszFile, int nLine); void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
#endif #endif
/** Wrapper around std::unique_lock<CCriticalSection> */ /** Wrapper around std::unique_lock style lock for Mutex. */
class SCOPED_LOCKABLE CCriticalBlock template <typename Mutex, typename Base = typename Mutex::UniqueLock>
class SCOPED_LOCKABLE UniqueLock : public Base
{ {
private: private:
std::unique_lock<CCriticalSection> lock;
void Enter(const char* pszName, const char* pszFile, int nLine) 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 #ifdef DEBUG_LOCKCONTENTION
if (!lock.try_lock()) { if (!Base::try_lock()) {
PrintLockContention(pszName, pszFile, nLine); PrintLockContention(pszName, pszFile, nLine);
#endif #endif
lock.lock(); Base::lock();
#ifdef DEBUG_LOCKCONTENTION #ifdef DEBUG_LOCKCONTENTION
} }
#endif #endif
@ -133,15 +141,15 @@ private:
bool TryEnter(const char* pszName, const char* pszFile, int nLine) bool TryEnter(const char* pszName, const char* pszFile, int nLine)
{ {
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true); EnterCritical(pszName, pszFile, nLine, (void*)(Base::mutex()), true);
lock.try_lock(); Base::try_lock();
if (!lock.owns_lock()) if (!Base::owns_lock())
LeaveCritical(); LeaveCritical();
return lock.owns_lock(); return Base::owns_lock();
} }
public: 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) if (fTry)
TryEnter(pszName, pszFile, nLine); TryEnter(pszName, pszFile, nLine);
@ -149,35 +157,41 @@ public:
Enter(pszName, pszFile, nLine); 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; if (!pmutexIn) return;
lock = std::unique_lock<CCriticalSection>(*pmutexIn, std::defer_lock); *static_cast<Base*>(this) = Base(*pmutexIn, std::defer_lock);
if (fTry) if (fTry)
TryEnter(pszName, pszFile, nLine); TryEnter(pszName, pszFile, nLine);
else else
Enter(pszName, pszFile, nLine); Enter(pszName, pszFile, nLine);
} }
~CCriticalBlock() UNLOCK_FUNCTION() ~UniqueLock() UNLOCK_FUNCTION()
{ {
if (lock.owns_lock()) if (Base::owns_lock())
LeaveCritical(); LeaveCritical();
} }
operator bool() 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 PASTE(x, y) x ## y
#define PASTE2(x, y) PASTE(x, y) #define PASTE2(x, y) PASTE(x, y)
#define LOCK(cs) CCriticalBlock PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__) #define LOCK(cs) DebugLock<decltype(cs)> PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__)
#define LOCK2(cs1, cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__), criticalblock2(cs2, #cs2, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true) 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) \ #define ENTER_CRITICAL_SECTION(cs) \
{ \ { \

View File

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <sync.h>
#include <util.h> #include <util.h>
#include <utiltime.h> #include <utiltime.h>
#include <validation.h> #include <validation.h>
@ -59,14 +60,14 @@ struct FailingCheck {
}; };
struct UniqueCheck { struct UniqueCheck {
static std::mutex m; static Mutex m;
static std::unordered_multiset<size_t> results; static std::unordered_multiset<size_t> results GUARDED_BY(m);
size_t check_id; size_t check_id;
UniqueCheck(size_t check_id_in) : check_id(check_id_in){}; UniqueCheck(size_t check_id_in) : check_id(check_id_in){};
UniqueCheck() : check_id(0){}; UniqueCheck() : check_id(0){};
bool operator()() bool operator()()
{ {
std::lock_guard<std::mutex> l(m); LOCK(m);
results.insert(check_id); results.insert(check_id);
return true; return true;
} }
@ -129,7 +130,7 @@ struct FrozenCleanupCheck {
std::mutex FrozenCleanupCheck::m{}; std::mutex FrozenCleanupCheck::m{};
std::atomic<uint64_t> FrozenCleanupCheck::nFrozen{0}; std::atomic<uint64_t> FrozenCleanupCheck::nFrozen{0};
std::condition_variable FrozenCleanupCheck::cv{}; std::condition_variable FrozenCleanupCheck::cv{};
std::mutex UniqueCheck::m; Mutex UniqueCheck::m;
std::unordered_multiset<size_t> UniqueCheck::results; std::unordered_multiset<size_t> UniqueCheck::results;
std::atomic<size_t> FakeCheckCheckCompletion::n_calls{0}; std::atomic<size_t> FakeCheckCheckCompletion::n_calls{0};
std::atomic<size_t> MemoryCheck::fake_allocated_memory{0}; std::atomic<size_t> MemoryCheck::fake_allocated_memory{0};
@ -293,11 +294,15 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck)
control.Add(vChecks); control.Add(vChecks);
} }
} }
{
LOCK(UniqueCheck::m);
bool r = true; bool r = true;
BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT); BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT);
for (size_t i = 0; i < COUNT; ++i) for (size_t i = 0; i < COUNT; ++i) {
r = r && UniqueCheck::results.count(i) == 1; r = r && UniqueCheck::results.count(i) == 1;
}
BOOST_REQUIRE(r); BOOST_REQUIRE(r);
}
tg.interrupt_all(); tg.interrupt_all();
tg.join_all(); tg.join_all();
} }
@ -442,4 +447,3 @@ BOOST_AUTO_TEST_CASE(test_CheckQueueControl_Locks)
} }
} }
BOOST_AUTO_TEST_SUITE_END() 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. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <threadinterrupt.h> #include <threadinterrupt.h>
#include <sync.h>
CThreadInterrupt::CThreadInterrupt() : flag(false) {} CThreadInterrupt::CThreadInterrupt() : flag(false) {}
@ -20,7 +21,7 @@ void CThreadInterrupt::reset()
void CThreadInterrupt::operator()() void CThreadInterrupt::operator()()
{ {
{ {
std::unique_lock<std::mutex> lock(mut); LOCK(mut);
flag.store(true, std::memory_order_release); flag.store(true, std::memory_order_release);
} }
cond.notify_all(); cond.notify_all();
@ -28,7 +29,7 @@ void CThreadInterrupt::operator()()
bool CThreadInterrupt::sleep_for(std::chrono::milliseconds rel_time) 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); }); 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 #ifndef BITCOIN_THREADINTERRUPT_H
#define BITCOIN_THREADINTERRUPT_H #define BITCOIN_THREADINTERRUPT_H
#include <sync.h>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
@ -28,7 +30,7 @@ public:
private: private:
std::condition_variable cond; std::condition_variable cond;
std::mutex mut; Mutex mut;
std::atomic<bool> flag; std::atomic<bool> flag;
}; };

View File

@ -6,6 +6,8 @@
#ifndef BITCOIN_THREADSAFETY_H #ifndef BITCOIN_THREADSAFETY_H
#define BITCOIN_THREADSAFETY_H #define BITCOIN_THREADSAFETY_H
#include <mutex>
#ifdef __clang__ #ifdef __clang__
// TL;DR Add GUARDED_BY(mutex) to member variables. The others are // TL;DR Add GUARDED_BY(mutex) to member variables. The others are
// rarely necessary. Ex: int nFoo GUARDED_BY(cs_foo); // rarely necessary. Ex: int nFoo GUARDED_BY(cs_foo);
@ -54,4 +56,26 @@
#define ASSERT_EXCLUSIVE_LOCK(...) #define ASSERT_EXCLUSIVE_LOCK(...)
#endif // __GNUC__ #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 #endif // BITCOIN_THREADSAFETY_H

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,17 @@ class ConfArgsTest(BitcoinTestFramework):
self.setup_clean_chain = True self.setup_clean_chain = True
self.num_nodes = 1 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): def run_test(self):
self.stop_node(0) self.stop_node(0)
self.test_log_buffer()
# Remove the -datadir argument so it doesn't override the config file # 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")] self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]