Skip to content

Conversation

vasild
Copy link
Contributor

@vasild vasild commented Feb 2, 2022

#24103 added annotations to
denote that the callers of CChainState::ActivateBestChain() and
CChainState::InvalidateBlock() must not own m_chainstate_mutex at
the time of the call.

Replace the added LOCKS_EXCLUDED() with a stronger
EXCLUSIVE_LOCKS_REQUIRED(), see
https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#negative for the
difference between both.

@hebasto
Copy link
Member

hebasto commented Feb 2, 2022

I'm a bit vague about using negated annotation for global mutexes, in particular EXCLUSIVE_LOCKS_REQUIRED(!::cs_main).

Btw, which clang versions were tested?

@vasild
Copy link
Contributor Author

vasild commented Feb 2, 2022

I tested with clang 14.

src/validation.h Outdated
@@ -639,7 +640,8 @@ class CChainState
*/
bool ActivateBestChain(
BlockValidationState& state,
std::shared_ptr<const CBlock> pblock = nullptr) LOCKS_EXCLUDED(m_chainstate_mutex, cs_main);
std::shared_ptr<const CBlock> pblock = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !m_chainstate_mutex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tend toward NACK using negative annotations with global mutexes, especially cs_main. I presume LOCKS_EXCLUDED(::cs_main) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) doesn't work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with using negative annotations with global mutexes? I don't see how the scope of the mutex is relevant, but maybe I miss something since @hebasto also wasn't excited about it. Can you elaborate?

I presume LOCKS_EXCLUDED(::cs_main) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) doesn't work?

It works. Updated the PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate?

See the commit that you ACKed: 9ac8f6d

@hebasto
Copy link
Member

hebasto commented Feb 2, 2022

Shouldn't -Wthread-safety-negative be added to configure.am and/or CI jobs?

@hebasto
Copy link
Member

hebasto commented Feb 2, 2022

Shouldn't -Wthread-safety-negative be added to configure.am and/or CI jobs?

No. It shouldn't. It gets too noisy with warnings about cs_main.

Sorry.

@hebasto
Copy link
Member

hebasto commented Feb 2, 2022

Restarted some CI jobs that failed due to connectivity issues.

bitcoin#24103 added annotations to
denote that the callers of `CChainState::ActivateBestChain()` and
`CChainState::InvalidateBlock()` must not own `m_chainstate_mutex` at
the time of the call.

Replace the added `LOCKS_EXCLUDED()` with a stronger
`EXCLUSIVE_LOCKS_REQUIRED()`, see
https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#negative for the
difference between both.
@vasild vasild force-pushed the followup_to_24103 branch from 5730f73 to 99de806 Compare February 2, 2022 13:04
@hebasto
Copy link
Member

hebasto commented Feb 2, 2022

Concept ACK.

@vasild
Copy link
Contributor Author

vasild commented Feb 2, 2022

5730f7327f...99de8068cd: lower the scope to just m_chainstate_mutex and don't change anything about cs_main which was a kind of scope creep.

I think the previous version was better (5730f73) as it provided stronger guarantees, but there is some frowning against using EXCLUSIVE_LOCKS_REQUIRED(!cs_main) (see comments above). Since the purpose of this PR is to do m_chainstate_mutex, leave cs_main alone.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested 99de806 with Ubuntu clang version 12.0.0-3ubuntu1~20.04.4.

  1. The following testing patch:
diff --git a/src/validation.cpp b/src/validation.cpp
index c12dc9e8b..2f8e8c0c9 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2845,6 +2845,12 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
     }
 }
 
