Yeah, I had hoped for the same :(
The problem here (as usual with these annotations) is aliasing.
As a simplified example:
0CCheckQueueControl<CScriptCheck> control(m_chainman.GetCheckQueue());
1CCheckQueueControl<CScriptCheck> control2(m_chainman.GetCheckQueue());
Here, clang has no idea that &control.m_lock.m_control_mutex
== &control2.m_lock.m_control_mutex
.
The only alternative, really, would be something like:
0diff --git a/src/checkqueue.h b/src/checkqueue.h
1index ade793ab392..5249e040d24 100644
2--- a/src/checkqueue.h
3+++ b/src/checkqueue.h
4@@ -188,6 +188,11 @@ public:
5 }
6 }
7
8+ UniqueLock<Mutex> TryLock()
9+ {
10+ return {IN_PLACE_TRY_LOCK(m_control_mutex)};
11+ }
12+
13 ~CCheckQueue()
14 {
15 WITH_LOCK(m_mutex, m_request_stop = true);
16@@ -209,13 +214,14 @@ class CCheckQueueControl
17 {
18 private:
19 CCheckQueue<T, R>& pqueue;
20+ UniqueLock<Mutex> m_lock;
21 bool fDone;
22
23 public:
24 CCheckQueueControl() = delete;
25 CCheckQueueControl(const CCheckQueueControl&) = delete;
26 CCheckQueueControl& operator=(const CCheckQueueControl&) = delete;
27- explicit CCheckQueueControl(CCheckQueue<T>& pqueueIn) : pqueue(pqueueIn), fDone(false)
28+ explicit CCheckQueueControl(CCheckQueue<T>& pqueueIn, UniqueLock<Mutex>&& lock) : pqueue(pqueueIn), m_lock(std::move(lock)), fDone(false)
29 {
30 ENTER_CRITICAL_SECTION(pqueue.m_control_mutex);
31 }
32diff --git a/src/sync.h b/src/sync.h
33index b22956ef1ab..db4fd977cee 100644
34--- a/src/sync.h
35+++ b/src/sync.h
36@@ -193,6 +193,8 @@ public:
37 Enter(pszName, pszFile, nLine);
38 }
39
40+ UniqueLock(UniqueLock&&) = default;
41+
42 ~UniqueLock() UNLOCK_FUNCTION()
43 {
44 if (Base::owns_lock())
45@@ -260,6 +262,7 @@ inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNE
46 UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
47 #define TRY_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
48 #define WAIT_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
49+#define IN_PLACE_TRY_LOCK(cs) MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true
50
51 #define ENTER_CRITICAL_SECTION(cs) \
52 { \
53diff --git a/src/validation.cpp b/src/validation.cpp
54index 3fa7afe3090..d690191b38e 100644
55--- a/src/validation.cpp
56+++ b/src/validation.cpp
57@@ -2612,7 +2612,10 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
58 // doesn't invalidate pointers into the vector, and keep txsdata in scope
59 // for as long as `control`.
60 std::optional<CCheckQueueControl<CScriptCheck>> control;
61- if (fScriptChecks && parallel_script_checks) control.emplace(m_chainman.GetCheckQueue());
62+ auto& queue = m_chainman.GetCheckQueue();
63+ auto queue_lock = queue.TryLock();
64+ assert(queue_lock);
65+ if (fScriptChecks && parallel_script_checks) control.emplace(m_chainman.GetCheckQueue(), std::move(queue_lock));
66
67 std::vector<PrecomputedTransactionData> txsdata(block.vtx.size());
Unfortunately, try_lock
is allowed to fail spuriously, so that’s not safe :(