-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Make LOCK, LOCK2, TRY_LOCK work with CWaitableCriticalSection #11640
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
Conversation
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.
Dont you need a similar DeleteLock() call as CCriticalSection?
src/sync.cpp
Outdated
@@ -95,7 +95,7 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, | |||
} | |||
LogPrintf(" %s\n", i.second.ToString()); | |||
} | |||
assert(false); | |||
throw std::logic_error("potential deadlock detected"); |
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.
I believe we would sometimes incorrectly catch this - we still have some generic catch blocks lying around in places :(. I wouldn't mind having a policy of only catching in tests, but until then, I think this should, sadly, remain an assert (unless you want to go to the hassle of adding some #define that is only set in test_bitcoin and compiling this unit differently for it :/).
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.
What exactly is the problem? DEBUG_LOCKORDER isn't enabled unless you are doing a special build anyway.
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.
I presume it wouldn't actually fail in some cases where a lockorder inversion is hit as the exception here would be caught. Precisely because its (essentially) only enabled in travis we should just keep an assert.
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.
I presume it wouldn't actually fail in some cases where a lockorder inversion is hit as the exception here would be caught. Precisely because its (essentially) only enabled in travis we should just keep an assert.
Makes sense, added back an optional abort (on by default), so it will still be guaranteed to fail travis, even in the presence of code that would catch std::logic_error exceptions.
Fails travis due to new lock checking in clang. |
fa4d6b0
to
6e3d96e
Compare
Good catch, added this. |
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.
Awesome, thanks for tackling this.
src/sync.cpp
Outdated
@@ -95,7 +95,8 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, | |||
} | |||
LogPrintf(" %s\n", i.second.ToString()); | |||
} | |||
assert(false); | |||
if (g_debug_lockorder_abort) abort(); |
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.
I believe abort() doesnt give the same useful stderr print as assert(false) does (specifically giving you a line number so you know at least where the crash was).
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.
Addressed in 9050eff. (I added fprintf() before the abort following the pattern from AssertLockHeldInternal/AssertLockNotHeldInternal. I can switch to assert though if you prefer that.)
src/sync.h
Outdated
|
||
/** | ||
* Call abort() if a potential lock order deadlock bug is detected, instead of | ||
* just logging information and throwing a logic_error. Defaults to true. |
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.
something something "Only used for unit testing DEBUG_LOCKORDER."
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.
Noted in 9050eff
src/sync.h
Outdated
{ | ||
public: | ||
~CCriticalSection() { | ||
~LockOrderMixin() { |
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.
Maybe just move this into AnnotatedMixin instead of making everything two classes on top of each other?
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.
Done in 396911e.
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true) | ||
#define LOCK(cs) DebugLock<decltype(cs)> PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__) | ||
#define LOCK2(cs1, cs2) \ | ||
DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \ |
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.
Isnt it considered bad practice to turn a #define into two statements (asking, I dont know, I've just always seen people bend over to avoid it).
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.
It's bad practice generally because it can break usages like if (cond) MY_MACRO();
, and the normal workaround is to wrap the statements in a do {...} while(0)
. But the concern doesn't apply to variable declarations like this.
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.
Fair enough, I just missed why you changed it, but see it now.
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.
src/sync.cpp
Outdated
@@ -95,7 +95,8 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, | |||
} | |||
LogPrintf(" %s\n", i.second.ToString()); | |||
} | |||
assert(false); | |||
if (g_debug_lockorder_abort) abort(); |
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.
Addressed in 9050eff. (I added fprintf() before the abort following the pattern from AssertLockHeldInternal/AssertLockNotHeldInternal. I can switch to assert though if you prefer that.)
src/sync.h
Outdated
|
||
/** | ||
* Call abort() if a potential lock order deadlock bug is detected, instead of | ||
* just logging information and throwing a logic_error. Defaults to true. |
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.
Noted in 9050eff
src/sync.h
Outdated
{ | ||
public: | ||
~CCriticalSection() { | ||
~LockOrderMixin() { |
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.
Done in 396911e.
#define TRY_LOCK(cs, name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true) | ||
#define LOCK(cs) DebugLock<decltype(cs)> PASTE2(criticalblock, __COUNTER__)(cs, #cs, __FILE__, __LINE__) | ||
#define LOCK2(cs1, cs2) \ | ||
DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \ |
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.
It's bad practice generally because it can break usages like if (cond) MY_MACRO();
, and the normal workaround is to wrap the statements in a do {...} while(0)
. But the concern doesn't apply to variable declarations like this.
utACK 674dcfe |
@@ -115,7 +115,7 @@ class WorkQueue | |||
/** Interrupt and exit loops */ | |||
void Interrupt() | |||
{ | |||
std::unique_lock<std::mutex> lock(cs); |
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.
I don't like replacing the standard C++11 lock usage here, and in other places, with the macros. The original code seems easier to understand at least.
Edit: so I get that this helps with the lock dependency checking, but I wonder if there isn't a way that can be done, with keeping the code closer to the original C++11 syntax.
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.
I don't like replacing the standard C++11 lock usage here, and in other places, with the macros
It doesn't matter to me which style is used, but the macros provide lock order checking which @TheBlueMatt seemed to think was important. It would be helpful if there could be a guideline for when to use LOCK macros vs standard locks. Is the guideline that you'd recommend just to avoid macros when locks are used with condition variables, or is it broader or narrower than that?
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.
We're in a not-great state right now where we have automated lock-checking code, but a bunch of places dont bother to use it. In practice most of those places are really limited use and "obviously-correct" (tm), but that only makes it much easier to break in the future, and making exceptions like that are kinda bad. I would strongly prefer we have a consistent syntax across the codebase that uses our lock-checking stuff vs moving back to an ad-hoc world.
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.
I would strongly prefer we have a consistent syntax across the codebase that uses our lock-checking stuff vs moving back to an ad-hoc world.
This PR switches code to consistently use LOCK syntax, which Wladimir objects to in some cases (not clear which ones).
@laanwj @TheBlueMatt, it'd be good to resolve the apparent disagreement about when to use lock_guard
and when to use LOCK
, if possible. But if there can't be any agreement, I could revert the changes in the PR to keep preexisting styles.
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.
I agree the lock checking is important, but I dislike the non-standard macro syntax as it hides what is happening. So I wondered if there is an official way to 'decorate' the C++11 locking primitives, as I doubt we're the only project to do checking like this. If not, this change is fine with me...
Yes, I tend to agree with that point. I think just explicitly using our wrapper classes instead of the LOCK macros would suffice there.
…On March 2, 2018 6:08:09 PM UTC, "Wladimir J. van der Laan" ***@***.***> wrote:
laanwj commented on this pull request.
> @@ -115,7 +115,7 @@ class WorkQueue
/** Interrupt and exit loops */
void Interrupt()
{
- std::unique_lock<std::mutex> lock(cs);
I agree the lock checking is important, but I dislike the non-standard
macro syntax as it hides what is happening. So I wondered if there is
an official way to 'decorate' the C++11 locking primitives, as I doubt
we're the only project to do checking like this. If not, this change is
fine with me...
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#11640 (comment)
|
Matt, maybe I'm misunderstanding, but are you suggesting that I write something like: DebugLock<CWaitableCriticalSection> lock(cs, "cs", __FILE__, __LINE__); to replace current master code: std::unique_lock<std::mutex> lock(cs); instead of current PR code: LOCK(cs); If this is the case, I guess I'd prefer to keep the current PR code, since it just uses one style ( |
Yes, I agree, my comment was somehow out of scope. Let's leave changing the style to something in the future. Or maybe just documenting how the various OS/C++ synchronization primitives map to "bitcoin core style" and vice versa. |
Rebased b6c88a1 -> 41dea6a (pr/dead.9 -> pr/dead.10) due to conflict with #12926 |
The last travis run for this pull request was 87 days ago and is thus outdated. To trigger a fresh travis build, this pull request should be closed and re-opened. |
src/test/sync_tests.cpp
Outdated
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#include "sync.h" | ||
#include "test/test_bitcoin.h" |
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.
Please use #include <sync.h>
instead.
Move AnnotatedMixin closer to where it's used, and after the DEBUG_LOCKORDER function declarations so it can call them.
They should also work with any other mutex type which std::unique_lock supports. There is no change in behavior for current code that calls these macros with CCriticalSection mutexes.
Instead of std::unique_lock.
utACK 9c4dc59 |
…ection 9c4dc59 Use LOCK macros for non-recursive locks (Russell Yanofsky) 1382913 Make LOCK, LOCK2, TRY_LOCK work with CWaitableCriticalSection (Russell Yanofsky) ba1f095 MOVEONLY Move AnnotatedMixin declaration (Russell Yanofsky) 41b88e9 Add unit test for DEBUG_LOCKORDER code (Russell Yanofsky) Pull request description: Make LOCK macros work with non-recursive mutexes, and use wherever possible for better deadlock detection. Also add unit test for DEBUG_LOCKORDER code. Tree-SHA512: 64ef209307f28ecd0813a283f15c6406138c6ffe7f6cbbd084161044db60e2c099a7d0d2edcd1c5e7770a115e9b931b486e86c9a777bdc96d2e8a9f4dc192942
merge bitcoin#11640, bitcoin#11599, bitcoin#16112, bitcoin#16127, bitcoin#18635, bitcoin#19249: thread safety and locking improvements
Make LOCK macros work with non-recursive mutexes, and use wherever possible for better deadlock detection.
Also add unit test for DEBUG_LOCKORDER code.