Update thread blocking call metrics and batcher logging Refine the logging/checking of thread blocking calls to allow for ignoring false positives in specific execution contexts/tests. In ScopedOperationsBatcher, introduce explicit tracking of expected blocking calls during operation batching in multi threaded environments. Key changes: * Added RTC_LOG_IGNORE_THREAD_BLOCK_COUNT to suppress metric collection when tracking is deemed uninformative. * Updated ScopedCountBlockingCalls with a Disable method to allow dynamic suppression of block-count reporting. * Adjusted the RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN macro to respect the enabled state of the tracker. * Allow Thread::HasPendingTasks to be called regardless of whether of being in a nested blocking call or not. The previous behavior was based on the "yield requested" implementation, which isn't strictly what HasPendingTasks returns. Bug: webrtc:42222804 Change-Id: Ia76f07af7d8355b7865fa5f6c4d04217b5cbf95a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/461640 Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47333}
diff --git a/pc/scoped_operations_batcher.cc b/pc/scoped_operations_batcher.cc index ad6ccc5..7ee6d43 100644 --- a/pc/scoped_operations_batcher.cc +++ b/pc/scoped_operations_batcher.cc
@@ -38,10 +38,23 @@ RTCError ScopedOperationsBatcher::Run() { RTC_DCHECK_RUN_ON(&sequence_checker_); - std::vector<FinalizerTask> return_tasks; + const bool target_thread_is_current = target_thread_->IsCurrent(); +#if RTC_DCHECK_IS_ON + RTC_LOG_THREAD_BLOCK_COUNT(); + int expected_block_count = tasks_.empty() ? 0 : 1; + if (target_thread_is_current) { + // Many tests in peerconnection_unittests run in single threaded mode where + // the operations below will not be accurately measured. Yielding is not + // supported for single threaded mode and tasks in those tests may + // internally run blocking tasks themselves, which affects the count but is + // not useful for the purposes of measuring the multithreaded behavior. + RTC_IGNORE_THREAD_BLOCK_COUNT(); + } +#endif + + std::vector<FinalizerTask> return_tasks; size_t task_idx = 0; - bool target_thread_is_current = target_thread_->IsCurrent(); RTCError error = RTCError::OK(); while (task_idx < tasks_.size()) { @@ -67,6 +80,9 @@ return; } if (!target_thread_is_current && target_thread_->HasPendingTasks()) { +#if RTC_DCHECK_IS_ON + ++expected_block_count; +#endif return; } } @@ -82,6 +98,11 @@ std::move(task)(); } +#if RTC_DCHECK_IS_ON + // If this triggers, then likely one of the `return_tasks` has issued a + // blocking call. + RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(expected_block_count); +#endif return error; }
diff --git a/rtc_base/thread.cc b/rtc_base/thread.cc index 81a7b6b..cc48a64 100644 --- a/rtc_base/thread.cc +++ b/rtc_base/thread.cc
@@ -311,7 +311,8 @@ start_time_ns_(TimeNanos()) {} Thread::ScopedCountBlockingCalls::~ScopedCountBlockingCalls() { - if (GetTotalBlockedCallCount() >= min_blocking_calls_for_callback_) { + if (GetTotalBlockedCallCount() >= min_blocking_calls_for_callback_ && + is_enabled()) { int64_t duration_us = (TimeNanos() - start_time_ns_) / 1000; std::move(result_callback_)(GetBlockingCallCount(), GetCouldBeBlockingCallCount(), @@ -333,6 +334,10 @@ uint32_t Thread::ScopedCountBlockingCalls::GetTotalBlockedCallCount() const { return GetBlockingCallCount() + GetCouldBeBlockingCallCount(); } + +void Thread::ScopedCountBlockingCalls::Disable() { + result_callback_ = nullptr; +} #else Thread::ScopedDisallowBlockingCalls::ScopedDisallowBlockingCalls() = default; Thread::ScopedDisallowBlockingCalls::~ScopedDisallowBlockingCalls() = default; @@ -764,12 +769,8 @@ RTC_DCHECK(this->IsInvokeToThreadAllowed(this)); RTC_DCHECK_RUN_ON(this); could_be_blocking_call_count_++; - ++running_synchronous_blocking_call_count_; #endif functor(); -#if RTC_DCHECK_IS_ON - --running_synchronous_blocking_call_count_; -#endif return; } @@ -971,11 +972,6 @@ bool Thread::HasPendingTasks() const { RTC_DCHECK_RUN_ON(this); -#if RTC_DCHECK_IS_ON - // If you've hit this, then there's a cooperative task running from inside a - // blocking call. - RTC_DCHECK_EQ(running_synchronous_blocking_call_count_, 0); -#endif MutexLock lock(&mutex_); return !messages_.empty(); }
diff --git a/rtc_base/thread.h b/rtc_base/thread.h index 2437272..790989c 100644 --- a/rtc_base/thread.h +++ b/rtc_base/thread.h
@@ -64,15 +64,27 @@ } \ }) +// For situations where an implementation decides that logging information about +// blocking calls is actually not useful, such as when a test is fundamentally +// single threaded and that may affect the behavior of blocking calls, or if a +// test purposely changes this behavior, this macro can disable an already +// initialized block-count logger object on the stack. +#define RTC_IGNORE_THREAD_BLOCK_COUNT() \ + do { \ + blocked_call_count_printer.Disable(); \ + } while (0) + // Adds an RTC_DCHECK_LE that checks that the number of blocking calls are // less than or equal to a specific value. Use to avoid regressing in the // number of blocking thread calls. // Note: Use of this macro, requires RTC_LOG_THREAD_BLOCK_COUNT() to be called // first. -#define RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x) \ - do { \ - blocked_call_count_printer.set_minimum_call_count_for_callback(x + 1); \ - RTC_DCHECK_LE(blocked_call_count_printer.GetTotalBlockedCallCount(), x); \ +#define RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x) \ + do { \ + if (blocked_call_count_printer.is_enabled()) { \ + blocked_call_count_printer.set_minimum_call_count_for_callback(x + 1); \ + RTC_DCHECK_LE(blocked_call_count_printer.GetTotalBlockedCallCount(), x); \ + } \ } while (0) // Use to disallow calls to Thread::BlockingCall() within a scope/function. @@ -81,6 +93,7 @@ #else #define RTC_LOG_THREAD_BLOCK_COUNT() +#define RTC_IGNORE_THREAD_BLOCK_COUNT() #define RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x) #define RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS() #endif @@ -239,11 +252,14 @@ uint32_t GetBlockingCallCount() const; uint32_t GetCouldBeBlockingCallCount() const; uint32_t GetTotalBlockedCallCount() const; + void Disable(); void set_minimum_call_count_for_callback(uint32_t minimum) { min_blocking_calls_for_callback_ = minimum; } + bool is_enabled() const { return result_callback_ != nullptr; } + private: Thread* const thread_; const uint32_t base_blocking_call_count_; @@ -553,16 +569,6 @@ friend class ThreadManager; int dispatch_warning_ms_ RTC_GUARDED_BY(this) = kSlowDispatchLoggingThreshold; - -#if RTC_DCHECK_IS_ON - // This is used to catch if a cooperative task ends up being called from - // within another task. If that happens, the risk is that a full yield won't - // actually happen, so this is to help with ensuring we catch when things - // don't run as expected since webrtc can be configured in many ways and - // sometimes virtual thread concepts such as worker and network threads, can - // map to the same thread object. - int running_synchronous_blocking_call_count_ RTC_GUARDED_BY(this) = 0; -#endif }; // AutoThread automatically installs itself at construction