• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/threading/scoped_blocking_call_internal.h"
11 
12 #include <algorithm>
13 #include <utility>
14 
15 #include "base/check_op.h"
16 #include "base/compiler_specific.h"
17 #include "base/functional/bind.h"
18 #include "base/functional/callback_helpers.h"
19 #include "base/no_destructor.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/scoped_clear_last_error.h"
22 #include "base/task/scoped_set_task_priority_for_current_thread.h"
23 #include "base/task/thread_pool.h"
24 #include "base/task/thread_pool/environment_config.h"
25 #include "base/task/thread_pool/thread_pool_instance.h"
26 #include "base/threading/scoped_blocking_call.h"
27 #include "build/build_config.h"
28 
29 namespace base {
30 namespace internal {
31 
32 namespace {
33 
34 constinit thread_local BlockingObserver* blocking_observer = nullptr;
35 
36 // Last ScopedBlockingCall instantiated on this thread.
37 constinit thread_local UncheckedScopedBlockingCall* last_scoped_blocking_call =
38     nullptr;
39 
40 // These functions can be removed, and the calls below replaced with direct
41 // variable accesses, once the MSAN workaround is not necessary.
GetBlockingObserver()42 BlockingObserver* GetBlockingObserver() {
43   // Workaround false-positive MSAN use-of-uninitialized-value on
44   // thread_local storage for loaded libraries:
45   // https://github.com/google/sanitizers/issues/1265
46   MSAN_UNPOISON(&blocking_observer, sizeof(BlockingObserver*));
47 
48   return blocking_observer;
49 }
GetLastScopedBlockingCall()50 UncheckedScopedBlockingCall* GetLastScopedBlockingCall() {
51   // Workaround false-positive MSAN use-of-uninitialized-value on
52   // thread_local storage for loaded libraries:
53   // https://github.com/google/sanitizers/issues/1265
54   MSAN_UNPOISON(&last_scoped_blocking_call,
55                 sizeof(UncheckedScopedBlockingCall*));
56 
57   return last_scoped_blocking_call;
58 }
59 
60 // Set to true by scoped_blocking_call_unittest to ensure unrelated threads
61 // entering ScopedBlockingCalls don't affect test outcomes.
62 bool g_only_monitor_observed_threads = false;
63 
IsBackgroundPriorityWorker()64 bool IsBackgroundPriorityWorker() {
65   return GetTaskPriorityForCurrentThread() == TaskPriority::BEST_EFFORT &&
66          CanUseBackgroundThreadTypeForWorkerThread();
67 }
68 
69 }  // namespace
70 
SetBlockingObserverForCurrentThread(BlockingObserver * new_blocking_observer)71 void SetBlockingObserverForCurrentThread(
72     BlockingObserver* new_blocking_observer) {
73   DCHECK(!GetBlockingObserver());
74   blocking_observer = new_blocking_observer;
75 }
76 
ClearBlockingObserverForCurrentThread()77 void ClearBlockingObserverForCurrentThread() {
78   blocking_observer = nullptr;
79 }
80 
ScopedMonitoredCall()81 IOJankMonitoringWindow::ScopedMonitoredCall::ScopedMonitoredCall()
82     : call_start_(TimeTicks::Now()),
83       assigned_jank_window_(MonitorNextJankWindowIfNecessary(call_start_)) {
84   if (assigned_jank_window_ &&
85       call_start_ < assigned_jank_window_->start_time_) {
86     // Sampling |call_start_| and being assigned an IOJankMonitoringWindow is
87     // racy. It is possible that |call_start_| is sampled near the very end of
88     // the current window; meanwhile, another ScopedMonitoredCall on another
89     // thread samples a |call_start_| which lands in the next window. If that
90     // thread beats this one to MonitorNextJankWindowIfNecessary(), this thread
91     // will incorrectly be assigned that window (in the future w.r.t. to its
92     // |call_start_|). To avoid OOB-indexing in AddJank(), crbug.com/1209622, it
93     // is necessary to correct this by bumping |call_start_| to the received
94     // window's |start_time_|.
95     //
96     // Note: The alternate approach of getting |assigned_jank_window_| before
97     // |call_start_| has the opposite problem where |call_start_| can be more
98     // than kNumIntervals ahead of |start_time_| when sampling across the window
99     // boundary, resulting in OOB-indexing the other way. To solve that a loop
100     // would be required (re-getting the latest window and re-sampling
101     // |call_start_| until the condition holds). The loopless solution is thus
102     // preferred.
103     //
104     // A lock covering this entire constructor is also undesired because of the
105     // lock-free logic at the end of MonitorNextJankWindowIfNecessary().
106     call_start_ = assigned_jank_window_->start_time_;
107   }
108 }
109 
~ScopedMonitoredCall()110 IOJankMonitoringWindow::ScopedMonitoredCall::~ScopedMonitoredCall() {
111   if (assigned_jank_window_) {
112     assigned_jank_window_->OnBlockingCallCompleted(call_start_,
113                                                    TimeTicks::Now());
114   }
115 }
116 
Cancel()117 void IOJankMonitoringWindow::ScopedMonitoredCall::Cancel() {
118   assigned_jank_window_ = nullptr;
119 }
120 
IOJankMonitoringWindow(TimeTicks start_time)121 IOJankMonitoringWindow::IOJankMonitoringWindow(TimeTicks start_time)
122     : start_time_(start_time) {}
123 
124 // static
CancelMonitoringForTesting()125 void IOJankMonitoringWindow::CancelMonitoringForTesting() {
126   g_only_monitor_observed_threads = false;
127   AutoLock lock(current_jank_window_lock());
128   current_jank_window_storage() = nullptr;
129   reporting_callback_storage() = NullCallback();
130 }
131 
132 // static
133 constexpr TimeDelta IOJankMonitoringWindow::kIOJankInterval;
134 // static
135 constexpr TimeDelta IOJankMonitoringWindow::kMonitoringWindow;
136 // static
137 constexpr TimeDelta IOJankMonitoringWindow::kTimeDiscrepancyTimeout;
138 // static
139 constexpr int IOJankMonitoringWindow::kNumIntervals;
140 
141 // static
142 scoped_refptr<IOJankMonitoringWindow>
MonitorNextJankWindowIfNecessary(TimeTicks recent_now)143 IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(TimeTicks recent_now) {
144   DCHECK_GE(TimeTicks::Now(), recent_now);
145 
146   scoped_refptr<IOJankMonitoringWindow> next_jank_window;
147 
148   {
149     AutoLock lock(current_jank_window_lock());
150 
151     if (!reporting_callback_storage())
152       return nullptr;
153 
154     scoped_refptr<IOJankMonitoringWindow>& current_jank_window_ref =
155         current_jank_window_storage();
156 
157     // Start the next window immediately after the current one (rather than
158     // based on Now() to avoid uncovered gaps). Only use Now() for the very
159     // first window in a monitoring chain.
160     TimeTicks next_window_start_time =
161         current_jank_window_ref
162             ? current_jank_window_ref->start_time_ + kMonitoringWindow
163             : recent_now;
164 
165     if (next_window_start_time > recent_now) {
166       // Another thread beat us to constructing the next monitoring window and
167       // |current_jank_window_ref| already covers |recent_now|.
168       return current_jank_window_ref;
169     }
170 
171     if (recent_now - next_window_start_time >= kTimeDiscrepancyTimeout) {
172       // If the delayed task runs on a regular heartbeat, |recent_now| should be
173       // roughly equal to |next_window_start_time|. If we miss by more than
174       // kTimeDiscrepancyTimeout, we likely hit machine sleep, cancel sampling
175       // that window in that case.
176       //
177       // Note: It is safe to touch |canceled_| without a lock here as this is
178       // the only time it's set and it naturally happens-before
179       // |current_jank_window_ref|'s destructor reads it.
180       current_jank_window_ref->canceled_ = true;
181       next_window_start_time = recent_now;
182     }
183 
184     next_jank_window =
185         MakeRefCounted<IOJankMonitoringWindow>(next_window_start_time);
186 
187     if (current_jank_window_ref && !current_jank_window_ref->canceled_) {
188       // If there are still IO operations in progress within
189       // |current_jank_window_ref|, they have a ref to it and will be the ones
190       // triggering ~IOJankMonitoringWindow(). When doing so, they will overlap
191       // into the |next_jank_window| we are setting up (|next_| will also own a
192       // ref so a very long jank can safely unwind across a chain of pending
193       // |next_|'s).
194       DCHECK(!current_jank_window_ref->next_);
195       current_jank_window_ref->next_ = next_jank_window;
196     }
197 
198     // Make |next_jank_window| the new current before releasing the lock.
199     current_jank_window_ref = next_jank_window;
200   }
201 
202   // Post a task to kick off the next monitoring window if no monitored thread
203   // beats us to it. Adjust the timing to alleviate any drift in the timer. Do
204   // this outside the lock to avoid scheduling tasks while holding it.
205   ThreadPool::PostDelayedTask(
206       FROM_HERE, BindOnce([] {
207         IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(
208             TimeTicks::Now());
209       }),
210       kMonitoringWindow - (recent_now - next_jank_window->start_time_));
211 
212   return next_jank_window;
213 }
214 
215 // NO_THREAD_SAFETY_ANALYSIS because ~RefCountedThreadSafe() guarantees we're
216 // the last ones to access this state (and ordered after all other accesses).
~IOJankMonitoringWindow()217 IOJankMonitoringWindow::~IOJankMonitoringWindow() NO_THREAD_SAFETY_ANALYSIS {
218   if (canceled_)
219     return;
220 
221   int janky_intervals_count = 0;
222   int total_jank_count = 0;
223 
224   for (size_t interval_jank_count : intervals_jank_count_) {
225     if (interval_jank_count > 0) {
226       ++janky_intervals_count;
227       total_jank_count += interval_jank_count;
228     }
229   }
230 
231   // reporting_callback_storage() is safe to access without lock because an
232   // IOJankMonitoringWindow existing means we're after the call to
233   // EnableIOJankMonitoringForProcess() and it will not change after that call.
234   DCHECK(reporting_callback_storage());
235   reporting_callback_storage().Run(janky_intervals_count, total_jank_count);
236 }
237 
OnBlockingCallCompleted(TimeTicks call_start,TimeTicks call_end)238 void IOJankMonitoringWindow::OnBlockingCallCompleted(TimeTicks call_start,
239                                                      TimeTicks call_end) {
240   // Confirm we never hit a case of TimeTicks going backwards on the same thread
241   // nor of TimeTicks rolling over the int64_t boundary (which would break
242   // comparison operators).
243   DCHECK_LE(call_start, call_end);
244 
245   if (call_end - call_start < kIOJankInterval)
246     return;
247 
248   // Make sure the chain of |next_| pointers is sufficient to reach
249   // |call_end| (e.g. if this runs before the delayed task kicks in)
250   if (call_end >= start_time_ + kMonitoringWindow)
251     MonitorNextJankWindowIfNecessary(call_end);
252 
253   // Begin attributing jank to the first interval in which it appeared, no
254   // matter how far into the interval the jank began.
255   const int jank_start_index =
256       ClampFloor((call_start - start_time_) / kIOJankInterval);
257 
258   // Round the jank duration so the total number of intervals marked janky is as
259   // close as possible to the actual jank duration.
260   const int num_janky_intervals =
261       ClampRound((call_end - call_start) / kIOJankInterval);
262 
263   AddJank(jank_start_index, num_janky_intervals);
264 }
265 
AddJank(int local_jank_start_index,int num_janky_intervals)266 void IOJankMonitoringWindow::AddJank(int local_jank_start_index,
267                                      int num_janky_intervals) {
268   DCHECK_GE(local_jank_start_index, 0);
269   DCHECK_LT(local_jank_start_index, kNumIntervals);
270 
271   // Increment jank counts for intervals in this window. If
272   // |num_janky_intervals| lands beyond kNumIntervals, the additional intervals
273   // will be reported to |next_|.
274   const int jank_end_index = local_jank_start_index + num_janky_intervals;
275   const int local_jank_end_index = std::min(kNumIntervals, jank_end_index);
276 
277   {
278     // Note: while this window could be |canceled| here we must add our count
279     // unconditionally as it is only thread-safe to read |canceled| in
280     // ~IOJankMonitoringWindow().
281     AutoLock lock(intervals_lock_);
282     for (int i = local_jank_start_index; i < local_jank_end_index; ++i)
283       ++intervals_jank_count_[i];
284   }
285 
286   if (jank_end_index != local_jank_end_index) {
287     // OnBlockingCallCompleted() should have already ensured there's a |next_|
288     // chain covering |num_janky_intervals| unless it caused this to be
289     // |canceled_|. Exceptionally for this check, reading these fields when
290     // they're expected to be true is thread-safe as their only modification
291     // happened-before this point.
292     DCHECK(next_ || canceled_);
293     if (next_) {
294       // If |next_| is non-null, it means |this| wasn't canceled and it implies
295       // |next_| covers the time range starting immediately after this window.
296       DCHECK_EQ(next_->start_time_, start_time_ + kMonitoringWindow);
297       next_->AddJank(0, jank_end_index - local_jank_end_index);
298     }
299   }
300 }
301 
302 // static
current_jank_window_lock()303 Lock& IOJankMonitoringWindow::current_jank_window_lock() {
304   static NoDestructor<Lock> current_jank_window_lock;
305   return *current_jank_window_lock;
306 }
307 
308 // static
309 scoped_refptr<IOJankMonitoringWindow>&
current_jank_window_storage()310 IOJankMonitoringWindow::current_jank_window_storage() {
311   static NoDestructor<scoped_refptr<IOJankMonitoringWindow>>
312       current_jank_window;
313   return *current_jank_window;
314 }
315 
316 // static
reporting_callback_storage()317 IOJankReportingCallback& IOJankMonitoringWindow::reporting_callback_storage() {
318   static NoDestructor<IOJankReportingCallback> reporting_callback;
319   return *reporting_callback;
320 }
321 
UncheckedScopedBlockingCall(BlockingType blocking_type,BlockingCallType blocking_call_type)322 UncheckedScopedBlockingCall::UncheckedScopedBlockingCall(
323     BlockingType blocking_type,
324     BlockingCallType blocking_call_type)
325     : blocking_observer_(GetBlockingObserver()),
326       previous_scoped_blocking_call_(GetLastScopedBlockingCall()),
327       resetter_(&last_scoped_blocking_call, this),
328       is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
329                      (previous_scoped_blocking_call_ &&
330                       previous_scoped_blocking_call_->is_will_block_)) {
331   // Only monitor non-nested ScopedBlockingCall(MAY_BLOCK) calls on foreground
332   // threads. Cancels() any pending monitored call when a WILL_BLOCK or
333   // ScopedBlockingCallWithBaseSyncPrimitives nests into a
334   // ScopedBlockingCall(MAY_BLOCK).
335   if (!IsBackgroundPriorityWorker() &&
336       (!g_only_monitor_observed_threads || blocking_observer_)) {
337     const bool is_monitored_type =
338         blocking_call_type == BlockingCallType::kRegular && !is_will_block_;
339     if (is_monitored_type && !previous_scoped_blocking_call_) {
340       monitored_call_.emplace();
341     } else if (!is_monitored_type && previous_scoped_blocking_call_ &&
342                previous_scoped_blocking_call_->monitored_call_) {
343       previous_scoped_blocking_call_->monitored_call_->Cancel();
344     }
345   }
346 
347   if (blocking_observer_) {
348     if (!previous_scoped_blocking_call_) {
349       blocking_observer_->BlockingStarted(blocking_type);
350     } else if (blocking_type == BlockingType::WILL_BLOCK &&
351                !previous_scoped_blocking_call_->is_will_block_) {
352       blocking_observer_->BlockingTypeUpgraded();
353     }
354   }
355 }
356 
~UncheckedScopedBlockingCall()357 UncheckedScopedBlockingCall::~UncheckedScopedBlockingCall() {
358   // TLS affects result of GetLastError() on Windows. ScopedClearLastError
359   // prevents side effect.
360   ScopedClearLastError save_last_error;
361   DCHECK_EQ(this, GetLastScopedBlockingCall());
362   if (blocking_observer_ && !previous_scoped_blocking_call_)
363     blocking_observer_->BlockingEnded();
364 }
365 
366 }  // namespace internal
367 
EnableIOJankMonitoringForProcess(IOJankReportingCallback reporting_callback,OnlyObservedThreadsForTest only_observed_threads)368 void EnableIOJankMonitoringForProcess(
369     IOJankReportingCallback reporting_callback,
370     OnlyObservedThreadsForTest only_observed_threads) {
371   {
372     AutoLock lock(internal::IOJankMonitoringWindow::current_jank_window_lock());
373 
374     DCHECK(internal::IOJankMonitoringWindow::reporting_callback_storage()
375                .is_null());
376     internal::IOJankMonitoringWindow::reporting_callback_storage() =
377         std::move(reporting_callback);
378   }
379 
380   if (only_observed_threads) {
381     internal::g_only_monitor_observed_threads = true;
382   } else {
383     // Do not set it to `false` when it already is as that causes data races in
384     // browser tests (which EnableIOJankMonitoringForProcess after ThreadPool is
385     // already running).
386     DCHECK(!internal::g_only_monitor_observed_threads);
387   }
388 
389   // Make sure monitoring starts now rather than randomly at the next
390   // ScopedMonitoredCall construction.
391   internal::IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(
392       TimeTicks::Now());
393 }
394 
395 }  // namespace base
396