-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Strengthen thread safety assertions #24931
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
Strengthen thread safety assertions #24931
Conversation
This hopefully avoids bugs like #24157 (comment) and gets most of the benefits of |
Concept ACK |
8f76596
to
b6914e6
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, 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. |
Concept ACK |
Concept ACK. I had a similar perspective view since f8213c0 :) Maybe split off changes related to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the goal of this change to allow building with -Wthread-safety-negative
? Still seeing many warnings on this branch with ./configure CC=clang CXX=clang++ CXXFLAGS='-Wthread-safety -Wthread-safety-negative' && make clean && make
. A regular debug build on this branch is clean, however.
Could be verified with $ ./configure CC=clang CXX=clang++ CXXFLAGS='-Wthread-safety -Wthread-safety-negative' $ make clean $ make 2>&1 | grep m_tx_relay_mutex
How about |
3cc9538
to
cd6ae69
Compare
Rebased onto #25175. The |
bitcoin/src/net_processing.cpp Lines 294 to 297 in cd6ae69
does not emit a warning about missed |
Though, on the other hand, However, tagging the |
-BEGIN VERIFY SCRIPT- sed -i -E -e '/^([a-z]+ )?Mutex [a-z]/ s/Mutex/GlobalMutex/' $(git grep -lE '^([a-z]+ )?Mutex [a-z]') -END VERIFY SCRIPT-
cd6ae69
to
ce893c0
Compare
Updated with negative assertions propogated when WITH_LOCK is used, which caught the above case and another. Couldn't see a way to avoid having WITH_LOCK expand cs twice though. |
… by propagating some negative capabilities 2b3373c refactor: Propagate negative `!m_tx_relay_mutex` capability (Hennadii Stepanov) 5a6e3c1 refactor: Propagate negative `!m_most_recent_block_mutex` capability (Hennadii Stepanov) Pull request description: This PR is a follow-up for bitcoin/bitcoin#22778 and bitcoin/bitcoin#24062, and it seems [required](bitcoin/bitcoin#24931 (comment)) for bitcoin/bitcoin#24931. See details in the commit messages. ACKs for top commit: ajtowns: ACK 2b3373c w0xlt: ACK bitcoin/bitcoin@2b3373c Tree-SHA512: 8a4bb9641af8d79824ec12e2d6bfce0e09524094b298a2edcdb2ab115fbaa21215b9c97a6b059f8aa023551071fd5798eca66ab8d262a3f97246a91d960850d0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK ce893c0
* | ||
* See: https://github.com/bitcoin/bitcoin/pull/20272#issuecomment-720755781 | ||
*/ | ||
class GlobalMutex : public Mutex { }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, clang-format-diff.py
complains:
class GlobalMutex : public Mutex { }; | |
class GlobalMutex : public Mutex | |
{ | |
}; |
#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }() | ||
#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While playing with this I observed that it is possible to circumvent the thread safety warnings mechanism by wrapping the lock call in a lambda and calling this lambda immediately. That circumvention only works for mutexes that are a member of a class:
Mutex mutex_global;
// Commented annotations would silence the warnings, clang 15
class C
{
public:
Mutex mutex_member;
void f1() // EXCLUSIVE_LOCKS_REQUIRED(!mutex_member)
{
// warning: calling function 'MaybeCheckNotHeld' requires negative capability '!mutex_member' [-Wthread-safety-analysis]
LOCK(mutex_member);
}
void f2()
{
[&]() { LOCK(mutex_member); }(); // no need for annotations
}
void f3() // EXCLUSIVE_LOCKS_REQUIRED(!mutex_global)
{
// warning: calling function 'MaybeCheckNotHeld' requires negative capability '!mutex_global' [-Wthread-safety-analysis]
LOCK(mutex_global);
}
void f4() // EXCLUSIVE_LOCKS_REQUIRED(!mutex_global)
{
// warning: calling function 'MaybeCheckNotHeld' requires negative capability '!mutex_global' [-Wthread-safety-analysis]
[&]() /* EXCLUSIVE_LOCKS_REQUIRED(!mutex_global) */ { LOCK(mutex_global); }();
}
};
Looks like the construct used for WITH_LOCK()
in this PR relies on the f2()
behavior from the example above. Is that behavior documented somewhere in the clang docs? If not, then it may be wiser to not depend on undocumented behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can avoid the thread safety warnings in a lot of ways if you create aliases for either the locks or the variables being guarded; eg:
int i GUARDED_BY(mutex_member) = 0;
void f5()
{
int* i_alias = &i;
*i_alias = 5;
}
(Using [&]
here is creating an implicit alias for all the member variables, though clang does a better job of analysing past the aliases there than in the above example...)
I think the best long term approach is to do away with any uses of both GlobalMutex
and RecursiveMutex
, at which point we can annotate the lambda in WITH_LOCK
with EXCLUSIVE_LOCKS_REQUIRED(!cs)
and we're done. Having it work in the meantime allows us to be more confident that we don't have any potential recursive cases when switching a RecursiveMutex
to a regular Mutex
, so seems like it aids that goal to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this explains why f2()
needs no annotations. I guess at some point the compiler may become smarter, detect the reference and issue a warning.
I think the best long term approach is to do away with any uses of both
GlobalMutex
andRecursiveMutex
, at which point we can annotate the lambda inWITH_LOCK
withEXCLUSIVE_LOCKS_REQUIRED(!cs)
and we're done.
I agree. Do you envision that we do not have mutexes in global scope, or we have ones that are of type Mutex
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anytime you currently have:
GlobalMutex g_mut;
int g_importantnum GUARDED_BY(g_mut) = 0;
std::string g_importantstr GUARDED_BY(g_mut);
void bump_importantint(int offset)
{
LOCK(g_mut);
g_importantnum += offset;
}
you can rewrite it as
class important_stuff
{
private:
Mutex m_mut;
int m_num GUARDED_BY(m_mut) = 0;
std::string m_str GUARDED_BY(m_mut);
public:
void bump(int offset) EXCLUSIVE_LOCKS_REQUIRED(!m_mut);
};
important_stuff g_important;
or similar (depending on whether you can move all the code that uses the mutex into the class, or if you have to expose the mutex), which doesn't require very large changes to the code, but does make for a better fit with the thread safety analysis. Might also make it easy to later change g_important
to not need to be a global too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you envision that we do not have mutexes in global scope, or we have ones that are of type
Mutex
I guess your reply above favors the "we do not have mutexes in global scope" case, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
g_important.m_mut
is still technically in global scope. Just works better with clang's thread safety logic
Maybe drop the merged commits from the broken GitHub web view?
|
Nvm, the merge commit will have the correct commit list. review ACK ce893c0 🐦 Show signatureSignature:
|
This changes
LOCK(mutex)
for non-global, non-recursive mutexes to be annotated with the negative capability for the mutex it refers to, to prevent . clang applies negative capabilities recursively, so this helps avoid forgetting to annotate functions.This can't reasonably be used for globals, because clang would require every function to be annotated with
EXCLUSIVE_LOCKS_REQUIRED(!g_mutex)
for each global mutex; so this introduces a trivialGlobalMutex
subclass ofMutex
, and reduces the annotations for bothGlobalMutex
toLOCKS_EXCLUDED
which only catches trivial errors (eg (LOCK(x); LOCK(x);
).