-
Notifications
You must be signed in to change notification settings - Fork 37.7k
log, sync: change lock contention from preprocessor directive to log category #22736
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
log, sync: change lock contention from preprocessor directive to log category #22736
Conversation
src/sync.h
Outdated
@@ -138,14 +137,10 @@ class SCOPED_LOCKABLE UniqueLock : public Base | |||
void Enter(const char* pszName, const char* pszFile, int nLine) | |||
{ | |||
EnterCritical(pszName, pszFile, nLine, Base::mutex()); | |||
#ifdef DEBUG_LOCKCONTENTION | |||
if (!Base::try_lock()) { |
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 am somewhat worried that the extra calls to Base::try_lock
cause performance overhead in the non-lock contention debugging case. It's unnecessary. We might want to gate that too on the log category being enabled.
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.
Thanks, wondering that as well (or alternatively, leave the directives in place and just improve the logging experience when DEBUG_LOCKCONTENTION
is defined).
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.
Implemented your suggestion (thanks!)
(Behind the gate it could now be just LogPrintf
if anyone prefers.)
6f6abbe
to
ccd73de
Compare
Moving this out of draft now, ready for review. |
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. |
Code review ACK ccd73de |
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.
cr ACK ccd73de
Concept ACK Nice idea. I've used something similar in my local tree when analysing lock contention locally. Non-blocking suggestions:
To rule out any accidental performance regressions: would it be possible to benchmark before vs after this PR (with and without |
ccd73de
to
6e7b6bf
Compare
I think I'd need your help (propose a commit to pull in?) or you could do it as a follow-up.
I'll look. Without |
@laanwj: following up on our IRC convo in |
To-do:
|
Shouldn't this be enabled in the functional test already by default? |
You're right, TIL. I was thinking of adding something like this assertion but probably not worth it or reliable enough: with node.assert_debug_log(expected_msgs=["Lock contention: cs_main"]):
node.generate(COINBASE_MATURITY) |
Concept ACK |
Observations
if (LogAcceptCategory(BCLog::LOCK)) {
if (!Base::try_lock()) {
LogPrintf("Lock contention: %s, %s:%d\n", pszName, pszFile, nLine);
Base::lock();
+ } else {
+ LogPrintf("No lock contention\n");
}
Edit: recalculated the numbers and updated. Some resources:
|
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.
Code-review ACK 6e7b6bf
Concept ACK. |
I think the benchmarking results could depend on a platform. Could we sacrifice the ability to turn "lock" debugging on/off at runtime, and use just a const? |
I could be wrong, and this goes way beyond my competence, but readings suggest that these options are low-level, fast, and near constant time compared to system calls, memory/cache coherency and fences, wait queues and (particularly) lock contentions. (Would a difference be apparent in, say, https://bitcoinperf.com? I don't know, but if this helps us reduce contentions, that may be a win. @jamesob, @0xB10C, @martinus thoughts?) |
And |
Alternative version: void Enter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, Base::mutex());
if (Base::try_lock()) return;
Base::lock();
LogPrint(BCLog::LOCK, "Lock contention: %s, %s:%d\n", pszName, pszFile, nLine);
} |
Nice! Maybe put logging before the blocking call |
I don't know.
Seems like a good idea--done. |
How about microseconds?
diff --git a/src/logging/timer.h b/src/logging/timer.h
index 159920e397..cadfe395ee 100644
--- a/src/logging/timer.h
+++ b/src/logging/timer.h
@@ -93,6 +93,8 @@ private:
} // namespace BCLog
+#define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \
+ BCLog::Timer<std::chrono::microseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category)
#define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \
BCLog::Timer<std::chrono::milliseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category)
#define LOG_TIME_SECONDS(end_msg) \
diff --git a/src/sync.cpp b/src/sync.cpp
index a7b63ea986..eace86d9dd 100644
--- a/src/sync.cpp
+++ b/src/sync.cpp
@@ -24,9 +24,9 @@
#include <utility>
#include <vector>
-void PrintLockContention(const char* pszName, const char* pszFile, int nLine)
+void LockContention(const char* pszName, const char* pszFile, int nLine)
{
- LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("%s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK);
+ LOG_TIME_MICROS_WITH_CATEGORY(strprintf("%s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK);
}
#ifdef DEBUG_LOCKORDER
diff --git a/src/sync.h b/src/sync.h
index 252dc95845..0a8ec9e58f 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -126,7 +126,8 @@ using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>;
/** Wrapped mutex: supports waiting but not recursive locking */
typedef AnnotatedMixin<std::mutex> Mutex;
-void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
+/** Print a lock contention to the log */
+void LockContention(const char* pszName, const char* pszFile, int nLine);
/** Wrapper around std::unique_lock style lock for Mutex. */
template <typename Mutex, typename Base = typename Mutex::UniqueLock>
@@ -137,7 +138,7 @@ private:
{
EnterCritical(pszName, pszFile, nLine, Base::mutex());
if (Base::try_lock()) return;
- PrintLockContention(pszName, pszFile, nLine);
+ LockContention(pszName, pszFile, nLine);
Base::lock();
}
|
yes, the only guarantee with By the way, I think that in that patch: if (LogAcceptCategory(BCLog::LOCK)) {
if (!Base::try_lock()) {
LogPrintf("Lock contention: %s, %s:%d\n", pszName, pszFile, nLine);
Base::lock();
+ } else {
+ LogPrintf("No lock contention\n");
} If |
1edbd5d
to
27392e2
Compare
and update BCLog::LogMsg() to omit irrelevant decimals for microseconds and skip unneeded code and math.
27392e2
to
e2ab1a6
Compare
Thanks for the ideas and feedback. Updates:
Good point. I read that doc previously and admit to discounting the spurious failures a little, as we don't know the frequency. It seems to be an additional reason to log the contention duration. |
in microseconds. Change the function name in order to print "LockContention" instead of "PrintLockContention" to the log. Add Doxygen documentation. With this change, the lock contention log prints: 2021-09-01T11:29:03Z LockContention: pnode->cs_vSend, net.cpp:1373 started 2021-09-01T11:29:03Z LockContention: pnode->cs_vSend, net.cpp:1373 completed (31μs) 2021-09-01T11:29:03Z LockContention: cs_vNodes, net.cpp:2277 started 2021-09-01T11:29:03Z LockContention: cs_vNodes, net.cpp:2277 completed (6μs) 2021-09-01T11:29:04Z LockContention: cs_vNodes, net.cpp:2242 started 2021-09-01T11:29:04Z LockContention: cs_vNodes, net.cpp:2242 completed (3μs) Co-authored-by: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Co-authored-by: practicalswift <practicalswift@users.noreply.github.com>
to allow logging the lock contentions without the need to define DEBUG_LOCKCONTENTION at compile time.
e2ab1a6
to
7e69873
Compare
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.
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.
re-ACK 7e69873 🔏 ⏲️
Since my last ACK, the lock contention logging was improved by also showing the duration in micro-seconds, and an alternate (more elegant) version of UniqueLock::Enter
is now used that doesn't need to check whether the lock
category is on if there is no contention (#22736 (comment)).
Tested by building with clang 10.0.1 on OpenBSD 6.9 and running ./src/bitcoind -signet -debug=lock
on an empty signet datadir.
On my system, during IBD the vast majority of lock contentions happen in
- cs_main, net_processing.cpp:1476
- m_mutex, ./checkqueue.h:168
- m_mutex, ./checkqueue.h:77
After IBD is finished, all lock contentions are in
- cs_vNodes, net.cpp (different lines)
Most contentions are in the range of 5-10us. (Note that these results are not based on deeper analysis, but on what I saw scrolling through the logs after a few minutes).
Thanks for testing! For me, there seems to be a higher frequency of contention and occuring in more places on mainnet than on signet, presumably as the scale/numbers are larger. Also, a big difference in contention durations between a regular build and a debug build, which makes sense. |
…g macro to fix duration, improve BCLog::LogMsg() f530202 Make unexpected time type in BCLog::LogMsg() a compile-time error (Martin Ankerl) bddae7e Add util/types.h with ALWAYS_FALSE template (MarcoFalke) 498b323 log, timer: improve BCLog::LogMsg() (Jon Atack) 8d2f847 sync: inline lock contention logging macro to fix time duration (Jon Atack) Pull request description: Follow-up to #22736. The first commit addresses the issue identified and reported by Martin Ankerl in bitcoin/bitcoin#22736 (comment) to fix the lock contention duration reporting. The next three commits make improvements to the timer code in `BCLog::LogMsg()` and add `util/types.h` with an `ALWAYS_FALSE` template, that springboard from bitcoin/bitcoin#22736 (comment) by Marco Falke. ACKs for top commit: martinus: re-ACK f530202. I ran a fully synced node for about a day. My node was mostly idle though so not much was going on. I [wrote a little script](https://github.com/martinus/bitcoin-stuff/blob/main/scripts/parse-debuglog-contention-single.rb) to parse the `debug.log` and summarize the output to see if anything interesting was going on, here is the result: theStack: ACK f530202 Tree-SHA512: 37d093eac5590e1b5846ab5994d0950d71e131177d1afe4a5f7fcd614270f977e0ea117e7af788e9a74ddcccab35b42ec8fa4db3a3378940d4988df7d21cdaaa
…to fix duration, improve BCLog::LogMsg() f530202 Make unexpected time type in BCLog::LogMsg() a compile-time error (Martin Ankerl) bddae7e Add util/types.h with ALWAYS_FALSE template (MarcoFalke) 498b323 log, timer: improve BCLog::LogMsg() (Jon Atack) 8d2f847 sync: inline lock contention logging macro to fix time duration (Jon Atack) Pull request description: Follow-up to bitcoin#22736. The first commit addresses the issue identified and reported by Martin Ankerl in bitcoin#22736 (comment) to fix the lock contention duration reporting. The next three commits make improvements to the timer code in `BCLog::LogMsg()` and add `util/types.h` with an `ALWAYS_FALSE` template, that springboard from bitcoin#22736 (comment) by Marco Falke. ACKs for top commit: martinus: re-ACK f530202. I ran a fully synced node for about a day. My node was mostly idle though so not much was going on. I [wrote a little script](https://github.com/martinus/bitcoin-stuff/blob/main/scripts/parse-debuglog-contention-single.rb) to parse the `debug.log` and summarize the output to see if anything interesting was going on, here is the result: theStack: ACK f530202 Tree-SHA512: 37d093eac5590e1b5846ab5994d0950d71e131177d1afe4a5f7fcd614270f977e0ea117e7af788e9a74ddcccab35b42ec8fa4db3a3378940d4988df7d21cdaaa
…TION preprocessor directive 4394733 Add DEBUG_LOCKCONTENTION documentation to the developer notes (Jon Atack) 39a34b6 Put lock logging behind DEBUG_LOCKCONTENTION preprocessor directive (Jon Atack) Pull request description: This is a more minimal, no-frills version of #24734 for backport. The other fixes and improvements in that pull can be done after. *Copy of the PR 24734 description:* PRs #22736, #22904 and #23223 changed lock contention logging from a `DEBUG_LOCKCONTENTION` compile-time preprocessor directive to a runtime `lock` log category and improved the logging output. This changed the locking from using `lock()` to `try_lock()`: - `void Mutex::UniqueLock::lock()` acquires the mutex and blocks until it gains access to it - `bool Mutex::UniqueLock::try_lock()` doesn't block but instead immediately returns whether it acquired the mutex; it may be used by `lock()` internally as part of the deadlock-avoidance algorithm In theory the cost of `try_lock` might be essentially the [same](https://www.erisian.com.au/bitcoin-core-dev/log-2022-03-31.html#l-697) relative to `lock`. The test-and-set logic of these calls is purported to be ~ constant time, optimised and light/quick if used carefully (i.e. no mutex convoying), compared to system calls, memory/cache coherency and fences, wait queues, and (particularly) lock contentions. See the discussion around bitcoin/bitcoin#22736 (comment) and after with respect to performance/cost aspects. However, there are reasonable concerns (see [here](bitcoin/bitcoin#22736 (comment)) and [here](https://www.erisian.com.au/bitcoin-core-dev/log-2022-03-31.html#l-620)) that `Base::try_lock()` may be potentially [costly](https://www.erisian.com.au/bitcoin-core-dev/log-2022-03-31.html#l-700) or [risky](bitcoin/bitcoin#22904 (comment)) compared to `Base::lock()` in this very frequently called code. One alternative to keep the run-time lock logging would be to gate the `try_lock` call behind the logging conditional, for example as proposed in bitcoin/bitcoin@ccd73de and ACKed [here](bitcoin/bitcoin#22736 (comment)). However, this would add the [cost](bitcoin/bitcoin#22736 (comment)) of `if (LogAcceptCategory(BCLog::LOCK))` to the hotspot, instead of replacing `lock` with `try_lock`, for the most frequent happy path (non-contention). It turns out we can keep the advantages of the runtime lock contention logging (the ability to turn it on/off at runtime) while out of prudence putting the `try_lock()` call and `lock` logging category behind a `DEBUG_LOCKCONTENTION` compile-time preprocessor directive, and also still retain the lock logging enhancements of the mentioned PRs, as suggested in bitcoin/bitcoin#24734 (comment) by W. J. van der Laan, in bitcoin/bitcoin#22736 (comment), and in the linked IRC discussion. Proposed here and for backport to v23. ACKs for top commit: laanwj: Code review ACK 4394733 Tree-SHA512: 89b1271cae1dca0eb251914b1a60fc5b68320aab4a3939c57eec3a33a3c8f01688f05d95dfc31f91d71a6ed80cfe2d67b77ff14742611cc206175e47b2e5d3b1
To enable lock contention logging,
DEBUG_LOCKCONTENTION
has to be defined at compilation. Once built, the logging is not limited to a category and is high frequency, verbose and in all-caps. With these factors combined, it seems likely to be rarely used.This patch:
lock
logging categoryLOG_TIME_MICROS_WITH_CATEGORY
BCLog::LogMsg()
to omit irrelevant decimals for microseconds and skip unneeded code and math-debug=lock
or at run time withbitcoin-cli logging '["lock"]'
I've tested this with Clang 13 and GCC 10.2.1, on Debian, with and without
--enable-debug
.