Merge #16127: More thread safety annotation coverage

5478d6c099 logging: thread safety annotations (Anthony Towns)
e685ca1992 util/system.cpp: add thread safety annotations for dir_locks (Anthony Towns)
a788789948 test/checkqueue_tests: thread safety annotations (Anthony Towns)
479c5846f7 rpc/blockchain.cpp: thread safety annotations for latestblock (Anthony Towns)
8b5af3d4c1 net: fMsgProcWake use LOCK instead of lock_guard (Anthony Towns)
de7c5f41ab wallet/wallet.h: Remove mutexScanning which was only protecting a single atomic bool (Anthony Towns)
c3cf2f5501 rpc/blockchain.cpp: Remove g_utxosetscan mutex that is only protecting a single atomic variable (Anthony Towns)

Pull request description:

  In a few cases we need to use `std::mutex` rather than the sync.h primitives. But `std::lock_guard<std::mutex>` doesn't include the clang thread safety annotations unless you also use clang's C library, which means you can't indicate when variables should be guarded by `std::mutex` mutexes.

  This adds an annotated version of `std::lock_guard<std::mutex>` to threadsafety.h to fix that, and modifies places where `std::mutex` is used to take advantage of the annotations.

  It's based on top of #16112, and turns the thread safety comments included there into annotations.

  It also changes the RAII classes in wallet/wallet.h and rpc/blockchain.cpp to just use the atomic<bool> flag for synchronisation rather than having a mutex that doesn't actually guard anything as well.

