-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[server] add unused ENVOY_BUG implementation #11503
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
Changes from all commits
ff7ca4f
798bb0e
8f9aba6
1ed929d
e3b47af
c5ab825
00e851e
26cbe25
1adce9f
ce70b00
aacf835
b56f27f
f84faf9
e598f28
9f036bb
4b2b808
6ba5df6
82afac3
2b1e871
3cdbc57
2d2f5ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| #include "common/common/assert.h" | ||
|
|
||
| #include "absl/container/flat_hash_map.h" | ||
| #include "absl/strings/str_join.h" | ||
| #include "absl/synchronization/mutex.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Assert { | ||
|
|
||
|
|
@@ -28,15 +32,84 @@ class ActionRegistrationImpl : public ActionRegistration { | |
| static std::function<void()> debug_assertion_failure_record_action_; | ||
| }; | ||
|
|
||
| // This class implements the logic for triggering ENVOY_BUG logs and actions. Logging and actions | ||
| // will be triggered with exponential back-off per file and line bug. | ||
| class EnvoyBugRegistrationImpl : public ActionRegistration { | ||
| public: | ||
| EnvoyBugRegistrationImpl(std::function<void()> action) { | ||
| ASSERT(envoy_bug_failure_record_action_ == nullptr, | ||
| "An ENVOY_BUG action was already set. Currently only a single action is supported."); | ||
| envoy_bug_failure_record_action_ = action; | ||
| counters_.clear(); | ||
| } | ||
|
|
||
| ~EnvoyBugRegistrationImpl() override { | ||
| ASSERT(envoy_bug_failure_record_action_ != nullptr); | ||
| envoy_bug_failure_record_action_ = nullptr; | ||
| } | ||
|
|
||
| // This method is invoked when an ENVOY_BUG condition fails. It increments a per file and line | ||
| // counter for every ENVOY_BUG hit in a mutex guarded map. | ||
| // The implementation may also be a inline static counter per-file and line. There is no benchmark | ||
| // to show that the performance of this mutex is any worse than atomic counters. Acquiring and | ||
| // releasing a mutex is cheaper than a cache miss, but the mutex here is contended for every | ||
| // ENVOY_BUG failure rather than per individual bug. Logging ENVOY_BUGs is not a performance | ||
| // critical path, and mutex contention would indicate that there is a serious failure. | ||
| // Currently, this choice reduces code size and has the advantage that behavior is easier to | ||
| // understand and debug, and test behavior is predictable. | ||
| static bool shouldLogAndInvoke(absl::string_view bug_name) { | ||
| // Increment counter, inserting first if counter does not exist. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I have a strong opinion on the map vs. inline static, but can you add more comments here on the trade-offs and why we chose this way? I assume it's to reduce code size and I guess per the discussion avoid cache misses on the inline atomic?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not that we're trying to avoid cache misses on the atomic; if we aren't worried about cache misses in this part of Envoy, and if we don't have benchmarks proving that atomics are significantly more efficient, then we don't have a performance argument for choosing atomics over a higher-level, easier-to-understand construct like a mutex. Quoting from an internal doc on the dangers of atomics that I've been meaning to open-source: There's a common assumption that mutexes are expensive, and that using atomic operations will be more efficient. But in reality, acquiring and releasing a mutex is cheaper than a cache miss; attention to cache behavior is usually a more fruitful way to improve performance.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. Is the solution to have some kind of static object per file/line in the macro that holds a mutex per file/line and makes accesses to the map? Or is that starting to get too complicated.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, a per file/line mutex/object/whatever is more complicated than is necessary at the moment. If we start seeing contention on this mutex, we'd have a lot of ENVOY_BUGs firing at once, which is worth investigation on its own. But I don't think we need to prematurely optimize this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a comment about this.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would just add a comment explaining that this is not performance critical path and contention on this mutex would imply that something horribly went off the rails and caused ENVOY_BUG to fire often on multiple threads.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| uint64_t counter_value = 0; | ||
| { | ||
| absl::MutexLock lock(&mutex_); | ||
| counter_value = ++counters_[bug_name]; | ||
| } | ||
|
|
||
| // Check if counter is power of two by its bitwise representation. | ||
| return (counter_value & (counter_value - 1)) == 0; | ||
| } | ||
|
|
||
| static void invokeAction() { | ||
| if (envoy_bug_failure_record_action_ != nullptr) { | ||
| envoy_bug_failure_record_action_(); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| // This implementation currently only handles one action being set at a time. This is currently | ||
| // sufficient. If multiple actions are ever needed, the actions should be chained when | ||
| // additional actions are registered. | ||
| static std::function<void()> envoy_bug_failure_record_action_; | ||
|
|
||
| using EnvoyBugMap = absl::flat_hash_map<std::string, uint64_t>; | ||
| static absl::Mutex mutex_; | ||
| static EnvoyBugMap counters_ GUARDED_BY(mutex_); | ||
| }; | ||
|
|
||
| std::function<void()> ActionRegistrationImpl::debug_assertion_failure_record_action_; | ||
| std::function<void()> EnvoyBugRegistrationImpl::envoy_bug_failure_record_action_; | ||
| EnvoyBugRegistrationImpl::EnvoyBugMap EnvoyBugRegistrationImpl::counters_; | ||
| absl::Mutex EnvoyBugRegistrationImpl::mutex_; | ||
|
|
||
| ActionRegistrationPtr setDebugAssertionFailureRecordAction(const std::function<void()>& action) { | ||
| return std::make_unique<ActionRegistrationImpl>(action); | ||
| } | ||
|
|
||
| void invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly() { | ||
| ActionRegistrationPtr setEnvoyBugFailureRecordAction(const std::function<void()>& action) { | ||
| return std::make_unique<EnvoyBugRegistrationImpl>(action); | ||
| } | ||
|
|
||
| void invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly() { | ||
| ActionRegistrationImpl::invokeAction(); | ||
| } | ||
|
|
||
| void invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly() { | ||
| EnvoyBugRegistrationImpl::invokeAction(); | ||
| } | ||
|
|
||
| bool shouldLogAndInvokeEnvoyBugForEnvoyBugMacroUseOnly(absl::string_view bug_name) { | ||
| return EnvoyBugRegistrationImpl::shouldLogAndInvoke(bug_name); | ||
| } | ||
|
|
||
| } // namespace Assert | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,13 +32,44 @@ using ActionRegistrationPtr = std::unique_ptr<ActionRegistration>; | |
| */ | ||
| ActionRegistrationPtr setDebugAssertionFailureRecordAction(const std::function<void()>& action); | ||
|
|
||
| /** | ||
| * Sets an action to be invoked when an ENVOY_BUG failure is detected in a release build. This | ||
| * action will be invoked each time an ENVOY_BUG failure is detected. | ||
| * | ||
| * This function is not thread-safe; concurrent calls to set the action are not allowed. | ||
| * | ||
| * The action may be invoked concurrently if two ENVOY_BUGs in different threads fail at the | ||
| * same time, so the action must be thread-safe. | ||
| * | ||
| * This has no effect in debug builds (envoy bug failure aborts the process). | ||
| * | ||
| * @param action The action to take when an envoy bug fails. | ||
| * @return A registration object. The registration is removed when the object is destructed. | ||
| */ | ||
| ActionRegistrationPtr setEnvoyBugFailureRecordAction(const std::function<void()>& action); | ||
|
|
||
| /** | ||
| * Invokes the action set by setDebugAssertionFailureRecordAction, or does nothing if | ||
| * no action has been set. | ||
| * | ||
| * This should only be called by ASSERT macros in this file. | ||
| */ | ||
| void invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly(); | ||
| void invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly(); | ||
|
|
||
| /** | ||
| * Invokes the action set by setEnvoyBugFailureRecordAction, or does nothing if | ||
| * no action has been set. | ||
| * | ||
| * This should only be called by ENVOY_BUG macros in this file. | ||
| */ | ||
| void invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly(); | ||
|
|
||
| /** | ||
| * Increments power of two counter for EnvoyBugRegistrationImpl. | ||
| * | ||
| * This should only be called by ENVOY_BUG macros in this file. | ||
| */ | ||
| bool shouldLogAndInvokeEnvoyBugForEnvoyBugMacroUseOnly(absl::string_view bug_name); | ||
|
|
||
| // CONDITION_STR is needed to prevent macros in condition from being expected, which obfuscates | ||
| // the logged failure, e.g., "EAGAIN" vs "11". | ||
|
|
@@ -87,7 +118,7 @@ void invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly(); | |
| #if !defined(NDEBUG) // If this is a debug build. | ||
| #define ASSERT_ACTION abort() | ||
| #else // If this is not a debug build, but ENVOY_LOG_DEBUG_ASSERT_IN_RELEASE is defined. | ||
| #define ASSERT_ACTION Envoy::Assert::invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly() | ||
| #define ASSERT_ACTION Envoy::Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly() | ||
| #endif // !defined(NDEBUG) | ||
|
|
||
| #define _ASSERT_ORIGINAL(X) _ASSERT_IMPL(X, #X, ASSERT_ACTION, "") | ||
|
|
@@ -111,7 +142,7 @@ void invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly(); | |
| // This non-implementation ensures that its argument is a valid expression that can be statically | ||
| // casted to a bool, but the expression is never evaluated and will be compiled away. | ||
| #define KNOWN_ISSUE_ASSERT _NULL_ASSERT_IMPL | ||
| #endif // defined(ENVOY_DEBUG_KNOWN_ISSUES) | ||
| #endif // defined(ENVOY_DISABLE_KNOWN_ISSUE_ASSERTS) | ||
|
|
||
| // If ASSERT is called with one argument, the ASSERT_SELECTOR will return | ||
| // _ASSERT_ORIGINAL and this will call _ASSERT_ORIGINAL(__VA_ARGS__). | ||
|
|
@@ -134,6 +165,42 @@ void invokeDebugAssertionFailureRecordAction_ForAssertMacroUseOnly(); | |
| abort(); \ | ||
| } while (false) | ||
|
|
||
| #if !defined(NDEBUG) | ||
| #define ENVOY_BUG_ACTION abort() | ||
| #else | ||
| #define ENVOY_BUG_ACTION Envoy::Assert::invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly() | ||
| #endif | ||
|
|
||
| // These macros are needed to stringify __LINE__ correctly. | ||
| #define STRINGIFY(X) #X | ||
| #define TOSTRING(X) STRINGIFY(X) | ||
|
|
||
| // CONDITION_STR is needed to prevent macros in condition from being expected, which obfuscates | ||
| // the logged failure, e.g., "EAGAIN" vs "11". | ||
| // ENVOY_BUG logging and actions are invoked only on power-of-two instances per log line. | ||
| #define _ENVOY_BUG_IMPL(CONDITION, CONDITION_STR, ACTION, DETAILS) \ | ||
| do { \ | ||
| if (!(CONDITION) && Envoy::Assert::shouldLogAndInvokeEnvoyBugForEnvoyBugMacroUseOnly( \ | ||
| __FILE__ ":" TOSTRING(__LINE__))) { \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have to make sure that windows build uses appropriate compiler flags, as by default FILE does not have full path, just the filename. @sunjayBhatia @wrowe
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this issue of the lack of uniqueness in the contents of FILE an argument to re-consider use of static atomics for these counters?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't counters have the same issue, since they'd be initialized according to file/line
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the regular component/misc logging uses
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't find specifically where we pass |
||
| const std::string& details = (DETAILS); \ | ||
| ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::envoy_bug), error, \ | ||
| "envoy bug failure: {}.{}{}", CONDITION_STR, \ | ||
| details.empty() ? "" : " Details: ", details); \ | ||
asraa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ACTION; \ | ||
| } \ | ||
| } while (false) | ||
|
|
||
| #define _ENVOY_BUG_VERBOSE(X, Y) _ENVOY_BUG_IMPL(X, #X, ENVOY_BUG_ACTION, Y) | ||
|
|
||
| /** | ||
| * Indicate a failure condition that should never be met in normal circumstances. In contrast | ||
| * with ASSERT, an ENVOY_BUG is compiled in release mode. If a failure condition is met in release | ||
| * mode, it is logged and a stat is incremented with exponential back-off per ENVOY_BUG. In debug | ||
| * mode, it will crash if the condition is not met. ENVOY_BUG must be called with two arguments for | ||
| * verbose logging. | ||
| */ | ||
| #define ENVOY_BUG(...) _ENVOY_BUG_VERBOSE(__VA_ARGS__) | ||
|
|
||
| // NOT_IMPLEMENTED_GCOVR_EXCL_LINE is for overridden functions that are expressly not implemented. | ||
| // The macro name includes "GCOVR_EXCL_LINE" to exclude the macro's usage from code coverage | ||
| // reports. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.