8000 Replace LockAssertion with AssertLockHeld, remove LockAssertion by hebasto · Pull Request #19979 · bitcoin/bitcoin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Replace LockAssertion with AssertLockHeld, remove LockAssertion #19979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 23, 2020

Conversation

hebasto
Copy link
Member
@hebasto hebasto commented Sep 19, 2020

This PR replaces LockAssertion with AssertLockHeld, and removes LockAssertion.

This PR is compared with alternatives in https://github.com/bitcoin-core/bitcoin-devwiki/wiki/AssertLockHeld-PRs

@hebasto
8000 Copy link
Member Author
hebasto commented Sep 19, 2020

Copy link
Contributor
@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Code review ACK 774face

Love this approach! This solves the main problem I'm concerned about, which is having multiple nearly identical assert implementations. The minor downsides to this approach are:

  • May be less efficient because invoking a callback through a std::function can require a heap allocation and indirect call through a function pointer instead of an inlined call or call to a fixed address.

  • This pattern for bypassing thread safety analysis--annotating a lambda with a EXCLUSIVE_LOCKS_REQUIRED and assigning the lambda to a std::function which ignores the annotation--isn't as nice as directly telling the analysis a lock is held using an ASSERT_EXCLUSIVE_LOCK function. An ASSERT_EXCLUSIVE_LOCK call makes sure unprovable assertions look different than proven annotation, and ensures they are accompanied by runtime check.

Because of these limitations, it's possible in the future we may want to provide an assert implementation annotated with ASSERT_EXCLUSIVE_LOCK. But this seems like a good step to eventually getting there (again).

@maflcko
Copy link
Member
maflcko commented Sep 19, 2020

As mentioned in previous prs, clang may have accidental bugs or intentional limitations when checking for locks held at compile time. So removing the run-time checks seems dangerous, see also #19865 (comment) .

For example, the following diff compiles, but is obviously wrong:

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index b256d719a6..b6006427a7 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1336,6 +1336,8 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_
     std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true);
     const CNetMsgMaker msgMaker(PROTOCOL_VERSION);
 
+    CConnman::NodeFn fun;
+    {
     LOCK(cs_main);
 
     static int nHighestFastAnnounce = 0;
@@ -1353,8 +1355,7 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_
         most_recent_compact_block = pcmpctblock;
         fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled;
     }
-
-    m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+    fun =[this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
         // TODO: Avoid the repeated-serialization here
         if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
             return;
@@ -1370,7 +1371,10 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_
             m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
             state.pindexBestHeaderSent = pindex;
         }