+void CChainState::foo()
+{
+    BlockValidationState state;
+    InvalidateBlock(state, nullptr);
+}
+
 bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
 {
     AssertLockNotHeld(m_chainstate_mutex);
diff --git a/src/validation.h b/src/validation.h
index fdfd29d1f..7bdc8aae3 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -755,6 +755,8 @@ private:
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     friend ChainstateManager;
+
+    void foo();
 };
 
 /**

results in:

$ make
...
validation.cpp:2851:5: warning: calling function 'InvalidateBlock' requires negative capability '!m_chainstate_mutex' [-Wthread-safety-analysis]
    InvalidateBlock(state, nullptr);
    ^
1 warning generated
...
  1. And the following testing patch:
diff --git a/src/validation.cpp b/src/validation.cpp
index c12dc9e8b..84f99312b 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2845,6 +2845,12 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
     }
 }
 
+void CChainState::foo()
+{
+    BlockValidationState state;
+    InvalidateBlock(state, nullptr);
+}
+
 bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
 {
     AssertLockNotHeld(m_chainstate_mutex);
@@ -2861,6 +2867,8 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr
     // we use m_chainstate_mutex to enforce mutual exclusion so that only one caller may execute this function at a time
     LOCK(m_chainstate_mutex);
 
+    foo();
+
     CBlockIndex *pindexMostWork = nullptr;
     CBlockIndex *pindexNewTip = nullptr;
     int nStopAtHeight = gArgs.GetIntArg("-stopatheight", DEFAULT_STOPATHEIGHT);
diff --git a/src/validation.h b/src/validation.h
index fdfd29d1f..39102ba43 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -755,6 +755,8 @@ private:
         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
 
     friend ChainstateManager;
+
+    void foo() EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex);
 };
 
 /**

causes the following warning:

$ make
...
validation.cpp:2870:5: warning: cannot call function 'foo' while mutex 'm_chainstate_mutex' is held [-Wthread-safety-analysis]
    foo();
    ^
1 warning generated.
...

The similar patch being applied to the master branch does not fire any thread-safety warnings.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 99de806.

Some note for the future:

  1. clang-format-diff.py fails to handle multiple multi-line TS annotations
  2. Should we recommend to use negative capability forms of TS annotations for member mutexes in new code?

@vasild
Copy link
Contributor Author

vasild commented Feb 2, 2022

2. Should we recommend to use negative capability forms of TS annotations for member mutexes in new code?

If a method does LOCK(m) I think it is a good practice to include EXCLUSIVE_LOCKS_REQUIRED(!m) in its declaration (assuming non-recursive mutex).

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 99de806. Tested with Debian clang version 13.0.1. Reproduced hebasto's results. Verified that LoadExternalBlockFile() needs the annotation added here.

Should the following run-time thread safety assertions be added?

suggested run-time assertions

diff --git a/src/validation.cpp b/src/validation.cpp
index c12dc9e8b6..109b1f95ce 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2848,6 +2848,7 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
 bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
 {
     AssertLockNotHeld(m_chainstate_mutex);
+    AssertLockNotHeld(::cs_main);
 
@@ -2949,6 +2950,8 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr
 
 bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
 {
+    AssertLockNotHeld(m_chainstate_mutex);
+    AssertLockNotHeld(::cs_main);
     {
         LOCK(cs_main);
@@ -2979,6 +2982,7 @@ bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex
 bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
 {
     AssertLockNotHeld(m_chainstate_mutex);
+    AssertLockNotHeld(::cs_main);
 
@@ -4089,6 +4093,8 @@ bool CChainState::LoadGenesisBlock()
 
 void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp)
 {
+    AssertLockNotHeld(m_chainstate_mutex);

@maflcko
Copy link
Member

maflcko commented Feb 8, 2022

(edited OP and review comments)

@maflcko maflcko merged commit 280a777 into bitcoin:master Feb 8, 2022
@jonatack
Copy link
Member

jonatack commented Feb 8, 2022

(edited OP and review comments)

Thanks @MarcoFalke, left an @-reference in my ACK.

Added the thread safety assertions in my comment in #24235 (review) to #24177.

@vasild vasild deleted the followup_to_24103 branch February 9, 2022 11:20
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Oct 12, 2022
Summary:
```
 So apparently there is no recursion involved, so the m_cs_chainstate can be a non-recursive mutex.
```

Backport of [[bitcoin/bitcoin#24103 | core#24103]].
Backport of [[bitcoin/bitcoin#24235 | core#24235]].

Test Plan:
With Debug and Clang:
  ninja all check
Run the TSAN build.

Reviewers: #bitcoin_abc, PiRK

Reviewed By: #bitcoin_abc, PiRK

Differential Revision: https://reviews.bitcoinabc.org/D12227
@bitcoin bitcoin locked and limited conversation to collaborators Feb 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants