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