-    });
+    };
+    }// release cs_main
+
+    m_connman.ForEachNode(fun);
 }
 
 /**

@ryanofsky
Copy link
Contributor

So removing the run-time checks seems dangerous

This moves the run time checks outside of the lambdas. I don't think it's fair to say it removes them. PR could be tweaked to move the checks inside the lambdas instead of outside if you prefer that.

It is true in general that the approach of annotating a lambda and calling it through a std::function so the annotation will not be enforced is less safe than using an ASSERT_EXCLUSIVE_LOCK assert, because an ASSERT_EXCLUSIVE_LOCK assert pairs any unproven declaration that a mutex is locked together with a runtime check. With this approach in this PR, you are responsible for doing the check separately yourself.

@DrahtBot
Copy link
Contributor
DrahtBot commented Sep 19, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@hebasto
Copy link
Member Author
hebasto commented Sep 19, 2020

Updated 774face -> 0bd1184 (pr19979.01 -> pr19979.02, diff):

As mentioned in previous prs, clang may have accidental bugs or intentional limitations when checking for locks held at compile time. So removing the run-time checks seems dangerous, see also #19865 (comment)

@aaronpuchert
Copy link
aaronpuchert commented Sep 19, 2020
  • May be less efficient because invoking a callback through a std::function can require a heap allocation and indirect call through a function pointer instead of an inlined call or call to a fixed address.

If that's a concern, use something like LLVM's function_ref. Still an indirect call, but without heap allocation.

  • This pattern for bypassing thread safety analysis--annotating a lambda with a EXCLUSIVE_LOCKS_REQUIRED and assigning the lambda to a std::function which ignores the annotation--isn't as nice as directly telling the analysis a lock is held using an ASSERT_EXCLUSIVE_LOCK function. An ASSERT_EXCLUSIVE_LOCK call makes sure unprovable assertions look different than proven annotation, and ensures they are accompanied by runtime check.

Right, casting away the annotations is not nice, the assertion is probably the best you can do here.

Since the annotations live on declarations and not on types, we can't warn about these casts because there is no way to fix that warning. You can't annotate the function pointer type.

Edit: thinking about it again, we could warn and for example require an explicit cast or NO_THREAD_SAFETY_ANALYSIS. But it would be an uphill battle.

@hebasto
Copy link
Member Author
hebasto commented Sep 19, 2020

FWIW, the same approach has already been used in the project code base in 6a72f26 from #16426 by @ariard.

@maflcko
Copy link
Member
maflcko commented Sep 20, 2020

ACK 0bd1184

Copy link
Contributor
@vasild vasild left a comment

Choose a reason for hiding this comment

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

ACK 0bd1184

Looks good - this leaves just one lock assertion method - AssertLockHeld() and uses some hacktweak to deal with the few exceptional cases where lambda callbacks are involved. I think those few cases fall within the clang's limitations and we shouldn't extort the compiler to provide maximum amount of warnings in those cases. In #19929 I tagged them with NO_THREAD_SAFETY_ANALYSIS.

Thanks!

@maflcko maflcko requested a review from ajtowns September 22, 2020 19:22
@ajtowns
Copy link
Contributor
ajtowns commented Sep 23, 2020

Since the annotations live on decla 8000 rations and not on types, we can't warn about these casts because there is no way to fix that warning. You can't annotate the function pointer type.

This may provide some justification for the "always do an AssertLockHeld(cs) in the functions with EXCLUSIVE_LOCKS_REQUIRED(cs)" rule -- any function that gets called via a std::function cast or a function pointer generally won't have the lock validity checked at compile time, and (I think) there's no way to prevent functions from being called that way, so having the runtime check is a good backup. I guess there's no easy way to add a linter to check that we don't have functions with EXCLUSIVE_LOCKS_REQUIRED that lack an AssertLockHeld?

If there were a way to prevent an EXCLUSIVE_LOCKS_REQUIRED function from being called via std::function etc and having its annotation discarded, then I think we'd want to enable that, and switch to an ASSERT_LOCK_HELD approach for the lambdas, since then we could guarantee all the EXCLUSIVE_LOCKS_REQUIRED things are actually checked at compile time.

But assuming we can't do that today, then this approach is no worse at catching bugs than other alternatives, and simpler to deal with than any of them (since it's just "always use EXCLUSIVE_LOCKS_REQUIRED, always use AssertLockHeld") as far as I can see.

I don't think the description ("eliminates from the code base all cases when Clang Thread Safety Analysis cannot determine if a lock is held") is quite accurate though -- clang still isn't able to determine the lock is held in these lambdas.

ACK 0bd1184

@maflcko
Copy link
Member
maflcko commented Sep 23, 2020

I don't think the description ("eliminates from the code base all cases when Clang Thread Safety Analysis cannot determine if a lock is held") is quite accurate though -- clang still isn't able to determine the lock is held in these lambdas.

What about "Replace LockAssertion with AssertLockHeld, remove LockAssertion" as OP?

Other than that, I think this can go in.

@hebasto hebasto changed the title Use proper TSA attributes (attempt two) Replace LockAssertion with AssertLockHeld, remove LockAssertion Sep 23, 2020
@hebasto
Copy link
Member Author
hebasto commented Sep 23, 2020

@MarcoFalke

I don't think the description ("eliminates from the code base all cases when Clang Thread Safety Analysis cannot determine if a lock is held") is quite accurate though -- clang still isn't able to determine the lock is held in these lambdas.

What about "Replace LockAssertion with AssertLockHeld, remove LockAssertion" as OP?

Done.

@maflcko maflcko merged commit 8235dca into bitcoin:master Sep 23, 2020
@hebasto hebasto deleted the 200919-tsa branch September 23, 2020 14:40
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Sep 23, 2020
…e LockAssertion

0bd1184 Remove unused LockAssertion struct (Hennadii Stepanov)
ab2a442 Replace LockAssertion with a proper thread safety annotations (Hennadii Stepanov)
73f71e1 refactor: Use explicit function type instead of template (Hennadii Stepanov)

Pull request description:

  This PR replaces `LockAssertion` with `AssertLockHeld`, and removes `LockAssertion`.

  This PR is compared with alternatives in https://github.com/bitcoin-core/bitcoin-devwiki/wiki/AssertLockHeld-PRs

ACKs for top commit:
  MarcoFalke:
    ACK 0bd1184
  ajtowns:
    ACK 0bd1184
  vasild:
    ACK 0bd1184

Tree-SHA512: ef7780dd689faf0bb479fdb97c49bc652e2dd10c148234bb95502dfbb676442d8565ee37864d923ca21a25f9dc2a335bf46ee82c095e387b59a664ab05c0ae41
@aaronpuchert
Copy link

Since the annotations live on declarations and not on types, we can't warn about these casts because there is no way to fix that warning. You can't annotate the function pointer type.

This may provide some justification for the "always do an AssertLockHeld(cs) in the functions with EXCLUSIVE_LOCKS_REQUIRED(cs)" rule -- any function that gets called via a std::function cast or a function pointer generally won't have the lock validity checked at compile time, and (I think) there's no way to prevent functions from being called that way, so having the runtime check is a good backup. I guess there's no easy way to add a linter to check that we don't have functions with EXCLUSIVE_LOCKS_REQUIRED that lack an AssertLockHeld?

We can warn, but we need a good idea on a way out. Since NO_THREAD_SAFETY_ANALYSIS also disables checking for arguments, users could have a function

template<typename F>
F thread_safety_cast(F&& f) NO_THREAD_SAFETY_ANALYSIS
{
    return std::forward<F>(f);
}

that would allow casting away annotations. But I haven't thought this through yet.

@@ -793,25 +793,6 @@ bool ChainstateManager::ProcessNewBlock(...)
}
```

- When Clang Thread Safety Analysis is unable to determine if a mutex is locked, use `LockAssertion` class instances:
Copy link
Contributor

Choose a reason for hiding this comment

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

In commit "Remove unused LockAssertion struct" (0bd1184)

Instead of removing this section, maybe it would have been better to update. The example could be updated with EXCLUSIVE_LOCKS_REQUIRED to be consistent with the code. And the text could be something like "When Clang Thread Safety Analysis is unable to determine if a mutex is locked, it is recommended to disable the analysis or to call the code through a function annotated with EXCLUSIVE_LOCKS_REQUIRED, using an indirect call so the requirement is not checked."

Copy link
Member

Choose a reason for hiding this comment

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

Imo using NO_THREAD_SAFETY_ANALYSIS (or thread_safety_cast above) or EXCLUSIVE_LOCKS_REQUIRED on the lambda is equivalent, as the resulting binary, as well as any compile time errors/warnings should be the same.

As there is no observable difference (except in the written code itself), is there a reason to mention it in the dev notes?

Copy link
Contributor

Choose a reason for hiding this comment

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

Imo, they're both not ideal. EXCLUSIVE_LOCKS_REQUIRED is preferable to NO_THREAD_SAFETY_ANALYSIS because it makes which mutex is assumed to be held explicit, and it doesn't disable checking for other mutexes. You might need to assume cs_main is held somewhere, and still want errors if you forget LOCK(cs_wallet) in the same function.

If it was useful before to have a section in developer notes advising how to deal with TSA errors, it's probably more useful now that the current practice is to add annotations that look like they might be enforced, but aren't enforced. But I don't know if the section was useful before it was removed. Just wanted to suggest here that it could be updated if it was useful.

Copy link
Member

Choose a reason for hiding this comment

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

You might need to assume cs_main is held somewhere, and still want errors if you forget LOCK(cs_wallet) in the same function.

Good point. Yeah, I agree the new "style" could be mentioned in the docs.

deadalnix pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Sep 22, 2021
Summary:
PR description:
> This PR replaces LockAssertion with AssertLockHeld, and removes LockAssertion.
>
> This PR is compared with alternatives in https://github.com/bitcoin-core/bitcoin-devwiki/wiki/AssertLockHeld-PRs

This is a backport of [[bitcoin/bitcoin#19979 | core#19979]] [1/3]
bitcoin/bitcoin@73f71e1

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D10171
deadalnix pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Sep 22, 2021
Summary:
This is a backport of [[bitcoin/bitcoin#19979 | core#19979]] [2/3]
bitcoin/bitcoin@ab2a442

This partially revert D10161
Depends on D10171

Test Plan:
With TSAN:
`ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D10172
deadalnix pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Sep 22, 2021
Summary:
This is a backport of [[bitcoin/bitcoin#19979 | core#19979]] [3/3]
bitcoin/bitcoin@0bd1184

Depends on D10172

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D10173
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
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.

7 participants
0