ACKs for top commit:
  MarcoFalke:
    ACK 5478d6c099 🗾
  hebasto:
    re-ACK 5478d6c099, only renamed s/`MutexGuard`/`LockGuard`/, and dropped the commit "test/util_threadnames_tests: add thread safety annotations" since the [previous](https://github.com/bitcoin/bitcoin/pull/16127#pullrequestreview-414184113) review.
  ryanofsky:
    Code review ACK 5478d6c099. Thanks for taking suggestions! Only changes since last review are dropping thread rename test commit d53072ec730d8eec5a5b72f7e65a54b141e62b19 and renaming mutex guard to lock guard

Tree-SHA512: 7b00d31f6f2b5a222ec69431eb810a74abf0542db3a65d1bbad54e354c40df2857ec89c00b4a5e466c81ba223267ca95f3f98d5fbc1a1d052a2c3a7d2209790a
This commit is contained in:
MarcoFalke
2020-05-27 19:31:25 -04:00
9 changed files with 58 additions and 47 deletions

View File

@@ -41,7 +41,7 @@ static int FileWriteStr(const std::string &str, FILE *fp)
bool BCLog::Logger::StartLogging() bool BCLog::Logger::StartLogging()
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
assert(m_buffering); assert(m_buffering);
assert(m_fileout == nullptr); assert(m_fileout == nullptr);
@@ -80,7 +80,7 @@ bool BCLog::Logger::StartLogging()
void BCLog::Logger::DisconnectTestLogger() void BCLog::Logger::DisconnectTestLogger()
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
m_buffering = true; m_buffering = true;
if (m_fileout != nullptr) fclose(m_fileout); if (m_fileout != nullptr) fclose(m_fileout);
m_fileout = nullptr; m_fileout = nullptr;
@@ -246,7 +246,7 @@ namespace BCLog {
void BCLog::Logger::LogPrintStr(const std::string& str) void BCLog::Logger::LogPrintStr(const std::string& str)
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
std::string str_prefixed = LogEscapeMessage(str); std::string str_prefixed = LogEscapeMessage(str);
if (m_log_threadnames && m_started_new_line) { if (m_log_threadnames && m_started_new_line) {

View File

@@ -8,6 +8,7 @@
#include <fs.h> #include <fs.h>
#include <tinyformat.h> #include <tinyformat.h>
#include <threadsafety.h>
#include <util/string.h> #include <util/string.h>
#include <atomic> #include <atomic>
@@ -62,9 +63,10 @@ namespace BCLog {
{ {
private: private:
mutable std::mutex m_cs; // Can not use Mutex from sync.h because in debug mode it would cause a deadlock when a potential deadlock was detected mutable std::mutex 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 = nullptr; // GUARDED_BY(m_cs)
std::list<std::string> m_msgs_before_open; // GUARDED_BY(m_cs) FILE* m_fileout GUARDED_BY(m_cs) = nullptr;
bool m_buffering{true}; //!< Buffer messages before logging can be started. GUARDED_BY(m_cs) 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
@@ -79,7 +81,7 @@ namespace BCLog {
std::string LogTimestampStr(const std::string& str); std::string LogTimestampStr(const std::string& str);
/** Slots that connect to the print signal */ /** Slots that connect to the print signal */
std::list<std::function<void(const std::string&)>> m_print_callbacks /* GUARDED_BY(m_cs) */ {}; std::list<std::function<void(const std::string&)>> m_print_callbacks GUARDED_BY(m_cs) {};
public: public:
bool m_print_to_console = false; bool m_print_to_console = false;
@@ -98,14 +100,14 @@ namespace BCLog {
/** Returns whether logs will be written to any output */ /** Returns whether logs will be written to any output */
bool Enabled() const bool Enabled() const
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty(); return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty();
} }
/** Connect a slot to the print signal and return the connection */ /** Connect a slot to the print signal and return the connection */
std::list<std::function<void(const std::string&)>>::iterator PushBackCallback(std::function<void(const std::string&)> fun) std::list<std::function<void(const std::string&)>>::iterator PushBackCallback(std::function<void(const std::string&)> fun)
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
m_print_callbacks.push_back(std::move(fun)); m_print_callbacks.push_back(std::move(fun));
return --m_print_callbacks.end(); return --m_print_callbacks.end();
} }
@@ -113,7 +115,7 @@ namespace BCLog {
/** Delete a connection */ /** Delete a connection */
void DeleteCallback(std::list<std::function<void(const std::string&)>>::iterator it) void DeleteCallback(std::list<std::function<void(const std::string&)>>::iterator it)
{ {
std::lock_guard<std::mutex> scoped_lock(m_cs); LockGuard scoped_lock(m_cs);
m_print_callbacks.erase(it); m_print_callbacks.erase(it);
} }

View File

@@ -1455,7 +1455,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();
@@ -2058,7 +2058,7 @@ void CConnman::ThreadMessageHandler()
WAIT_LOCK(mutexMsgProc, lock); 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;
} }
@@ -2366,7 +2366,7 @@ static CNetCleanup instance_of_cnetcleanup;
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

@@ -454,7 +454,7 @@ 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;
Mutex mutexMsgProc; Mutex mutexMsgProc;

View File

@@ -52,7 +52,7 @@ struct CUpdatedBlock
static 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);
NodeContext& EnsureNodeContext(const util::Ref& context) NodeContext& EnsureNodeContext(const util::Ref& context)
{ {
@@ -223,7 +223,7 @@ static UniValue getbestblockhash(const JSONRPCRequest& request)
void RPCNotifyBlockChange(const CBlockIndex* pindex) void RPCNotifyBlockChange(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;
} }
@@ -258,9 +258,9 @@ static UniValue waitfornewblock(const JSONRPCRequest& request)
WAIT_LOCK(cs_blockchange, lock); 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);
@@ -300,9 +300,9 @@ static UniValue waitforblock(const JSONRPCRequest& request)
{ {
WAIT_LOCK(cs_blockchange, lock); 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;
} }
@@ -344,9 +344,9 @@ static UniValue waitforblockheight(const JSONRPCRequest& request)
{ {
WAIT_LOCK(cs_blockchange, lock); 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;
} }
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
@@ -1995,7 +1995,6 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
} }
/** RAII object to prevent concurrency issue when scanning the txout set */ /** RAII object to prevent concurrency issue when scanning the txout set */
static std::mutex g_utxosetscan;
static std::atomic<int> g_scan_progress; static std::atomic<int> g_scan_progress;
static std::atomic<bool> g_scan_in_progress; static std::atomic<bool> g_scan_in_progress;
static std::atomic<bool> g_should_abort_scan; static std::atomic<bool> g_should_abort_scan;
@@ -2008,18 +2007,15 @@ public:
bool reserve() { bool reserve() {
CHECK_NONFATAL(!m_could_reserve); CHECK_NONFATAL(!m_could_reserve);
std::lock_guard<std::mutex> lock(g_utxosetscan); if (g_scan_in_progress.exchange(true)) {
if (g_scan_in_progress) {
return false; return false;
} }
g_scan_in_progress = true;
m_could_reserve = true; m_could_reserve = true;
return true; return true;
} }
~CoinsViewScanReserver() { ~CoinsViewScanReserver() {
if (m_could_reserve) { if (m_could_reserve) {
std::lock_guard<std::mutex> lock(g_utxosetscan);
g_scan_in_progress = false; g_scan_in_progress = false;
} }
} }

View File

@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <checkqueue.h> #include <checkqueue.h>
#include <sync.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <util/memory.h> #include <util/memory.h>
#include <util/system.h> #include <util/system.h>
@@ -57,14 +58,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;
} }
@@ -127,7 +128,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};
@@ -290,11 +291,15 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck)
control.Add(vChecks); control.Add(vChecks);
} }
} }
bool r = true; {
BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT); LOCK(UniqueCheck::m);
for (size_t i = 0; i < COUNT; ++i) bool r = true;
r = r && UniqueCheck::results.count(i) == 1; BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT);
BOOST_REQUIRE(r); for (size_t i = 0; i < COUNT; ++i) {
r = r && UniqueCheck::results.count(i) == 1;
}
BOOST_REQUIRE(r);
}
tg.interrupt_all(); tg.interrupt_all();
tg.join_all(); tg.join_all();
} }

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,13 @@
#define ASSERT_EXCLUSIVE_LOCK(...) #define ASSERT_EXCLUSIVE_LOCK(...)
#endif // __GNUC__ #endif // __GNUC__
// LockGuard provides an annotated version of lock_guard for us
// should only be used when sync.h Mutex/LOCK/etc aren't usable
class SCOPED_LOCKABLE LockGuard : public std::lock_guard<std::mutex>
{
public:
explicit LockGuard(std::mutex& cs) EXCLUSIVE_LOCK_FUNCTION(cs) : std::lock_guard<std::mutex>(cs) { }
~LockGuard() UNLOCK_FUNCTION() {};
};
#endif // BITCOIN_THREADSAFETY_H #endif // BITCOIN_THREADSAFETY_H

View File

@@ -3,6 +3,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/system.h> #include <util/system.h>
#include <chainparamsbase.h> #include <chainparamsbase.h>
@@ -75,18 +76,18 @@ const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
ArgsManager gArgs; ArgsManager gArgs;
/** 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
@@ -110,13 +111,13 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b
void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name) void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name)
{ {
std::lock_guard<std::mutex> lock(cs_dir_locks); LOCK(cs_dir_locks);
dir_locks.erase((directory / lockfile_name).string()); dir_locks.erase((directory / lockfile_name).string());
} }
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

@@ -631,7 +631,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;
//! the current wallet version: clients below this version are not able to load the wallet //! the current wallet version: clients below this version are not able to load the wallet
@@ -1287,13 +1286,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;
} }
@@ -1305,7 +1302,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;
} }